<?xml version="1.0" encoding="utf-8"?>
<!-- name="GENERATOR" content="github.com/mmarkdown/mmark Mmark Markdown Processor - mmark.miek.nl" -->
<rfc version="3" ipr="trust200902" docName="draft-google-cfrg-libzk-03" submissionType="IETF" category="info" xml:lang="en" xmlns:xi="http://www.w3.org/2001/XInclude" indexInclude="true">

<front>
<title>Longfellow ZK</title><seriesInfo value="draft-google-cfrg-libzk-03" stream="IETF" status="informational" name="Internet-Draft"></seriesInfo>
<author initials="M." surname="Frigo" fullname="Matteo Frigo"><organization>Google</organization><address><postal><street></street>
</postal><email>matteof@google.com</email>
</address></author><author initials="a." surname="shelat" fullname="abhi shelat"><organization>Google</organization><address><postal><street></street>
</postal><email>shelat@google.com</email>
</address></author><date/>
<area>Internet</area>
<workgroup>Network Working Group</workgroup>

<abstract>
<t>This document defines an algorithm for generating and verifying a succinct non-interactive zero-knowledge argument that for a given input <tt>x</tt> and a circuit <tt>C</tt>, there exists a witness <tt>w</tt>, such that <tt>C(x,w)</tt> evaluates to 0. The technique here combines the MPC-in-the-head approach for constructing ZK arguments described in Ligero <xref target="ligero"></xref> with a verifiable computation protocol based on sumcheck for proving that <tt>C(x,w)=0</tt>.</t>
</abstract>

</front>

<middle>

<section anchor="introduction"><name>Introduction</name>
<t>A zero-knowledge (ZK) scheme allows a Prover who holds an arithmetic circuit <tt>C</tt> defined over a finite field <tt>F</tt> and two inputs <tt>(x,w)</tt> to convince a Verifier who holds only <tt>(C,x)</tt> that the Prover knows <tt>w</tt> such that <tt>C(x,w) = 0</tt> without revealing any extra information to the Verifier.</t>
<t>The concept of a zero-knowledge scheme was introduced by Goldwasser, Micali, and Rackoff <xref target="GMR"></xref>, and has since been rigourously explored and optimized in the academic literature.</t>
<t>There are several models and efficiency goals that different ZK schemes aim to achieve, such as reducing prover time, reducing verifier time, or reducing proof size.  Some ZK schemes also impose other requirements to achieve their efficienc goals.  This document considers the scenario in which there are no common reference strings, or trusted parameter setups that are available to the parties.  This immediately rules out several succinct ZK scheme from the literature.  In addition, this document also focuses on schemes that can be instantiated from a collision-resistant hash function and require no other complexity theoretic assumption.  Again, this rules out several schemes in the literature.   All of the ZK schemes from the literature that remain can be defined in the Interactive Oracle Proof (IOP) model, and this document specifies a family of them that enjoys both efficiency and simplicity.</t>

<section anchor="the-longfellow-system"><name>The Longfellow system</name>
<t>This document specifies the Longfellow ZK scheme described in the paper <xref target="longfellow"></xref>.  The scheme is constructed from two components: the first is the Ligero scheme, which provides a cryptographic commitment scheme that supports an efficient ZK argument system that enables proving linear and quadratic constraints on the committed witness, and the second is a public-coin interactive protocol (IP) for producing an argument that <tt>C(x,w)=0</tt> where <tt>C</tt> is such a circuit, <tt>x</tt> is a public input, and <tt>w</tt> is a private witness. The overall scheme works by having the Prover commit to the witness <tt>w</tt> as well as a <tt>pad</tt> used to commit the transcript of the IP, then to run the IP with the verifier in a way that produces a commitment to the transcript of the IP, and finally, by running the Ligero proof system to prove that the transcript in the commitment induces the IP verifier to accept.</t>
<t>A companion document specifies how the circuit <tt>C</tt> is specified.</t>
</section>
</section>

<section anchor="basic-operations-and-notation"><name>Basic Operations and Notation</name>
<t>The key words &quot;<bcp14>MUST</bcp14>&quot;, &quot;<bcp14>MUST NOT</bcp14>&quot;, &quot;<bcp14>REQUIRED</bcp14>&quot;, &quot;<bcp14>SHALL</bcp14>&quot;, &quot;<bcp14>SHALL NOT</bcp14>&quot;, &quot;<bcp14>SHOULD</bcp14>&quot;, &quot;<bcp14>SHOULD NOT</bcp14>&quot;, &quot;<bcp14>RECOMMENDED</bcp14>&quot;, &quot;<bcp14>MAY</bcp14>&quot;, and &quot;<bcp14>OPTIONAL</bcp14>&quot; in this document are to be interpreted as described in RFC 6919 <xref target="RFC6919"></xref>.</t>
<t>Except if said otherwise, random choices in this specification refer to drawing with uniform distribution from a given set (i.e., &quot;random&quot; is short for &quot;uniformly random&quot;).  Random choices can be replaced with fresh outputs from a cryptographically strong pseudorandom generator, according to the requirements in <xref target="RFC4086"></xref>, or pseudorandom function.</t>

<section anchor="array-primitives"><name>Array primitives</name>
<t>The notation <tt>A[0..N]</tt> refers to the array of size <tt>N</tt> that contains <tt>A[0],A[1],...,A[N-1]</tt>, i.e., the right-boundary in the notation <tt>X..Y</tt> is an exclusive index bound.
The following functions are used throughout the document:</t>

<ul spacing="compact">
<li>copy(n, Dst, Src): copies n elements from Src to Dst with different strides</li>
<li>axpy(n, Y, A, X): sets Y[i] += A*X[i] for 0 &lt;= i &lt; n.</li>
<li>sum(n, A): computes the sum of the first n elements in array A</li>
<li>dot(n, A, Y): computes the dot product of length n between arrays A and Y.</li>
<li>add(n, A, Y): returns the array <tt>[A[0]+Y[0], A[1]+Y[1], ..., A[n-1]+Y[n-1]]</tt>.</li>
<li>prod(n, A, Y): returns the array <tt>[A[0]*Y[0], A[1]*Y[1], ..., A[n-1]*Y[n-1]]</tt>.</li>
<li>equal(n, A, Y): true if <tt>A[i]==Y[i]</tt> for 0 &lt;= i &lt; n and false otherwise.</li>
<li>gather(n, A, I): returns the array <tt>[A[I[0]], A[I[1]], ..., A[I[n-1]]</tt>.</li>
<li><tt>A[n][m] = [0]</tt>: initializes the 2-dimensional n x m array A to all zeroes.</li>
<li><tt>A[0..NREQ] = X</tt> : array assignment, this operation copies the first NREQ elements of X into the corresponding indicies of the A array.</li>
</ul>
</section>

<section anchor="polynomial-operations"><name>Polynomial operations</name>
<t>This section describes operations on and associated with polynomials
that are used in the main protocol.</t>

<section anchor="extend-method-in-field-f-p"><name>Extend method in Field F_p</name>
<t>The <tt>extend(f, n, m)</tt> method interprets the array <tt>f[0..n]</tt> as the evaluations of a polynomial <tt>P</tt> of degree less than <tt>n</tt> at the points <tt>0,...,n-1</tt>, and returns the evaluations of the same <tt>P</tt> at the points <tt>0,...,m-1</tt>.  For sufficiently large fields <tt>|F_p| = p &gt;= m</tt>, polynomial <tt>P</tt> is uniquely determined by the input, and thus <tt>extend</tt> is well defined.</t>
<t>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 <tt>extend</tt> operation, and in some cases, either the Number Theoretic Transform or Nussbaumer's algorithm can be used to efficiently compute a convolution.</t>
</section>

<section anchor="gf2k"><name>Extend method in Field GF 2<sup>k</sup></name>
<t>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 <xref target="additivefft"></xref>.
Lin et al. define a novel polynomial basis for polynomials as an alternative to the usual monomial
basis x<sup>i</sup>, and give an algorithm for evaluating a degree-(d-1) polynomial at all d points in a subspace, for d=2<sup>ell</sup>, and for polynomials expressed in the novel basis.</t>
<t>Specifically, this document implements GF(2<sup>128</sup>) as GF{2}[x] / (Q(x)) where</t>

<artwork><![CDATA[    Q(x) = x^{128} + x^{7} + x^{2} + x + 1
]]></artwork>
<t>With this choice of Q(x), <tt>x</tt> is a generator of the multiplicative group of the field.
Next, choose GF(2<sup>16</sup>) as the subfield of GF(2<sup>128</sup>) with <tt>g=x^{(2^{128}-1) / (2^{16}-1)}</tt> as its generator, and <tt>beta_i=g^i^</tt> for 0 &lt;= i &lt; 16 as the basis of the subfield.  For relevant problem sizes, this allows encoding elements in a commitment scheme with 16-bits instead of 128.</t>
<t>Writing <tt>j_i</tt> for the <tt>i</tt>-th bit of the binary representation of <tt>j</tt>, that is,</t>

<artwork><![CDATA[    j = sum_{0 <= i < k} j_i 2^i     j_i \in {0,1}
]]></artwork>
<t>inject integer <tt>j</tt> into a field element <tt>inj(j)</tt> by interpreting the bits of <tt>j</tt>  as coordinates in terms of the basis:</t>

<artwork><![CDATA[    inj(j) = sum_{0 <= i < k} j_i beta_i
]]></artwork>
<t>In this setting, define the extend operator to interpret the array <tt>f[0..n]</tt> to consist of the evaluations of a polynomial <tt>p(x)</tt> of degree at most <tt>n-1</tt> at the <tt>n</tt> points <tt>x \in { inj(i) : 0 &lt;= i &lt; n }</tt> and to return the set <tt>{ p(inj(i)) : 0 &lt;= i &lt; m}</tt> which consist of the evaluations of the same polynomial <tt>p(x)</tt> at the injected points <tt>0,...,m-1</tt>.</t>
<t>This convention allows this operation to be completed efficiently using various forms of the additive FFT as described in <xref target="longfellow"></xref> <xref target="additivefft"></xref>.</t>
</section>
</section>
</section>

<section anchor="fiat-shamir"><name>Fiat-Shamir primitives</name>
<t>A ZK protocol may in general instruct the Prover and Verifier to engage in multiple rounds of communication. However, it is often more convenient to deploy a non-interactive or single-message protocol that only requires a single message from Prover to Verifier. It is possible to apply the Fiat-Shamir heuristic to transform an Interactive Oracle Protocol (IOP) into a single-message protocol. In this variant of the protocol, the Verifier does not explicitly send challenges to the Prover; instead, the Verifier computes the challenges by hashing the transcript of the conversation so far.</t>
<t>While the base Fiat-Shamir framework is described in <xref target="I-D.irtf-cfrg-fiat-shamir"></xref>, Interactive Oracle Proofs require more structured multi-round transcripts and multi-challenge extractions (such as combinations without replacement for column queries). The subsections below define a complete, self-contained specification of the <em>Hash-and-Expand</em> transcript instantiation and <em>Universal ZK TLV Codec</em> aligning with the IOP extensions proposed for the CFRG Fiat-Shamir draft.</t>
<t>The Fiat-Shamir transform is a method for generating a verifier's public-coin challenges by processing the concatenation of all preceding prover messages. The transform is proven to be sound when applied to an interactive protocol that is round-by-round sound and when the oracle is implemented with a hash function satisfying correlation-intractability with respect to the relation's verification state function (see <xref target="rbr"></xref>).</t>
<t>While standard Sigma protocols operate in a single round with scalar challenges, Interactive Oracle Proofs (IOPs) such as Longfellow require structured, multi-round transcripts with typed messages (byte strings, field elements, arrays of field elements) and multi-challenge extractions (such as combinations without replacement for column queries and subfield elements).</t>

<section anchor="suite-hash-and-expand"><name>The Hash-and-Expand Duplex Sponge Instantiation</name>
<t>The transcript is modeled as a stateful object maintaining an internal string <tt>tr</tt> (or incrementally updated hash state) and a pseudorandom stream generator. In the <tt>SHA256-AES256CTR</tt> ciphersuite:</t>

<ol spacing="compact">
<li>Absorb: Prover messages are appended to <tt>tr</tt> using the Universal ZK TLV Codec.</li>
<li>Squeeze: When a verifier challenge is requested, the transcript derives a 32-byte key <tt>\text{SEED} = H(\text{tr})</tt> using SHA-256. This seed initializes a Fiat-Shamir Pseudorandom Function (<tt>FsPrf</tt>) running AES-256 in counter mode.</li>
</ol>

<section anchor="the-fsprf-keystream-generator"><name>The FSPRF Keystream Generator</name>
<t>The <tt>FsPrf</tt> object generates an infinite sequence of pseudorandom bytes organized into 16-byte blocks. Block <tt>i</tt> (for <tt>i \ge 0</tt>) is computed as:</t>

<artwork><![CDATA[  Block[i] = AES256(SEED, ID(i))
]]></artwork>
<t>where <tt>SEED</tt> is the 32-byte hash digest <tt>H(\text{tr})</tt>, and <tt>ID(i)</tt> is the 16-byte little-endian encoding of integer <tt>i</tt>.</t>

<sourcecode type="rust"><![CDATA[use aes::{
    Aes256,
    cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray},
};
use sha2::{Digest, Sha256};
use crate::algebra::{Field, Rng};

#[derive(Clone)]
pub struct FsPrf {
    _key: [u8; 32],
    cipher: Aes256,
    block_counter: u64,
    read_pointer: usize,
    output_buffer: [u8; 16],
}

impl FsPrf {
    pub fn new(key: [u8; 32]) -> Self {
        let key_arr = GenericArray::from(key);
        let cipher = Aes256::new(&key_arr);
        Self {
            _key: key,
            cipher,
            block_counter: 0,
            read_pointer: 16, // Force refill on first read
            output_buffer: [0u8; 16],
        }
    }

    fn refill(&mut self) {
        assert!(self.block_counter < 0x10000000000);
        let mut inp = [0u8; 16];
        for i in 0..8 {
            inp[i] = ((self.block_counter >> (8 * i)) & 0xff) as u8;
        }
        let mut block = GenericArray::from(inp);
        self.cipher.encrypt_block(&mut block);
        self.output_buffer.copy_from_slice(&block);
        self.block_counter += 1;
        self.read_pointer = 0;
    }

    pub fn get_bytes(&mut self, len: usize) -> Vec<u8> {
        let mut buf = Vec::with_capacity(len);
        for _ in 0..len {
            if self.read_pointer >= 16 {
                self.refill();
            }
            buf.push(self.output_buffer[self.read_pointer]);
            self.read_pointer += 1;
        }
        buf
    }
}
]]></sourcecode>
</section>
</section>

<section anchor="encoding-tlv"><name>Universal ZK TLV Codec</name>
<t>To ensure prefix-free and unambiguous parsing of multi-round interactive oracle proofs, all prover messages absorbed by the transcript are framed with explicit type tags:</t>

<ul>
<li><t>Byte Array (<tt>TAG_BSTR = 0x00</tt>): Serialized as the 1-byte tag <tt>0x00</tt>, followed by an 8-byte little-endian length prefix, followed by the raw bytes:</t>

<artwork><![CDATA[0x00 || len_le_u64 || bytes
]]></artwork>
</li>
<li><t>Field Element (<tt>TAG_FIELD_ELEM = 0x01</tt>): Serialized as the 1-byte tag <tt>0x01</tt>, followed directly by the canonical byte serialization of the field element (length is fixed by the field definition):</t>

<artwork><![CDATA[0x01 || canonical_field_bytes
]]></artwork>
</li>
<li><t>Field Element Array (<tt>TAG_ARRAY = 0x02</tt>): Serialized as the 1-byte tag <tt>0x02</tt>, followed by an 8-byte little-endian count prefix, followed by the concatenated canonical byte serializations of all field elements:</t>

<artwork><![CDATA[0x02 || count_le_u64 || elt_0_bytes || ... || elt_{n-1}_bytes
]]></artwork>
</li>
</ul>

<sourcecode type="rust"><![CDATA[const TAG_BSTR: u8 = 0x00;
const TAG_FIELD_ELEM: u8 = 0x01;
const TAG_ARRAY: u8 = 0x02;

#[derive(Clone)]
pub struct Transcript {
    hash_accumulator: Sha256,
    pseudorandom_generator: Option<FsPrf>,
}

impl Transcript {
    pub fn new(init: &[u8]) -> Self {
        let mut t = Self {
            hash_accumulator: Sha256::new(),
            pseudorandom_generator: None,
        };
        t.write_bytes(init);
        t
    }

    pub fn get_hash(&self) -> [u8; 32] {
        let h = self.hash_accumulator.clone();
        let digest = h.finalize();
        let mut res = [0u8; 32];
        res.copy_from_slice(&digest);
        res
    }

    pub fn write_untyped(&mut self, data: &[u8]) {
        self.pseudorandom_generator = None;
        self.hash_accumulator.update(data);
    }

    pub fn tag(&mut self, tg: u8) {
        self.write_untyped(&[tg]);
    }

    pub fn write_length(&mut self, x: usize) {
        let x_u64 = x as u64;
        let mut len_bytes = [0u8; 8];
        for i in 0..8 {
            len_bytes[i] = ((x_u64 >> (8 * i)) & 0xff) as u8;
        }
        self.write_untyped(&len_bytes);
    }

    pub fn write_bytes(&mut self, data: &[u8]) {
        self.tag(TAG_BSTR);
        self.write_length(data.len());
        self.write_untyped(data);
    }

    pub fn write0(&mut self, n: usize) {
        self.tag(TAG_BSTR);
        self.write_length(n);
        let data = vec![0x00; n];
        self.write_untyped(&data);
    }

    pub fn write_untyped_elt<F: Field>(&mut self, e: F) {
        let b = e.to_bytes();
        self.write_untyped(&b);
    }

    pub fn write_elt_field<F: Field>(&mut self, e: F) {
        self.tag(TAG_FIELD_ELEM);
        self.write_untyped_elt(e);
    }

    pub fn write_elt_field_slice<F: Field>(&mut self, e: &[F]) {
        self.tag(TAG_ARRAY);
        self.write_length(e.len());
        for elt in e {
            self.write_untyped_elt(*elt);
        }
    }

    pub fn get_random_bytes(&mut self, len: usize) -> Vec<u8> {
        if self.pseudorandom_generator.is_none() {
            let key = self.get_hash();
            self.pseudorandom_generator = Some(FsPrf::new(key));
        }
        self.pseudorandom_generator.as_mut().unwrap().get_bytes(len)
    }
}
]]></sourcecode>
</section>

<section anchor="correlation-intractability"><name>Correlation-Intractability and Computational Depth</name>
<t>The security of the Fiat-Shamir transformation relies on correlation intractability. When proving arbitrary circuit satisfiability or recursive statements, a malicious prover might attempt self-referential attacks if the circuit's complexity exceeds the depth required to compute the random oracle.</t>
<t>To prevent self-referential attacks (see <xref target="krs"></xref>), the first prover message in Longfellow enforces that the oracle's computational depth exceeds the verification logic of circuit <tt>C</tt>:</t>

<ol spacing="compact">
<li>Absorb the initial prover commitment (<tt>session_id</tt> and initial matrix commitment root).</li>
<li>Absorb the statement encoding: circuit identifier <tt>id</tt>, public inputs, and outputs.</li>
<li>Absorb <tt>0^{|C|}</tt> via <tt>write0(|C|)</tt> (a byte array of <tt>|C|</tt> zero bytes, where <tt>|C|</tt> is the gate count of the circuit).</li>
</ol>
</section>

<section anchor="decoding"><name>Challenge Extraction Methods</name>
<t>In the CFRG Fiat-Shamir framework (<xref target="I-D.irtf-cfrg-fiat-shamir"></xref>), challenge extraction is formalized as the decoding component of a <em>codec</em>. While the codec's <tt>prover_message</tt> procedure serializes and absorbs prover messages into the sponge state, its <tt>verifier_challenge</tt> procedure squeezes uniformly distributed pseudorandom bytes and decodes them into the verifier's target challenge domain.</t>
<t>For Interactive Oracle Proofs, the codec defines four decoding procedures that translate the raw byte stream into structured verifier challenges:</t>

<section anchor="decoding-bounded-uint"><name>Bounded Natural Numbers (<tt>generate_nat</tt> / <tt>nat</tt>)</name>
<t>Samples a uniformly distributed integer in <tt>[0, m - 1]</tt> via minimal bitmask rejection sampling:</t>

<sourcecode type="rust"><![CDATA[impl Transcript {
    pub fn nat(&mut self, n: usize) -> usize {
        assert!(n > 0, "nat(0) is undefined");
        let mut nn = n;
        let mut l = 0;
        while nn != 0 {
            nn >>= 8;
            l += 1;
        }
        let mut msk = 0;
        while (n & msk) != n {
            msk = (msk << 1) | 1;
        }

        loop {
            let b = self.bytes(l);
            let mut r = 0usize;
            for i in (0..l).rev() {
                r = (r << 8) | (b[i] as usize);
            }
            r &= msk;
            if r < n {
                return r;
            }
        }
    }
}
]]></sourcecode>
</section>

<section anchor="decoding-sample-distinct"><name>Combinations Without Replacement (<tt>generate_nats_wo_replacement</tt> / <tt>choose</tt>)</name>
<t>Samples <tt>k</tt> distinct natural numbers uniformly from <tt>[0, n - 1]</tt> without replacement (used for Ligero column query indices) using an in-place Fisher-Yates shuffle:</t>

<sourcecode type="rust"><![CDATA[impl Transcript {
    pub fn choose(&mut self, n: usize, k: usize) -> Vec<usize> {
        if n == 0 || k == 0 {
            return Vec::new();
        }
        assert!(n >= k);
        let mut a: Vec<usize> = (0..n).collect();
        let mut res = vec![0; k];
        for i in 0..k {
            let val = self.nat(n - i);
            let j = i + val;
            a.swap(i, j);
            res[i] = a[i];
        }
        res
    }
}
]]></sourcecode>
</section>

<section anchor="decoding-field"><name>Field Elements and Vectors (<tt>generate_field</tt> / <tt>generate_challenge</tt>)</name>

<ul spacing="compact">
<li>Prime Fields (e.g. NIST P-256 scalar field <tt>\mathbb{F}_p</tt>): Samples 32 bytes from <tt>bytes(32)</tt> and rejects if the integer value <tt>\ge p</tt>.</li>
<li>Binary Extension Fields (e.g. <tt>\text{GF}(2^{128})</tt>): Samples 16 bytes directly from <tt>bytes(16)</tt> and interprets them as the polynomial coefficients in <tt>\text{GF}(2)[X]/(X^{128} + X^7 + X^2 + X + 1)</tt>. Subfield elements in <tt>\text{GF}(2^{16})</tt> sample 2 bytes from <tt>bytes(2)</tt> and map to the subfield basis.</li>
</ul>

<sourcecode type="rust"><![CDATA[impl Transcript {
    pub fn get_elt_field<F: Field + 'static>(&mut self) -> F {
        F::sample(self)
    }

    pub fn generate_challenge<F: Field + 'static>(&mut self, n: usize) -> Vec<F> {
        (0..n).map(|_| self.get_elt_field::<F>()).collect()
    }
}

impl Rng for Transcript {
    fn bytes(&mut self, len: usize) -> Vec<u8> {
        self.get_random_bytes(len)
    }
}
]]></sourcecode>
</section>
</section>
</section>

<section anchor="ligero-zk-proof"><name>Ligero ZK Proof</name>
<t>This section specifies the construction and verification method for a Ligero commitment and zero-knowledge argument. The Ligero system as described by Ames, Hazay, Ishai, and Venkitasubramaniam <xref target="ligero"></xref>, consists of a commitment scheme, and a method for proving linear and quadratic constraints on the committed values in zero-knowledge. The latter interface is sufficient to prove arbitrary circuits, but in the Longfellow scheme, it suffices to describe how to use such constraints to directly verify an IP transcript.</t>

<section anchor="merkle-trees"><name>Merkle trees</name>
<t>This section describes how to construct a Merkle tree from a sequence of <tt>n</tt> strings, and how to verify that a given string <tt>x</tt> was placed at leaf <tt>i</tt> in a Merkle tree. These methods do not assume that <tt>n</tt> is a power of two. This construction is parameterized by the cryptographic hash function SHA-256 <xref target="RFC6234"></xref>.  In this application, a leaf in a tree is a message digest instead of an arbitrary string; for example, when the hash function is SHA-256, then the leaf is a 32-byte string.</t>
<t>A tree that contains <tt>n</tt> leaves is represented by an array of <tt>2 * n</tt> message digests in which the input digests are written at indicies <tt>n..2*n</tt>.  The tree is constructed by iteratively hashing the concatenation of the values at indicies <tt>2*j</tt> and <tt>2*j+1</tt>, starting at <tt>j=n-1</tt>, and continuing until <tt>j=1</tt>. The root is at index 1. In this specification, the prover and verifier will already know the value of <tt>n</tt> when they produce or verify a Merkle tree.</t>

<section anchor="blinding-the-leaves-with-nonces"><name>Blinding the leaves with nonces</name>
<t>As per folklore, a Merkle commitment does not hash a leaf's data directly, but instead includes a fresh random nonce of 32 bytes for every leaf. The leaf digest is the hash of that nonce followed by the leaf's data.</t>

<sourcecode type="rust"><![CDATA[pub fn commit_merkle_heap<R, F>(
    num_leaves: usize,
    update_leaf_hash_fn: F,
    rng: &mut R,
) -> (MerkleHeap, Vec<Vec<u8>>)
where
    R: Rng,
    F: Fn(usize) -> Vec<u8>,
{
    let mut nonces = Vec::with_capacity(num_leaves);
    for _ in 0..num_leaves {
        nonces.push(rng.bytes(32));
    }

    let mut leaves_digests = Vec::with_capacity(num_leaves);
    for i in 0..num_leaves {
        let mut data = Vec::new();
        data.extend_from_slice(&nonces[i]);
        data.extend_from_slice(&update_leaf_hash_fn(i));
        leaves_digests.push(sha256_bytes(&data));
    }

    let heap = MerkleHeap::new(&leaves_digests);
    (heap, nonces)
}
]]></sourcecode>
<t>The nonces are retained by the prover.  One nonce is generated per
leaf, but only the <tt>NREQ</tt> nonces belonging to the opened columns are
revealed, and those are sent as part of the proof; the nonces of the
unopened columns are never disclosed.  A verifier recomputes the
digest of an opened leaf in the same order, nonce first, as shown in
the <tt>verify_merkle</tt> function of the Ligero verification procedure
below.  Because the nonces are secret and uniformly random, the
unopened leaves are computationally hidden.</t>
<t>The functions in the remainder of this section operate on the leaf
digests produced above, and are therefore stated without reference to
the nonces.</t>
</section>

<section anchor="constructing-a-merkle-tree-from-n-digests"><name>Constructing a Merkle tree from <tt>n</tt> digests</name>

<sourcecode type="rust"><![CDATA[pub fn sha256_bytes(data: &[u8]) -> Vec<u8> {
    let mut hasher = Sha256::new();
    hasher.update(data);
    hasher.finalize().to_vec()
}

#[derive(Clone, Debug)]
pub struct MerkleHeap {
    pub num_leaves: usize,
    pub layers: Vec<Vec<u8>>,
    pub root: Vec<u8>,
}

impl MerkleHeap {
    pub fn new(leaves: &[Vec<u8>]) -> Self {
        let n = leaves.len();
        let mut layers = vec![Vec::new(); 2 * n];
        layers[n..(n + n)].clone_from_slice(&leaves[..n]);
        for i in (1..n).rev() {
            let mut data = Vec::new();
            data.extend_from_slice(&layers[2 * i]);
            data.extend_from_slice(&layers[2 * i + 1]);
            layers[i] = sha256_bytes(&data);
        }
        let root = layers[1].clone();
        Self {
            num_leaves: n,
            layers,
            root,
        }
    }
}
]]></sourcecode>
</section>

<section anchor="constructing-a-proof-of-inclusion"><name>Constructing a proof of inclusion</name>
<t>This section describes how to construct a Merkle proof that <tt>k</tt> input digests at indicies <tt>i[0],...,i[k-1]</tt> belong to the tree.  The simplest way to generate such a proof is to produce independent proofs for each of the <tt>k</tt> leaves. However, this turns out to be wasteful in that internal nodes may be included multiple times along different paths, and some nodes may not need to be included at all because they are implied by nodes that have already been included.</t>
<t>To address these inefficiencies, this section explains how to produce a batch proof of inclusion for <tt>k</tt> leaves. The main idea is to start from the requested set of leaves and build all of the implied internal nodes given the leaves. For example, if sibling leaves are included, then their parent is implied, and the parent need not be included in the compressed proof.  Then it suffices to revisit the same tree and include the necessary siblings along all of the Merkle paths.  It is assumed that the verifier already has the leaf digests that are at the indicies, and thus the proof only contains the necessary internal nodes of the Merkle tree that are used to verify the claim.</t>
<t>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 <tt>requested_leaves</tt> array.</t>

<sourcecode type="rust"><![CDATA[pub fn open_merkle_heap(
    mh: &MerkleHeap,
    leaf_indices: &[usize],
) -> Result<Vec<Vec<u8>>, &'static str> {
    let n = mh.num_leaves;
    let mut seen = vec![false; n];
    let mut is_on_path = vec![false; 2 * n];
    for &idx in leaf_indices {
        if idx >= n {
            return Err("Leaf index out of bounds in Merkle opening");
        }
        if seen[idx] {
            return Err("Duplicate leaf index in Merkle opening");
        }
        seen[idx] = true;
        is_on_path[n + idx] = true;
    }
    for i in (1..n).rev() {
        is_on_path[i] = is_on_path[2 * i] || is_on_path[2 * i + 1];
    }

    let mut path = Vec::new();
    for i in (1..n).rev() {
        if is_on_path[i] {
            if is_on_path[2 * i] && !is_on_path[2 * i + 1] {
                path.push(mh.layers[2 * i + 1].clone());
            } else if !is_on_path[2 * i] && is_on_path[2 * i + 1] {
                path.push(mh.layers[2 * i].clone());
            }
        }
    }
    Ok(path)
}
]]></sourcecode>
</section>

<section anchor="verifying-a-proof-of-inclusion"><name>Verifying a proof of inclusion</name>
<t>This section describes how to verify a compressed Merkle proof. The claim to verify is that &quot;the commitment <tt>root</tt> defines an <tt>n</tt>-leaf Merkle tree that contains <tt>k</tt> digests <tt>s[0], ..., s[k-1]</tt> at corresponding indices <tt>i[0], ..., i[k-1]</tt>.&quot;  The strategy of this verification procedure is to deduce which nodes are needed along the <tt>k</tt> 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 <tt>root</tt> digest. As an optimization, the <tt>defined[]</tt> 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 <tt>k=0</tt> digests is defined to fail. It is assumed that the <tt>indices[]</tt> array does not contain duplicates.</t>

<sourcecode type="rust"><![CDATA[pub fn verify_merkle_proof<F>(
    n: usize,
    root: &[u8],
    leaf_indices: &[usize],
    path: &[Vec<u8>],
    mut leaf_hash_fn: F,
) -> Result<(), &'static str>
where
    F: FnMut(usize) -> Vec<u8>,
{
    let mut seen = vec![false; n];
    let mut is_on_path = vec![false; 2 * n];
    for &idx in leaf_indices {
        if idx >= n {
            return Err("Leaf index out of bounds in Merkle proof verification");
        }
        if seen[idx] {
            return Err("Duplicate leaf index in Merkle proof verification");
        }
        seen[idx] = true;
        is_on_path[n + idx] = true;
    }
    for i in (1..n).rev() {
        is_on_path[i] = is_on_path[2 * i] || is_on_path[2 * i + 1];
    }

    let mut layers: Vec<Option<Vec<u8>>> = vec![None; 2 * n];
    for &idx in leaf_indices {
        layers[n + idx] = Some(leaf_hash_fn(idx));
    }

    let mut path_idx = 0;
    for i in (1..n).rev() {
        if is_on_path[i] {
            let left_val = if is_on_path[2 * i] {
                layers[2 * i].clone()
            } else {
                let val = path.get(path_idx).cloned();
                path_idx += 1;
                val
            };
            let right_val = if is_on_path[2 * i + 1] {
                layers[2 * i + 1].clone()
            } else {
                let val = path.get(path_idx).cloned();
                path_idx += 1;
                val
            };

            if let (Some(left_val), Some(right_val)) = (left_val, right_val) {
                let mut data = Vec::with_capacity(left_val.len() + right_val.len());
                data.extend_from_slice(&left_val);
                data.extend_from_slice(&right_val);
                layers[i] = Some(sha256_bytes(&data));
            } else {
                return Err("Missing path value in Merkle proof verification");
            }
        }
    }

    if path_idx != path.len() {
        return Err("Not all Merkle path elements were consumed");
    }

    if let Some(computed_root) = &layers[1] {
        if computed_root == root {
            Ok(())
        } else {
            Err("Merkle root mismatch")
        }
    } else {
        Err("Merkle root was not computed")
    }
}
]]></sourcecode>
</section>
</section>

<section anchor="ligero-parameters"><name>Common parameters</name>
<t>The Prover and Verifier in Ligero must agree on the following parameters. These parameters can be agreed upon out of band.</t>

<ul spacing="compact">
<li><tt>F</tt>: The finite field over which the commit is produced.</li>
<li><tt>NREQ</tt>: The number of columns of the commitment matrix that the Verifier requests to be revealed by the Prover.</li>
<li><tt>rate</tt>: The inverse rate of the error correcting code. This parameter, along with <tt>NREQ</tt> and Field size, determines the soundness of the scheme.</li>
<li><tt>BLOCK</tt>: the size of each row, in terms of number of field elements</li>
<li><tt>DBLOCK</tt>: 2 * <tt>BLOCK</tt> - 1</li>
<li><tt>WR</tt>: the number of witness values included in each row.</li>
<li><tt>IW</tt>: Row index at which the witness values start, usually IW = 3.</li>
<li><tt>IQ</tt>: Row index at which the quadratic constraints begin, it is the first row after all of the witnesses have been encoded.</li>
<li><tt>NL</tt>: Number of linear constraints.</li>
<li><tt>NQ</tt>: Number of quadratic constraints.</li>
<li><tt>NWROW</tt>: Number of rows used to encode witnesses.</li>
<li><tt>NQT</tt>: Number of row triples needed to encode the quadratic constraints.</li>
<li><tt>NQW</tt>: <tt>NWROW + 3 * NQT</tt>, rows needed to encode witnesses and quadratic constraints. Each triple of quadratic constraints occupies three rows (<tt>Qx</tt>, <tt>Qy</tt>, <tt>Qz</tt>).</li>
<li><tt>NROW</tt>: Total number of rows in the tableau matrix, <tt>3 + NQW</tt> (the three additional rows are the random rows <tt>ILDT</tt>, <tt>IDOT</tt>, and <tt>IQD</tt>).</li>
<li><tt>NCOL</tt>: Total number of columns in the tableau matrix.</li>
</ul>
<t>A row of the tableau consists of</t>
<t>|     NREQ     |        WR          | ... DBLOCK | ... NCOL  |
|  random pad  |   witness values   | polynomial evaluations |</t>

<section anchor="constraints-on-parameters"><name>Constraints on parameters</name>

<ul spacing="compact">
<li><tt>NCOL &lt; |F|</tt> The block size must be smaller than the field size.</li>
<li><tt>NCOL &gt;= DBLOCK + NREQ</tt></li>
<li><tt>BLOCK &gt; NREQ</tt> The block size must be larger than the number of columns requested.</li>
<li><tt>BLOCK = NREQ + WR</tt></li>
<li><tt>WR &gt;= NREQ</tt></li>
<li><tt>BLOCK = (NCOL + 1) / (2 + rate)</tt></li>
</ul>
</section>
</section>

<section anchor="ligero-commitment"><name>Ligero commitment</name>
<t>The first step of the proof procedure requires the Prover to commit to a witness vector <tt>W</tt> of length <tt>NW</tt>.  The length <tt>NW</tt> need not be a multiple of <tt>WR</tt>; the witness occupies <tt>NWROW = ceil(NW / WR)</tt> rows, and any unused entries of the last such row are set to zero by the commitment procedure itself. The commitment is the root of a Merkle tree. The leaves of the Merkle tree are a sequence of columns of the tableau matrix <tt>T[][]</tt>.</t>
<t>This tableau matrix is constructed row-by-row by applying the extend procedure to arrays that are formed from random field elements and elements copied from the witness vector. Matrix T[][] has size NROW x NCOL and has the following structure:</t>

<artwork><![CDATA[row ILDT = 0                         : RANDOM row for low-degree test
row IDOT = 1                         : RANDOM row for linear test
row IQD  = 2                         : RANDOM row for quadratic test
row i for IW = IQD + 1 <= i < IQ     : witness rows
row i for IQ <= i < NROW             : quadratic rows
]]></artwork>

<ol type="%d)">
<li><t>The first ILDT row is defined as</t>

<artwork><![CDATA[extend(RANDOM[BLOCK], BLOCK, NCOL)
]]></artwork>
<t>by selecting BLOCK random field elements and applying extend.</t>
</li>
<li><t>The second IDOT row is defined as</t>

<artwork><![CDATA[Z = RANDOM[DBLOCK] such that
    sum_{NREQ <= i < NREQ + WR} Z_i = 0
extend(Z, DBLOCK, NCOL)
]]></artwork>
<t>by first selecting DBLOCK random field elements such that the subarray
[NREQ .. NREQ + WR] sums to 0 and then applying extend.
The first step can be performed by selecting DBLOCK-1 random
field elements, and then setting <tt>Z[NREQ]</tt> to be the additive inverse of the
sum of the elements with <tt>NREQ &lt; i &lt; NREQ + WR</tt>.</t>
</li>
<li><t>The third IQD row is defined as
    ZQ = RANDOM[DBLOCK]
    ZQ[NREQ .. NREQ + WR] = 0
    extend(ZQ, DBLOCK, NCOL)
by first selecting DBLOCK random field elements, and then setting the
portion coresponding to the witness values to 0 and then applying extend.</t>
</li>
<li><t>The next rows from IW=3,...,IQ are <em>padded witness</em> rows that contain
random elements and portions of the witness vector.
Specifically, row i is formed by applying <tt>extend</tt> to an array that
consists of <tt>NREQ</tt> random elements and then <tt>WR</tt> elements from the vector <tt>W</tt>:</t>

<artwork><![CDATA[extend([RANDOM[NREQ], W[(i-2) * WR .. (i-1) * WR]], BLOCK, NCOL)
]]></artwork>
<t>When the finite field contains a subfield, and if all of the witness elements in a given row are elements from this subfield, then the randomness for that row can also be chosen from the subfield.
Consequently, the <tt>extend</tt> method for that row produces polynomial evaluations that are elements of the subfield. When these elements are serialized, they will require less space.
The simplest way to apply this optimization is for the commiting process to maintain an index <tt>SF</tt> such that witnesses at indices <tt>0..SF</tt> belong to the subfield, and the rest do not. This value <tt>SF</tt> can be conveyed to the verifier as part of the proof, or part of the circuit.</t>
</li>
<li><t>The final portion of the witness matrix consists of <em>padded quadratic</em> rows
that consists of NREQ random elements and WR quadratic constraint elements:</t>

<artwork><![CDATA[extend([RANDOM[NREQ], QX[WR]], BLOCK, NCOL)
extend([RANDOM[NREQ], QY[WR]], BLOCK, NCOL)
extend([RANDOM[NREQ], QZ[WR]], BLOCK, NCOL)
]]></artwork>
<t>The specific elements in the QX, QY, QZ array are determined by the quadratic
constraints on the witness values that are verified by the proof.</t>
</li>
</ol>
<t>The second step of the procedure is to compute a Merkle tree on columns
of the tableau matrix.  Only the columns <tt>DBLOCK..NCOL</tt> are committed,
so the tree has <tt>NCOL - DBLOCK</tt> leaves.  Specifically, the <tt>i</tt>-th leaf
of the tree is the entire column <tt>DBLOCK + i</tt> of the tableau <tt>T</tt>, that
is, the <tt>NROW</tt> elements <tt>T[0][DBLOCK + i], ..., T[NROW - 1][DBLOCK + i]</tt>
serialized in row order, prefixed by that column's nonce and hashed as
described in the <eref target="#merkle-trees">Merkle trees Section</eref>.</t>
<t>Input:</t>

<ul spacing="compact">
<li>The witness vector <tt>W</tt>.</li>
<li>Array of quadratic constraints <tt>lqc[]</tt>, which consists of triples <tt>(x,y,z)</tt> that represent the constraint that <tt>W[x] * W[y] = W[z]</tt>.</li>
</ul>
<t>Output:</t>

<ul spacing="compact">
<li>A digest; root of a Merkle tree formed from columns of the tableau.</li>
</ul>

<sourcecode type="rust"><![CDATA[pub struct LigeroCommitResult<F> {
    pub geometry: LigeroGeometry,
    pub tableau: Vec<Vec<F>>,
    pub merkle: MerkleHeap,
    pub nonces: Vec<Vec<u8>>,
}

impl<F: Field + 'static> LigeroProver<F> {
    pub fn commit<R: Rng>(
        &self,
        witness: &[F],
        lqc: &[LqcTriple],
        rng: &mut R,
        subfield_boundary: usize,
    ) -> LigeroCommitResult<F> {
        let tableau = self.layout_tableau(witness, lqc, subfield_boundary, rng);

        let geom = self.geometry;
        let dblock = geom.dblock_len;
        let block_enc = geom.encoded_len;

        let num_committed_cols = block_enc - dblock;
        let update_leaf_hash = |j: usize| {
            let col_idx = j + dblock;
            let mut data = Vec::new();
            for row in 0..tableau.len() {
                data.extend_from_slice(&tableau[row][col_idx].to_bytes());
            }
            data
        };

        let (heap, nonces) = commit_merkle_heap(num_committed_cols, update_leaf_hash, rng);

        LigeroCommitResult {
            geometry: geom,
            tableau,
            merkle: heap,
            nonces,
        }
    }

    fn layout_tableau<R: Rng>(
        &self,
        witness: &[F],
        lqc: &[LqcTriple],
        subfield_boundary: usize,
        rng: &mut R,
    ) -> Vec<Vec<F>> {
        let mut tableau = Vec::new();
        tableau.push(self.layout_ildt_row(rng));
        tableau.push(self.layout_idot_row(rng));
        tableau.push(self.layout_iquad_row(rng));
        tableau.extend(self.layout_witness_rows(witness, subfield_boundary, rng));
        tableau.extend(self.layout_quadratic_constraint_rows(witness, lqc, rng));
        tableau
    }

    fn layout_ildt_row<R: Rng>(&self, rng: &mut R) -> Vec<F> {
        let row = (0..self.geometry.block_len)
            .map(|_| F::sample(rng))
            .collect::<Vec<F>>();
        self.rs_block.encode_row()(&row)
    }

    fn layout_idot_row<R: Rng>(&self, rng: &mut R) -> Vec<F> {
        let geom = self.geometry;
        let mut row = (0..geom.dblock_len)
            .map(|_| F::sample(rng))
            .collect::<Vec<F>>();
        let sum_w1 = dot1(&row[geom.num_queries..(geom.num_queries + geom.witnesses_per_row)]);
        row[geom.num_queries] -= sum_w1;
        self.rs_dblock.encode_row()(&row)
    }

    fn layout_iquad_row<R: Rng>(&self, rng: &mut R) -> Vec<F> {
        let geom = self.geometry;
        let mut row = (0..geom.dblock_len)
            .map(|_| F::sample(rng))
            .collect::<Vec<F>>();
        for j in 0..geom.witnesses_per_row {
            row[geom.num_queries + j] = F::zero();
        }
        self.rs_dblock.encode_row()(&row)
    }

    fn layout_witness_rows<R: Rng>(
        &self,
        witness: &[F],
        subfield_boundary: usize,
        rng: &mut R,
    ) -> Vec<Vec<F>> {
        let geom = self.geometry;
        let nw = witness.len();
        let mut witness_rows = Vec::new();

        for i in 0..geom.num_witness_rows {
            let subfield_only = (i + 1) * geom.witnesses_per_row <= subfield_boundary;
            let mut row_raw = vec![F::zero(); geom.block_len];
            for k in 0..geom.num_queries {
                row_raw[k] = if subfield_only {
                    self.subfield.sample(rng)
                } else {
                    F::sample(rng)
                };
            }
            let start = i * geom.witnesses_per_row;
            if start < nw {
                let max_col = std::cmp::min(geom.witnesses_per_row, nw - start);
                row_raw[geom.num_queries..(geom.num_queries + max_col)]
                    .copy_from_slice(&witness[start..(start + max_col)]);
            }
            witness_rows.push(self.rs_block.encode_row()(&row_raw));
        }

        witness_rows
    }

    fn layout_quadratic_constraint_rows<R: Rng>(
        &self,
        witness: &[F],
        lqc: &[LqcTriple],
        rng: &mut R,
    ) -> Vec<Vec<F>> {
        let geom = self.geometry;
        let nq = lqc.len();

        let mut tableau = Vec::new();
        let mut x_rows = Vec::new();
        let mut y_rows = Vec::new();
        let mut z_rows = Vec::new();
        for i in 0..geom.num_quad_rows {
            let mut row_x = self.sample_random_prefix_row(rng);
            let mut row_y = self.sample_random_prefix_row(rng);
            let mut row_z = self.sample_random_prefix_row(rng);
            let start = i * geom.witnesses_per_row;
            if start < nq {
                let max_j = std::cmp::min(geom.witnesses_per_row, nq - start);
                for j in 0..max_j {
                    let c = lqc[start + j];
                    row_x[geom.num_queries + j] = witness[c.x];
                    row_y[geom.num_queries + j] = witness[c.y];
                    row_z[geom.num_queries + j] = witness[c.z];
                }
            }
            x_rows.push(self.rs_block.encode_row()(&row_x));
            y_rows.push(self.rs_block.encode_row()(&row_y));
            z_rows.push(self.rs_block.encode_row()(&row_z));
        }

        tableau.extend(x_rows);
        tableau.extend(y_rows);
        tableau.extend(z_rows);
        tableau
    }
}
]]></sourcecode>
</section>

<section anchor="ligero-prove"><name>Ligero Prove</name>
<t>This section specifies how a Ligero proof for a given sequence of linear constraints and quadratic constraints on the committed witness vector <tt>W</tt> is constructed. The proof consists of a low-degree test on the tableau, a linearity test, and a quadratic constraint test.</t>

<section anchor="low-degree-test"><name>Low-degree test</name>
<t>In the low-degree test, the verifier sends a challenge vector consisting of <tt>NQW</tt> field elements, <tt>u_ldt[0..NQW]</tt>. This challenge is generated via the Fiat-Shamir transform. The prover computes the linear combination:</t>

<artwork><![CDATA[y_ldt = T[ILDT][0..BLOCK] + sum_{0 <= i < NQW} u_ldt[i] * T[IW + i][0..BLOCK]
]]></artwork>
<t>and returns the <tt>BLOCK</tt> elements of <tt>y_ldt</tt>. Notice that the random blinding row <tt>ILDT</tt> (row 0) is included with implicit coefficient 1, while rows 1 (<tt>IDOT</tt>) and 2 (<tt>IQD</tt>) are excluded because their polynomial degree is <tt>DBLOCK</tt> (<tt>2 \cdot \text{BLOCK} - 1</tt>) rather than <tt>BLOCK</tt>. The verifier applies the <tt>extend</tt> method to this response, and verifies consistency with the opened columns of the tableau requested at the challenge indices.</t>
</section>

<section anchor="linear-and-quadratic-constraints"><name>Linear and Quadratic constraints</name>
<t>The linear test is represented by a matrix <tt>A</tt>, and a vector <tt>b</tt>, and aims to verify that <tt>A * W + b = 0</tt>. The constraint matrix <tt>A</tt> is given as input in a sparse form: it is an array of <tt>LigeroTerm</tt> triples <tt>(c,j,k)</tt> in which <tt>c</tt> indicates the constraint index, <tt>j</tt> represents the witness index, and <tt>k</tt> represents the linear coefficient. For example, if the first constraint (at index 0) is <tt>W[2] + 2*W[3] - 3 = 0</tt>, then the linear constraints array contains the terms <tt>(0,2,1), (0,3,2)</tt> and the <tt>b</tt> vector has <tt>b[0]=-3</tt>.</t>
<t>The quadratic constraints are given as input in an array <tt>lqc[]</tt> that contains triples <tt>(x,y,z)</tt>; one such triple represents the constraint that <tt>W[x] * W[y] = W[z]</tt>. To process quadratic constraints, tableau <tt>T</tt> is augmented with 3 extra rows per triple, called <tt>Qx</tt>, <tt>Qy</tt>, and <tt>Qz</tt> which hold <em>copied</em> witnesses and their products. If the <tt>i</tt>-th quadratic constraint is <tt>(x,y,z)</tt>, then the prover sets <tt>Qx[i] = W[x]</tt>, <tt>Qy[i] = W[y]</tt> and <tt>Qz[i] = W[x] * W[y]</tt>. Next, the prover adds a linear constraint that <tt>Qx[i] - W[x] = 0</tt>, <tt>Qy[i] - W[y] = 0</tt> and <tt>Qz[i] - W[z] = 0</tt> to ensure that the copied witness is consistent.</t>
<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 <tt>Qz</tt> row is the product of its counterpart in the <tt>Qx</tt> and <tt>Qy</tt> row.</t>
<t>The prover computes the quadratic test polynomial <tt>y_quad</tt> across degree <tt>DBLOCK</tt>. The middle <tt>WR</tt> witness values of <tt>y_quad</tt> are identically zero by construction and are omitted from the proof. The proof contains only the non-zero segments: <tt>quad_poly_low</tt> of length <tt>NREQ</tt> (elements <tt>0..NREQ</tt>) and <tt>quad_poly_high</tt> of length <tt>DBLOCK - BLOCK</tt> (elements <tt>BLOCK..DBLOCK</tt>).</t>
</section>

<section anchor="selection-of-challenge-indicies"><name>Selection of challenge indicies</name>
<t>The last step of the prove method is for the verifier to select a subset of <tt>NREQ</tt> unique indices (sampled without replacement) from the range <tt>0..(NCOL - DBLOCK)</tt> and request that the prover open these columns of tableau <tt>T</tt> (at column offsets <tt>DBLOCK + idx</tt>). These opened columns, along with their column blinding nonces and Merkle authentication paths, are then used to verify consistency with the polynomial responses sent by the prover.</t>
</section>

<section anchor="ligero-prover-procedure"><name>Ligero Prover procedure</name>
<t>The <tt>statement_hash</tt> argument is application-dependent and commits to the circuit or statement being proven.</t>

<sourcecode type="rust"><![CDATA[impl<F: Field + 'static> LigeroProver<F> {
    pub fn prove(
        &self,
        commit: &LigeroCommitResult<F>,
        lqc: &[LqcTriple],
        a: &[LigeroTerm<F>],
        b: &[F],
        statement_hash: &[u8],
        ts: &mut Transcript,
    ) -> LigeroProof<F> {
        ts.write_bytes(statement_hash);

        let geom = self.geometry;
        let nqw = geom.total_rows - 3;
        let nq = lqc.len();

        let u_ldt = gen_uldt(ts, nqw);
        let alphal = gen_alphal(ts, b.len());
        let alphaq = gen_alphaq(ts, nq);
        let u_quad = gen_uquad(ts, geom.num_quad_rows);

        let y_ldt = self.prove_compute_y_ldt(commit, &u_ldt);
        let a_full = self.prove_compute_a_full(lqc, a, &alphal, &alphaq);
        let y_dot = self.prove_compute_y_dot(commit, &a_full);
        let y_quad = self.prove_compute_y_quad(commit, &u_quad);

        let y_quad_0 = y_quad[0..geom.num_queries].to_vec();
        let y_quad_2 = y_quad[geom.block_len..geom.dblock_len].to_vec();

        ts.write_elt_field_slice(&y_ldt);
        ts.write_elt_field_slice(&y_dot);
        ts.write_elt_field_slice(&y_quad_0);
        ts.write_elt_field_slice(&y_quad_2);

        let idx = ts.choose(geom.encoded_len - geom.dblock_len, geom.num_queries);
        let mut query_nonces = Vec::with_capacity(geom.num_queries);
        for &col in &idx {
            query_nonces.push(commit.nonces[col].clone());
        }
        let merkle_paths =
            open_merkle_heap(&commit.merkle, &idx).expect("Failed to open Merkle heap");

        let mut req = Vec::new();
        for row in 0..commit.tableau.len() {
            for &col in &idx {
                let col_idx = col + geom.dblock_len;
                req.push(commit.tableau[row][col_idx]);
            }
        }

        LigeroProof {
            ldt_poly: y_ldt,
            linear_poly: y_dot,
            quad_poly_low: y_quad_0,
            quad_poly_high: y_quad_2,
            column_nonces: query_nonces,
            queried_columns: req,
            merkle_paths,
        }
    }

    fn prove_compute_y_ldt(&self, commit: &LigeroCommitResult<F>, u_ldt: &[F]) -> Vec<F> {
        let geom = self.geometry;
        let nqw = geom.total_rows - 3;
        let mut y_ldt = commit.tableau[geom.ldt_row_idx()][0..geom.block_len].to_vec();
        for i in 0..nqw {
            axpy(
                &mut y_ldt,
                &commit.tableau[geom.witness_row_start() + i][0..geom.block_len],
                u_ldt[i],
            );
        }
        y_ldt
    }

    fn prove_compute_a_full(
        &self,
        lqc: &[LqcTriple],
        a: &[LigeroTerm<F>],
        alphal: &[F],
        alphaq: &[Vec<F>],
    ) -> Vec<F> {
        let geom = self.geometry;
        let nqw = geom.total_rows - 3;
        let nq = lqc.len();

        let mut a_full = vec![F::zero(); nqw * geom.witnesses_per_row];
        for term in a {
            a_full[term.witness_idx] += term.coeff * alphal[term.constraint_idx];
        }

        let nqtriples_w = geom.num_quad_rows * geom.witnesses_per_row;
        let ax_offset = (nqw - 3 * geom.num_quad_rows) * geom.witnesses_per_row;
        let ay_offset = ax_offset + nqtriples_w;
        let az_offset = ay_offset + nqtriples_w;

        for i in 0..geom.num_quad_rows {
            let mut j = 0;
            while j < geom.witnesses_per_row && j + i * geom.witnesses_per_row < nq {
                let idx = j + i * geom.witnesses_per_row;
                let l = lqc[idx];
                a_full[ax_offset + idx] += alphaq[idx][0];
                a_full[l.x] -= alphaq[idx][0];
                a_full[ay_offset + idx] += alphaq[idx][1];
                a_full[l.y] -= alphaq[idx][1];
                a_full[az_offset + idx] += alphaq[idx][2];
                a_full[l.z] -= alphaq[idx][2];
                j += 1;
            }
        }
        a_full
    }

    fn prove_compute_y_dot(&self, commit: &LigeroCommitResult<F>, a_full: &[F]) -> Vec<F> {
        let geom = self.geometry;
        let nwqrow = geom.total_rows - 3;
        let mut y_dot = commit.tableau[geom.linear_row_idx()][0..geom.dblock_len].to_vec();
        for i in 0..nwqrow {
            let mut a_ext = vec![F::zero(); geom.block_len];
            let start = i * geom.witnesses_per_row;
            a_ext[geom.num_queries..(geom.num_queries + geom.witnesses_per_row)]
                .copy_from_slice(&a_full[start..(start + geom.witnesses_per_row)]);
            let a_evals = self.rs_block.encode_row()(&a_ext);
            vaxpy(
                &mut y_dot,
                &commit.tableau[geom.witness_row_start() + i][0..geom.dblock_len],
                &a_evals[0..geom.dblock_len],
            );
        }
        y_dot
    }

    fn prove_compute_y_quad(&self, commit: &LigeroCommitResult<F>, u_quad: &[F]) -> Vec<F> {
        let geom = self.geometry;
        let mut y_quad = commit.tableau[geom.quad_row_idx()][0..geom.dblock_len].to_vec();
        for i in 0..geom.num_quad_rows {
            let mut tmp = commit.tableau[geom.quad_z_row_start() + i][0..geom.dblock_len].to_vec();
            for j in 0..geom.dblock_len {
                tmp[j] -= commit.tableau[geom.quad_x_row_start() + i][j]
                    * commit.tableau[geom.quad_y_row_start() + i][j];
            }
            axpy(&mut y_quad, &tmp, u_quad[i]);
        }
        y_quad
    }
}
]]></sourcecode>
</section>
</section>

<section anchor="ligero-verification-procedure"><name>Ligero verification procedure</name>
<t>This section specifies how to verify a Ligero proof with respect to a commitment root, statement hash, linear constraints <tt>A \cdot W + b = 0</tt>, and quadratic constraints <tt>lqc[]</tt>.</t>
<t>The verification procedure checks:</t>

<ol spacing="compact">
<li>Merkle Proof Consistency (<tt>verify_merkle</tt>): Verifies the authentication paths for the opened columns against the committed Merkle root.</li>
<li>Low-Degree Test (<tt>verify_ldt</tt>): Verifies that the linear combination of queried column entries equals the Reed-Solomon encoding of <tt>ldt_poly</tt> evaluated at the query column indices.</li>
<li><strong>Linear Constraint Test (<tt>verify_dot</tt>)</strong>: Verifies that the inner product combination matches <tt>linear_poly</tt> evaluations at the query columns, and that <tt>\sum \text{linear\_poly}[j] + \langle b, \alpha_l \rangle = 0</tt>.</li>
<li><strong>Quadratic Constraint Test (<tt>verify_quad</tt>)</strong>: Verifies that <tt>z[i] - x[i] \cdot y[i]</tt> across the quadratic triple rows matches <tt>y_quad</tt> (reconstructed from <tt>quad_poly_low</tt> and <tt>quad_poly_high</tt>) at the query column indices.</li>
</ol>

<sourcecode type="rust"><![CDATA[impl<F: Field + 'static> LigeroVerifier<F> {
    pub fn verify(
        &self,
        nw: usize,
        b: &[F],
        root: &[u8; 32],
        proof: &LigeroProof<F>,
        a: &[LigeroTerm<F>],
        statement_hash: &[u8],
        lqc: &[LqcTriple],
        ts: &mut Transcript,
    ) -> Result<(), VerificationError> {
        ts.write_bytes(statement_hash);

        let geom = LigeroGeometry::new(&self.config, nw, lqc.len());
        let expected_req_len = geom.total_rows * geom.num_queries;
        if proof.queried_columns.len() != expected_req_len {
            return Err(VerificationError::InvalidQueriedColumnsLength {
                expected: expected_req_len,
                actual: proof.queried_columns.len(),
            });
        }
        if proof.ldt_poly.len() != geom.block_len
            || proof.linear_poly.len() != geom.dblock_len
            || proof.quad_poly_low.len() != geom.num_queries
            || proof.quad_poly_high.len() != geom.dblock_len - geom.block_len
            || proof.column_nonces.len() != geom.num_queries
        {
            return Err(VerificationError::InvalidProofPolynomialsLength);
        }
        let nwqrow = geom.total_rows - 3;

        let u_ldt = gen_uldt(ts, nwqrow);
        let alphal = gen_alphal(ts, b.len());
        let alphaq = gen_alphaq(ts, lqc.len());
        let u_quad = gen_uquad(ts, geom.num_quad_rows);

        ts.write_elt_field_slice(&proof.ldt_poly);
        ts.write_elt_field_slice(&proof.linear_poly);
        ts.write_elt_field_slice(&proof.quad_poly_low);
        ts.write_elt_field_slice(&proof.quad_poly_high);

        let idx = ts.choose(geom.encoded_len - geom.dblock_len, geom.num_queries);

        self.verify_merkle(&geom, root, proof, &idx)?;
        self.verify_ldt(&geom, proof, &u_ldt, &idx)?;
        self.verify_dot(&geom, b, proof, a, lqc, &alphal, &alphaq, &idx)?;
        self.verify_quad(&geom, proof, &u_quad, &idx)?;

        Ok(())
    }

    fn verify_merkle(
        &self,
        geom: &LigeroGeometry,
        root: &[u8; 32],
        proof: &LigeroProof<F>,
        idx: &[usize],
    ) -> Result<(), VerificationError> {
        let leaf_hash_fn = |col: usize| {
            let mut r_idx = 0;
            for i in 0..idx.len() {
                if idx[i] == col {
                    r_idx = i;
                    break;
                }
            }
            let mut data = Vec::new();
            data.extend_from_slice(&proof.column_nonces[r_idx]);
            for row in 0..geom.total_rows {
                data.extend_from_slice(
                    &proof.queried_columns[row * geom.num_queries + r_idx].to_bytes(),
                );
            }
            sha256_bytes(&data)
        };

        verify_merkle_proof(
            geom.encoded_len - geom.dblock_len,
            root,
            idx,
            &proof.merkle_paths,
            leaf_hash_fn,
        )
        .map_err(|_| VerificationError::MerkleProofInvalid)
    }

    fn verify_ldt(
        &self,
        geom: &LigeroGeometry,
        proof: &LigeroProof<F>,
        u_ldt: &[F],
        idx: &[usize],
    ) -> Result<(), VerificationError> {
        let nwqrow = geom.total_rows - 3;
        let ildt = geom.ldt_row_idx();
        let iw = geom.witness_row_start();

        let mut yc_ldt =
            proof.queried_columns[ildt * geom.num_queries..(ildt + 1) * geom.num_queries].to_vec();
        for i in 0..nwqrow {
            let row_req = &proof.queried_columns
                [(iw + i) * geom.num_queries..(iw + i + 1) * geom.num_queries];
            axpy(&mut yc_ldt, row_req, u_ldt[i]);
        }
        let yp_ldt = self.interpolate_req_columns(geom, geom.block_len, &proof.ldt_poly, idx);
        if yc_ldt == yp_ldt {
            Ok(())
        } else {
            Err(VerificationError::LowDegreeTestFailed)
        }
    }

    fn verify_dot(
        &self,
        geom: &LigeroGeometry,
        b: &[F],
        proof: &LigeroProof<F>,
        a: &[LigeroTerm<F>],
        lqc: &[LqcTriple],
        alphal: &[F],
        alphaq: &[Vec<F>],
        idx: &[usize],
    ) -> Result<(), VerificationError> {
        let nwqrow = geom.total_rows - 3;
        let idot = geom.linear_row_idx();
        let iw = geom.witness_row_start();

        let mut a_full = vec![F::zero(); nwqrow * geom.witnesses_per_row];
        for term in a {
            a_full[term.witness_idx] += term.coeff * alphal[term.constraint_idx];
        }
        let nqtriples_w = geom.num_quad_rows * geom.witnesses_per_row;
        let ax_offset = (nwqrow - 3 * geom.num_quad_rows) * geom.witnesses_per_row;
        let ay_offset = ax_offset + nqtriples_w;
        let az_offset = ay_offset + nqtriples_w;

        for i in 0..geom.num_quad_rows {
            let mut j = 0;
            while j < geom.witnesses_per_row && j + i * geom.witnesses_per_row < lqc.len() {
                let idx_lqc = j + i * geom.witnesses_per_row;
                let l = lqc[idx_lqc];
                a_full[ax_offset + idx_lqc] += alphaq[idx_lqc][0];
                a_full[l.x] -= alphaq[idx_lqc][0];
                a_full[ay_offset + idx_lqc] += alphaq[idx_lqc][1];
                a_full[l.y] -= alphaq[idx_lqc][1];
                a_full[az_offset + idx_lqc] += alphaq[idx_lqc][2];
                a_full[l.z] -= alphaq[idx_lqc][2];
                j += 1;
            }
        }

        let mut yc_dot =
            proof.queried_columns[idot * geom.num_queries..(idot + 1) * geom.num_queries].to_vec();
        let a_interp = ReedSolomonCode::new(geom.block_len, geom.encoded_len, &self.subfield);
        for i in 0..nwqrow {
            let mut a_ext = vec![F::zero(); geom.block_len];
            let start = i * geom.witnesses_per_row;
            a_ext[geom.num_queries..geom.block_len]
                .copy_from_slice(&a_full[start..(start + geom.witnesses_per_row)]);
            let a_evals = a_interp.encode_row()(&a_ext);
            let mut a_queried = Vec::with_capacity(geom.num_queries);
            for &col in idx {
                a_queried.push(a_evals[geom.dblock_len + col]);
            }
            let row_req = &proof.queried_columns
                [(iw + i) * geom.num_queries..(iw + i + 1) * geom.num_queries];
            vaxpy(&mut yc_dot, row_req, &a_queried);
        }
        let yp_dot = self.interpolate_req_columns(geom, geom.dblock_len, &proof.linear_poly, idx);
        if yc_dot != yp_dot {
            return Err(VerificationError::LinearConstraintFailed);
        }

        let want_dot = dot(b, alphal);
        let proof_dot =
            dot1(&proof.linear_poly[geom.num_queries..(geom.num_queries + geom.witnesses_per_row)]);
        if proof_dot + want_dot != F::zero() {
            return Err(VerificationError::LinearConstraintSumMismatch);
        }

        Ok(())
    }

    fn verify_quad(
        &self,
        geom: &LigeroGeometry,
        proof: &LigeroProof<F>,
        u_quad: &[F],
        idx: &[usize],
    ) -> Result<(), VerificationError> {
        let iquad = geom.quad_row_idx();
        let iqx = geom.quad_x_row_start();
        let iqy = geom.quad_y_row_start();
        let iqz = geom.quad_z_row_start();

        let mut yc_quad = proof.queried_columns
            [iquad * geom.num_queries..(iquad + 1) * geom.num_queries]
            .to_vec();
        for i in 0..geom.num_quad_rows {
            let u = u_quad[i];
            let mut tmp = vec![F::zero(); geom.num_queries];
            for j in 0..geom.num_queries {
                let x_val = proof.queried_columns[(iqx + i) * geom.num_queries + j];
                let y_val = proof.queried_columns[(iqy + i) * geom.num_queries + j];
                let z_val = proof.queried_columns[(iqz + i) * geom.num_queries + j];
                tmp[j] = z_val - x_val * y_val;
            }
            axpy(&mut yc_quad, &tmp, u);
        }
        let mut y_quad = proof.quad_poly_low.clone();
        y_quad.resize(geom.block_len, F::zero());
        y_quad.extend_from_slice(&proof.quad_poly_high);
        let yp_quad = self.interpolate_req_columns(geom, geom.dblock_len, &y_quad, idx);
        if yc_quad != yp_quad {
            return Err(VerificationError::QuadraticConstraintFailed);
        }

        Ok(())
    }
}
]]></sourcecode>
</section>
</section>

<section anchor="overview"><name>Overview of the Longfellow protocol</name>
<t>The Longfellow ZK protocol uses two protocol components. The first is a variant of the sumcheck protocol, modified to support zero knowledge. Informally, the standard sumcheck prover takes the description of a circuit and the concrete values of all the wires in the circuit, and produces a proof that all wires have been computed correctly.  The proof itself is a sequence of field elements.  Longfellow uses an encrypted-variant of the sumcheck prover that also takes as input a random and secret one-time pad and outputs an &quot;encrypted&quot; proof such that each element in this proof is the difference of the element in the standard sumcheck proof and its corresponding element in the pad.  (The choice of &quot;difference&quot; instead of &quot;sum&quot; is a matter of convention.)</t>
<t>In this encrypted sumcheck variant, the verifier cannot check the proof
directly because it cannot access the one-time pad.  Instead of running the
sumcheck verifier directly, a commitment scheme is used to hide the
one-time pad, and the sumcheck verifier is translated into a sequence of linear and
quadratic constraints on the inputs and the one-time pad.  A secondary proof system
is then used to produce a proof with respect to the commitment that the constraints are satisfied.</t>
<t>The protocol requires both parties to agree on a circuit as part of the theorem statement.  The wire format of a circuit is defined in a separate document.</t>
<t>Some of the wires of the circuit are <em>inputs</em>, i.e., set outside the circuit and not computed by the circuit itself.  Some of the inputs are <em>public</em>, i.e., known to both parties, and some are <em>private</em>, i.e., known only to the prover.  Sumcheck does not use the distinction between public and private inputs. This document distinguishes private inputs from the one-time pad.  The commitment scheme does not use public inputs at all, but it does treat private inputs and the one-time pad elements
equally.  These constraints motivate the following terminology.</t>

<ul spacing="compact">
<li><em>public inputs</em>: inputs to the circuit known to both parties.</li>
<li><em>private inputs</em>: inputs to the circuit known to the prover but not to the verifier.</li>
<li><em>inputs</em>: both public and private inputs.  When forming an array of all inputs, the public inputs come first, followed
by the private inputs.</li>
<li><em>witnesses</em>: the private inputs and the elements in the one-time pad.  When forming an array of all witnesses, the private inputs come first, followed by the one-time pad.</li>
</ul>
<t>Thus, at a high level, the sequence of operations in the ZK protocol is the following:</t>

<ol>
<li><t>The prover commits to all witness values.</t>
</li>
<li><t>The prover runs the encrypted sumcheck prover on the witness values to producing an encrypted proof, all-the-while sending the encrypted proof to the verifier.</t>
</li>
<li><t>Both the prover and the verifier take the public inputs and the
encrypted proof and produce a sequence of constraints.</t>
</li>
<li><t>Using the commitment scheme and the witnesses, the prover generates
a proof that the constraints from step 3 are satisfied.</t>
</li>
<li><t>The verifier uses the proof from step 4 and the constraints from
step 3 to check the constraints.</t>
</li>
</ol>
<t>Steps 2 and 3 are referred to as &quot;sumcheck&quot;, and the rest as &quot;commitment scheme&quot;.  While the classification of step 3 as &quot;sumcheck&quot; is  arbitrary, there are situations where one might want to use a commitment scheme other than the Ligero protocol specified in this document.  In this case, the &quot;commitment scheme&quot; can change while the &quot;sumcheck&quot; remains unaffected.</t>

<section anchor="parameters-needed-to-define-longfellow"><name>Parameters needed to define Longfellow</name>
<t>Longfellow is parameterized by a sumcheck protocol, a commitment protocol, and a Fiat-Shamir instantiation.
A selection of all three defines a <tt>Longfellow profile</tt>. This document introduces one opinionated profile that
uses (a) The longfellow sumcheck described below, (b) the Ligero commitment described above, (c) the Fiat-Shamir instantiation defined
above and using SHA-256 as the function <tt>H</tt>.</t>
<t>In addition to the component profile, the following security parameters described in the <eref target="#ligero-parameters">Ligero Zero-Knowledge Proof Section</eref> must also be selected:</t>

<ul spacing="compact">
<li><tt>NREQ</tt>: The number of columns of the commitment matrix in the Ligero commitment scheme that the Verifier requests to be revealed by the Prover.</li>
<li><tt>rate</tt>: The inverse rate of the error correcting code used by Ligero.</li>
</ul>
<t>These two parameters are chosen to balance the size of the proof against the soundness of the protocol.  In principle, these parameters can differ based on the Field size. Based on the latest analysis, we support the following profiles which have been analyzed to provide at least 115 bits of security.</t>

<ul spacing="compact">
<li>(p256, 132, 7)</li>
<li>(GF(2<sup>128</sup>), 132, 7)</li>
</ul>
</section>
</section>

<section anchor="sumcheck"><name>Sumcheck</name>

<section anchor="special-conventions-for-sumcheck-arrays"><name>Special conventions for sumcheck arrays</name>
<t>The square brackets <tt>A[j]</tt> denote generic array indexing.</t>
<t>For the arrays of field elements used in the sumcheck protocol,
however, it is convenient to use the conventions that follow.</t>
<t>The sumcheck array <tt>A[i]</tt> is implicitly assumed to be defined for all
nonnegative integers <tt>i</tt>, padding with zeroes as necessary.  Here,
&quot;zero&quot; is well defined because <tt>A[]</tt> is an array of field elements.</t>
<t>Arrays can be multi-dimensional, as in the three-dimensional array
<tt>Q[g, l, r]</tt>.  It is understood that the array is padded with
infinitely many zeroes in each dimension.</t>
<t>Depending on the context, some arrays may consist of almost all non-zero
values, while other arrays may be sparse, containing very few non-zero
values (ignoring the zero-padding convention above). Implementations
should use dense or sparse representations of arrays as appropriate.</t>
<t>Given array <tt>A[]</tt> and field element <tt>x</tt>, the function
<tt>bind(A, x)</tt> returns the array <tt>B</tt> such that</t>

<artwork><![CDATA[  B[i] = (1 - x) * A[2 * i] + x * A[2 * i + 1]
]]></artwork>
<t>In case of multiple dimensions such as <tt>Q[g, l, r]</tt>,
always bind across the first dimension.  For example,</t>

<artwork><![CDATA[  bind(Q, x)[g, l, r] =
     (1 - x) * Q[2 * g, l, r] + x * Q[2 * g + 1, l, r]
]]></artwork>
<t>This <tt>bind</tt> can be generalized to an array of field elements as follows:</t>

<artwork><![CDATA[  bindv(A, X) =
       A                                  if X is empty
       bindv(bind(A, X[0]), X[1..])       otherwise
]]></artwork>
<t>Two-dimentional arrays can be transposed in the usual way:</t>

<artwork><![CDATA[  transpose(Q)[l, r] = Q[r, l] .
]]></artwork>
</section>

<section anchor="the-eq-array"><name>The <tt>EQ[]</tt> array</name>
<t><tt>EQ_{n}[i, j]</tt> is a special 2D array defined as</t>

<artwork><![CDATA[   EQ_{n}[i, j] = 1   if i = j and i < n
                  0   otherwise
]]></artwork>
<t>The sumcheck literature usually assumes that <tt>n</tt> is a power of 2,
but this document allows <tt>n</tt> to be an arbitrary integer.  When <tt>n</tt> is clear from
context or unimportant, the subscript is omitted like
<tt>EQ[i, j]</tt>.</t>
<t><tt>EQ[]</tt> is important because the general expansion</t>

<artwork><![CDATA[   V[i] = SUM_{j} EQ[i, j] V[j]
]]></artwork>
<t>commutes with binding, yielding</t>

<artwork><![CDATA[   bindv(V, X) = SUM_{j} bindv(EQ, X)[j] V[j] .
]]></artwork>
<t>That is, one way to compute <tt>bindv(V, X)</tt> is via
dot product of <tt>V</tt> with <tt>bindv(EQ, X)</tt>.  This strategy
may or may not be advantageous in practice, but it
becomes mandatory when <tt>bindv(V, X)</tt> must be computed
via a commitment scheme that supports linear
constraints but not binding.</t>
<t>This document only uses bindings of <tt>EQ</tt> and never <tt>EQ</tt> itself,
and therefore the whole array never needs to be stored explicitly.
For <tt>n = 2^l</tt> and <tt>X</tt> of size <tt>l</tt>, <tt>bindv(EQ_{n}, X)</tt> can be computed
recursively in linear time as follows.</t>

<sourcecode type="rust"><![CDATA[/// Computes the multilinear extension of the equality polynomial EQ_{2^l}(x, r).
pub fn bindeq<F: Field>(challenges: &[F]) -> Vec<F> {
    let log_n = challenges.len();
    if log_n == 0 {
        return vec![F::one()];
    }
    let n = 1 << log_n;
    let mut b = vec![F::zero(); n];
    let a = bindeq(&challenges[1..]);
    for i in 0..(n / 2) {
        b[2 * i] = (F::one() - challenges[0]) * a[i];
        b[2 * i + 1] = challenges[0] * a[i];
    }
    b
}

/// Evaluates the equality polynomial EQ(r, x) at integer point `x_int` of length `nbits`.
pub fn eq<F: Field>(r: &[F], x_int: usize, nbits: usize) -> F {
    let mut product = F::one();
    for b in 0..nbits {
        if ((x_int >> b) & 1) == 1 {
            product *= r[b];
        } else {
            product *= F::one() - r[b];
        }
    }
    product
}

/// Evaluates a linear combination of equality polynomials across two challenge vectors:
/// `eq2(x, logn, g0, g1, alpha) = eq(g0, x, logn) + alpha * eq(g1, x, logn)`
pub fn eq2<F: Field>(x: usize, logn: usize, g0: &[F], g1: &[F], alpha: F) -> F {
    eq(g0, x, logn) + alpha * eq(g1, x, logn)
}
]]></sourcecode>
<t>For <tt>m &lt;= n</tt>, <tt>bindv(EQ_{n}, X)[i]</tt> and <tt>bindv(EQ_{m}, X)[i]</tt>
agree for <tt>0 &lt;= i &lt; m</tt>, and thus
<tt>bindv(EQ_{m}, X)[i]</tt> can be computed by padding <tt>m</tt> to the next power of 2
and ignoring the extra elements.
With some care, it is possible to compute <tt>bindeq()</tt>
in-place on a single array of arbitrary size <tt>m</tt> and eliminate
the recursion completely.</t>

<section anchor="remark"><name>Remark</name>
<t>Let <tt>m &lt;= n</tt>, <tt>A = bindv(EQ_{m}, X)</tt> and <tt>B = bindv(EQ_{n}, X)</tt>.  It
is true that <tt>A[i] = B[i]</tt> for <tt>i &lt; m</tt>.  However, it is also true that <tt>A[i] = 0</tt> for <tt>i &gt;= m</tt>, whereas <tt>B[i]</tt> is in general nonzero.  Thus, care
must be taken when computing a further binding <tt>bindv(A, Y)</tt>,
which is in general not the same as <tt>bindv(B, Y)</tt>.  A second binding is
not needed in this document,  but certain closed-form expressions for
the binding found in the literature agree with these definitions only
when <tt>m</tt> is a power of 2.</t>
</section>
</section>

<section anchor="circuits"><name>Circuits</name>

<section anchor="layered-circuits"><name>Layered circuits</name>
<t>A circuit consists of <tt>NL</tt> <em>layers</em>.  By convention, layer <tt>j</tt>
computes wires <tt>V[j]</tt> given wires <tt>V[j + 1]</tt>, where each <tt>V[j]</tt> is an
array of field elements.  A <em>wire</em> is an element <tt>V[j][w]</tt> for some <tt>j</tt>
and <tt>w</tt>.  Thus, <tt>V[0]</tt> denotes the output wires of the entire circuit,
and <tt>V[NL]</tt> denotes the input wires.</t>
<t>A circuit is intended to check that some property of the input holds,
and by convention, the check is considered successful if all output
wires are 0, that is, if <tt>V[0][w] = 0</tt> for all <tt>w</tt>.</t>
</section>

<section anchor="quad-representation"><name>Quad representation</name>
<t>The computation of circuit is defined by a set of <em>quads</em> <tt>Q[j]</tt>, one
per layer.  Given the output of layer <tt>j + 1</tt>, the output of of layer
<tt>j</tt> is given by the following equation:</t>

<artwork><![CDATA[  V[j][g] = SUM_{l, r} Q[j][g, l, r] V[j + 1][l] V[j + 1][r] .
]]></artwork>
<t>The quad <tt>Q[j][]</tt> is thus a three-dimensional array in the indices <tt>g</tt>,
<tt>l</tt>, and <tt>r</tt> where <tt>0 &lt;= g &lt; NW[j]</tt> and <tt>0 &lt;= l, r &lt; NW[j + 1]</tt>.  In
practice, <tt>Q[j][]</tt> is sparse.</t>
<t>The specification of the circuit contains an auxiliary
vector of quantities <tt>LV[j]</tt> with the property that <tt>V[j][w] = 0</tt>
for all <tt>w &gt;= 2^{LV[j]}</tt>.  Informally, <tt>LV[j]</tt> is the number
of bits needed to name a wire at layer <tt>j</tt>, but <tt>LV[j]</tt> may
be larger than the minimum required value.</t>
</section>

<section anchor="in-circuit-assertions"><name>In-circuit assertions</name>
<t>In the libzk system, a theorem is represented by a circuit such that
the theorem is true if and only if all outputs of the circuit are
zero.  It happens in practice that many output wires are computed early
in the circuit (i.e., in a layer closer to the input), but because of
layering, they need to be copied all the way to output layer in order
to be compared against zero.  This copy seems to introduce large
overheads in practice.</t>
<t>A special convention can mitigate this problem.  Abstractly,
a layer is represented by <em>two</em> quads <tt>Q</tt> and <tt>Z</tt>, and the
operation of the layer is described by the two equations</t>

<artwork><![CDATA[  V[j][g] = SUM_{l, r} Q[j][g, l, r] V[j + 1][l] V[j + 1][r]
       0  = SUM_{l, r} Z[j][g, l, r] V[j + 1][l] V[j + 1][r]
]]></artwork>
<t>Thus, the <tt>Z</tt> quad asserts that, for given layer <tt>j</tt>
and output wire <tt>g</tt>, a certain quadratic combination of
the input wires is zero.</t>
<t>The actual protocol verifies a random linear combination
of those two equations, effectively operating on a combined
quad <tt>QZ = Q + beta * Z</tt> for some random <tt>beta</tt>.</t>
<t>To allow for a compact representation of the two quads without
losing any real generality, the following conditions are imposed:</t>

<ul spacing="compact">
<li>The two quads <tt>Q</tt> and <tt>Z</tt> are disjoint: for all layers <tt>j</tt> and output
wire <tt>g</tt>, if any <tt>Q[j][g, ., .]</tt> are nonzero, then all <tt>Z[j][g, ., .]</tt>
are zero, and vice versa.</li>
<li><tt>Z</tt> is binary: <tt>Z[j][g, l, r] \in {0, 1}</tt></li>
</ul>
<t>With these choices, the two quads allow a compact sparse
representation as a single list of 4-tuples <tt>(g, l, r, v)</tt>
with the following conventions:</t>

<ul spacing="compact">
<li>If <tt>v = 0</tt>, the 4-tuple represents an element of <tt>Z</tt>,
and <tt>Z[j][g, l, r] = 1</tt>.</li>
<li>If <tt>v != 0</tt>, the 4-tuple represents an element of <tt>Q</tt>,
and <tt>Q[j][g, l, r] = v</tt>.</li>
<li>All other elements of <tt>Q</tt> and <tt>Z</tt> not specified by the list are
zero.</li>
</ul>
<t>Moreover, this compact representation can be transformed into
a representation of <tt>QZ = Q + beta * Z</tt> by replacing all <tt>v = 0</tt>
with <tt>v = beta</tt>.</t>
</section>
</section>

<section anchor="representation-of-polynomials"><name>Representation of polynomials</name>
<t>In a generic sumcheck protocol, the prover sends to the verifier
polynomials of a degree specified in advance.  In the present document,
the polynomials are always of degree two, and are represented by their
evaluations at three points <tt>P0 = 0</tt>, <tt>P1 = 1</tt>, and <tt>P2</tt>, where <tt>0</tt>
and <tt>1</tt> are the additive and multiplicative identities in the field.
The choice of <tt>P2</tt> depends upon the field.  For fields of characteristic
greater than 2, set <tt>P2 = 2</tt> (= <tt>1 + 1</tt> in the field).  For <tt>GF(2^128)</tt>
expressed as <tt>GF(2)[X] / (X^128 + X^7 + X^2 + X + 1)</tt>, set <tt>P2 = inj(2)</tt>
as defined in <xref target="gf2k"></xref>.  This document does not prescribe a choice of
P2 for binary fields other than <tt>GF(2^128)</tt>.</t>
<t>At the start of each round of communication in a sumcheck protocol, both the
prover and the (virtual) sumcheck verifier agree on a claim value, which
represents the sum of the evaluation of some function at all inputs <tt>{0,1}^*</tt>.
The polynomials computed by the prover represent the sum of the
evaluations of the multilinear extension of that same function, with one
argument fixed to <tt>P0</tt>, <tt>P1</tt>, or <tt>P2</tt>, and all other arguments chosen
from <tt>{0,1}</tt>.
Therefore, the sum of <tt>p(P0) + p(P1)</tt> is equal to the claim from the
start of the sumcheck round, and the prover only needs to send two field
elements in order for the parties to agree on the entire degree two
polynomial.
Here, <tt>p(P0)</tt> and <tt>p(P2)</tt> are sent to the (virtual) sumcheck verifier,
and <tt>p(P1)</tt> is reconstructed from <tt>p(P0)</tt> and the claim.</t>
</section>

<section anchor="transcript-encryption-and-deferred-verification"><name>Transcript encryption and deferred verification</name>
<t>The sumcheck protocol produces a series of polynomials and claim values,
computed from the circuit input values, to prove that the circuit
was evaluated correctly.
As described in <xref target="overview"></xref>, these polynomials and claims are not
directly revealed to the verifier.
Rather, the field elements that make up these values are encrypted with
a one-time pad by subtracting a randomly chosen pad value from each
field element, and the difference is sent to the verifier.</t>
<t>When the verifier executes the sumcheck protocol, it does not have
direct access to all the circuit inputs, and it is only given the
one-time pad encrypted forms of the sumcheck polynomials and per-layer
claims, not the corresponding plaintext values.
Therefore, the prover and verifier defer part of the verification by
producing a series of linear and quadratic constraints, relating the
private input values and the one-time pad values, so that those
constraints can be checked with the Ligero zero-knowledge system (see
<xref target="ligero-zk-proof"></xref>).</t>
<t>The variables used in these constraints are assigned sequentially, first
to the private circuit inputs, then to elements of the one-time pad.
Variables for one-time pad values are assigned to values for circuit
layers in order, starting with the output layer (layer 0). Within each layer,
variables are first assigned to one-time pad values for sumcheck
polynomials, then to the per-layer claim values. The number of sumcheck
polynomials for each layer is equal to double the value of
<tt>logw</tt> for that layer of the circuit (two hands for each round).
The polynomials are represented by two field
elements each, one for the evaluation at <tt>P0 = 0</tt>, and one for the
evaluation at <tt>P2</tt>. At the end of the variables for each layer, three
variables are assigned for claim-related values. Two variables <tt>c0</tt> and <tt>c1</tt> are used
for the one-time pad values for the claims <tt>vl</tt> and <tt>vr</tt>. Then, a
variable <tt>cr</tt> is used for the product of those two one-time pad values (<tt>cr = c0 * c1</tt>).</t>

<sourcecode type="rust"><![CDATA[/// Padding for a single round of sumcheck (masks for p0 and p2 evals for both hands).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RoundPad<T> {
    pub hp: [[T; 2]; 2], // hp[hand] = [p0_mask, p2_mask]
}

/// Padding for final layer claim masks.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LayerClaimsPad<T> {
    pub c0: T,
    pub c1: T,
    pub cr: T, // Product c0 * c1
}

/// Padding for a single circuit layer.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LayerPad<T> {
    pub rounds: Vec<RoundPad<T>>,
    pub claims: LayerClaimsPad<T>,
}

/// Padding for the entire circuit.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CircuitPad<T> {
    pub layers: Vec<LayerPad<T>>,
}

impl<F: Field> LayerPad<F> {
    /// Samples concrete random field element masks for a single circuit layer.
    pub fn sample<R: Rng>(logw: usize, rng: &mut R) -> Self {
        let mut rounds = Vec::with_capacity(logw);
        for _ in 0..logw {
            rounds.push(RoundPad {
                hp: [
                    [F::sample(rng), F::sample(rng)],
                    [F::sample(rng), F::sample(rng)],
                ],
            });
        }
        let c0 = F::sample(rng);
        let c1 = F::sample(rng);
        let cr = c0 * c1;

        LayerPad {
            rounds,
            claims: LayerClaimsPad { c0, c1, cr },
        }
    }

    /// Appends all pad elements in order to a witness vector.
    pub fn flatten_into(&self, out: &mut Vec<F>) {
        for r in &self.rounds {
            out.extend_from_slice(&r.hp[0]);
            out.extend_from_slice(&r.hp[1]);
        }
        out.push(self.claims.c0);
        out.push(self.claims.c1);
        out.push(self.claims.cr);
    }
}

impl LayerPad<usize> {
    /// Generates the symbolic witness indices for a single circuit layer pad starting at `pad_base`.
    pub fn generate_indices(logw: usize, pad_base: &mut usize) -> Self {
        let mut rounds = Vec::with_capacity(logw);
        for _ in 0..logw {
            let hand0 = [*pad_base, *pad_base + 1];
            let hand1 = [*pad_base + 2, *pad_base + 3];
            *pad_base += 4;
            rounds.push(RoundPad { hp: [hand0, hand1] });
        }
        let c0 = *pad_base;
        let c1 = *pad_base + 1;
        let cr = *pad_base + 2;
        *pad_base += 3;

        LayerPad {
            rounds,
            claims: LayerClaimsPad { c0, c1, cr },
        }
    }
}

impl<F: Field> CircuitPad<F> {
    /// Samples random field element padding for all layers in the circuit and flattens them into witness padding.
    pub fn sample<R: Rng>(circuit_data: &Circuit<F>, rng: &mut R) -> (Self, Vec<F>) {
        let mut pad_witness = Vec::new();
        let mut layers = Vec::with_capacity(circuit_data.layers.len());
        for layer in &circuit_data.layers {
            let layer_pad = LayerPad::sample(layer.logw, rng);
            layer_pad.flatten_into(&mut pad_witness);
            layers.push(layer_pad);
        }
        (CircuitPad { layers }, pad_witness)
    }
}

impl CircuitPad<usize> {
    /// Generates symbolic witness indices for all layers in the circuit starting at `pad_base`.
    pub fn generate_indices<F: Field>(circuit_data: &Circuit<F>, pad_base: &mut usize) -> Self {
        let mut layers = Vec::with_capacity(circuit_data.layers.len());
        for layer in &circuit_data.layers {
            layers.push(LayerPad::generate_indices(layer.logw, pad_base));
        }
        CircuitPad { layers }
    }
}
]]></sourcecode>
</section>

<section anchor="transform-circuit-and-wires-into-a-padded-proof"><name>Transform circuit and wires into a padded proof</name>
<t>The prover constructs a padded proof by executing the sumcheck protocol
in order to certify that the wires at each layer of the circuit are
correctly calculated from the wires at the preceding layer.</t>
<t>The goal is to prove that, for some layer index <tt>j</tt>, and every output
wire index <tt>g</tt> in that layer, the following all hold with high
probability.</t>

<artwork><![CDATA[V[j][g] = SUM_{l, r} Q[j][g, l, r] V[j + 1][l] V[j + 1][r]

0 = SUM_{l, r} Z[j][g, l, r] V[j + 1][l] V[j + 1][r]
]]></artwork>
<t>These equations are combined into one equation after multiplying them by
random verifier challenges. This equation is of the form</t>

<artwork><![CDATA[claim = SUM_{l, r} QUAD[j][l, r] V[j + 1][l] V[j + 1][r]
]]></artwork>
<t>If we reinterpret the wire indices <tt>l</tt> and <tt>r</tt> as binary numbers,
replacing them both with <tt>log_num_input_wires</tt> many variables having
value 0 or 1, then this equation has the form needed to apply the
sumcheck protocol.</t>
<t>At each layer, both parties start with two claims that each represent a
linear combination of the layer's output wire values.
Concretely, the claims for the layer's outputs are <tt>bind(V[j], G[0])</tt>
and <tt>bind(V[j], G[1])</tt> where <tt>G[0]</tt> and <tt>G[1]</tt> are arrays of verifier
challenges.
These two claim values get combined into one using a random challenge
value.
In each successive round of communication, the function inside the
summation is replaced with a new function having one fewer parameter,
one of the output wire arrays is halved in size by binding it with a
random challenge, and the claim value is replaced with a newly computed
claim value.
The prover proves that the new claim values and the new function at each
round are consistent with those in the previous round by evaluating the
multilinear extension of the function at multiple points, including
points with a random challenge coordinate.
The prover computes a degree two polynomial by summing this multilinear
extension at many points, with the polynomial's parameter determining
the last parameter of the multilinear extension.
Two evaluations of this polynomial are sent to the verifier, though as
noted above these evaluations get encrypted with a one-time pad.
After several rounds of communication, the function being summed is
replaced with a constant, and both output wire arrays are replaced with
two new claim values.
Concretely, the new claims will be <tt>bind(V[j + 1], G'[0])</tt> and <tt>bind(V[j
+ 1], G'[1])</tt>, where <tt>V[j + 1]</tt> is the input wires of layer j, and
<tt>G'[0]</tt> and <tt>G'[1]</tt> are a fresh set of verifier challenges, chosen at
each round of the sumcheck protocol.
These two claim values are encrypted with a one-time pad and sent to the
verifier.</t>
<t>Before the first round, a fixed number of verifier challenges are
generated and discarded (<tt>begin_circuit</tt>). These are reserved for possible future
extensions to the protocol. Additionally, a fixed number of challenges
are generated for binding the output wires before the first round, with
the remainder of the challenges being discarded. In both of these cases,
<tt>MAX_LOGW = 40</tt> challenges are generated. For all subsequent layers,
challenges used for binding output wires are generated one at a time,
with no extra unused challenges.</t>

<sourcecode type="rust"><![CDATA[#[derive(Clone, Debug)]
pub struct SumcheckRoundEvals<F> {
    pub evals: [F; 2],
}

#[derive(Clone, Debug)]
pub struct SumcheckLayerProof<F> {
    pub hp: [Vec<SumcheckRoundEvals<F>>; 2],
    pub claims: [F; 2],
}

/// Returns the element at `index`, treating the slice as infinitely padded with zeroes.
pub fn vector_ref<F: Field>(w: &[F], index: usize) -> F {
    if index < w.len() { w[index] } else { F::zero() }
}

/// Evaluates a single round polynomial for the sumcheck protocol.
/// Returns evaluations at 0, 1, and x2.
fn eval_round_poly<F: Field + 'static>(
    quad_terms: &[Term<F>],
    active_hand: &[F],
    other_hand: &[F],
    hand: usize,
    eval_point_x2: F,
) -> [F; 3] {
    let one_minus_x2 = F::one() - eval_point_x2;
    let mut ev0 = F::zero();
    let mut ev1 = F::zero();
    let mut ev2 = F::zero();

    let other_hand_idx = 1 - hand;

    for term in quad_terms {
        let is_even = term.h[hand] % 2 == 0;
        let pair_base_idx = term.h[hand] & !1;

        let w0 = vector_ref(active_hand, pair_base_idx);
        let w1 = vector_ref(active_hand, pair_base_idx | 1);
        let other_val = vector_ref(other_hand, term.h[other_hand_idx]);

        let coef = term.k * other_val;
        let wx2 = w0 + eval_point_x2 * (w1 - w0);

        if is_even {
            ev0 += coef * w0;
            ev2 += coef * one_minus_x2 * wx2;
        } else {
            ev1 += coef * w1;
            ev2 += coef * eval_point_x2 * wx2;
        }
    }

    [ev0, ev1, ev2]
}

/// Binds active wires to a challenge point:
/// wires[i] = (1 - challenge) * wires[2*i] + challenge * wires[2*i + 1]
pub fn bind<F: Field>(wires: &mut Vec<F>, challenge: F) {
    let n = wires.len().div_ceil(2);
    let one_minus_c = F::one() - challenge;
    for i in 0..n {
        let w0 = vector_ref(wires, 2 * i);
        let w1 = vector_ref(wires, 2 * i + 1);
        wires[i] = w0 * one_minus_c + w1 * challenge;
    }
    wires.truncate(n);
}

pub fn sumcheck_prove_layer<F: Field + 'static>(
    transcript: &mut Transcript,
    layer_pad: &LayerPad<F>,
    wires: &[F],
    mut quad_terms: Vec<Term<F>>,
    logw: usize,
) -> (SumcheckLayerProof<F>, [Vec<F>; 2], [F; 2]) {
    let mut challenges = [Vec::new(), Vec::new()];
    let mut hp = [Vec::with_capacity(logw), Vec::with_capacity(logw)];

    let x2 = F::sumcheck_eval_points()[2];
    let mut active_wires = [wires.to_vec(), wires.to_vec()];

    for round in 0..logw {
        for hand in 0..2 {
            let other_hand = 1 - hand;
            let evaluations = eval_round_poly(
                &quad_terms,
                &active_wires[hand],
                &active_wires[other_hand],
                hand,
                x2,
            );

            // Pad the polynomial evaluations
            let round_pad = &layer_pad.rounds[round].hp[hand];
            let padded_sumcheck_poly =
                [evaluations[0] - round_pad[0], evaluations[2] - round_pad[1]];

            // Get challenge from transcript
            let challenge = round_poly(transcript, &padded_sumcheck_poly);
            challenges[hand].push(challenge);

            hp[hand].push(SumcheckRoundEvals {
                evals: padded_sumcheck_poly,
            });

            // Fold the active wires with the challenge
            bind(&mut active_wires[hand], challenge);

            // Update quadratic terms for the next round
            let one_minus_c = F::one() - challenge;
            for term in quad_terms.iter_mut() {
                if term.h[hand] % 2 == 0 {
                    term.k *= one_minus_c;
                } else {
                    term.k *= challenge;
                }
                term.h[hand] /= 2;
            }
        }
    }

    let next_claims = [
        vector_ref(&active_wires[0], 0),
        vector_ref(&active_wires[1], 0),
    ];
    let proof_claims = [
        next_claims[0] - layer_pad.claims.c0,
        next_claims[1] - layer_pad.claims.c1,
    ];

    end_layer(transcript, &proof_claims);

    let proof = SumcheckLayerProof {
        hp,
        claims: proof_claims,
    };
    (proof, challenges, next_claims)
}

pub fn sumcheck_prove<F: Field + 'static>(
    transcript: &mut Transcript,
    in_layers: &[Vec<F>],
    circuit_data: &Circuit<F>,
    circuit_pad: &CircuitPad<F>,
) -> (Vec<SumcheckLayerProof<F>>, [F; 2]) {
    let (_copy_challenges, global_challenges) = begin_circuit::<F>(transcript);

    let initial_logv = ceil_lg2(circuit_data.noutput);
    let mut current_logv = initial_logv;
    let mut current_challenges = [
        global_challenges[0..initial_logv].to_vec(),
        global_challenges[0..initial_logv].to_vec(),
    ];

    let mut final_claims = [F::zero(); 2];
    let mut proofs = Vec::with_capacity(circuit_data.layers.len());

    for layer_index in 0..circuit_data.layers.len() {
        let layer = &circuit_data.layers[layer_index];
        let (alpha, beta) = begin_layer(transcript);

        let mut quad_terms = layer.quad.clone();
        bind_g(
            &mut quad_terms,
            current_logv,
            &current_challenges[0],
            &current_challenges[1],
            alpha,
            beta,
        );

        let (proof, next_challenges, next_claims) = sumcheck_prove_layer(
            transcript,
            &circuit_pad.layers[layer_index],
            &in_layers[layer_index],
            quad_terms,
            layer.logw,
        );

        current_logv = layer.logw;
        current_challenges = next_challenges;
        final_claims = next_claims;
        proofs.push(proof);
    }

    (proofs, final_claims)
}
]]></sourcecode>
</section>

<section anchor="generate-constraints-from-the-public-inputs-and-the-padded-proof"><name>Generate constraints from the public inputs and the padded proof</name>
<t>This section defines the procedure <tt>symbolic_sumcheck_verifier_core</tt> for transforming
the proof returned by <tt>sumcheck_prove</tt> into constraints to be checked
by the commitment scheme. Specifically, each layer produces one linear
constraint and one quadratic constraint. One additional linear
constraint is added after processing the input layer.</t>
<t>The main difficulty in describing the algorithm is that it operates
not on concrete witnesses, but on expressions in which the witnesses
are symbolic quantities. Symbolic manipulation is necessary because
the verifier does not have access to the witnesses. In the reference implementation,
symbolic quantities are represented by affine expressions <tt>Expression&lt;F&gt;</tt>
of the form <tt>k + SUM_{i} a[i] * Var(i)</tt> for known constant <tt>k</tt> and coefficients <tt>a[i]</tt>.
<tt>Var(i)</tt> represents the <tt>i</tt>-th variable in the combined witness vector <tt>W</tt>.</t>
<t>Linear constraints are converted into sparse constraint terms <tt>LigeroTerm&lt;F&gt;</tt> representing
<tt>A * W + b = 0</tt>, and quadratic constraints are represented by variable indices <tt>(c0, c1, cr)</tt>
enforcing <tt>W[c0] * W[c1] = W[cr]</tt>.</t>

<sourcecode type="rust"><![CDATA[pub struct ClaimsState<F> {
    pub logv: usize,
    pub claim: [Expression<F>; 2],
    pub hc: [Vec<F>; 2],
}

pub struct SymRes<F> {
    pub a: Vec<LigeroTerm<F>>,
    pub b: Vec<F>,
}

fn constrain_to_be_zero<F: Field>(
    a: &mut Vec<LigeroTerm<F>>,
    b: &mut Vec<F>,
    expr: &Expression<F>,
) {
    let c = b.len();
    for (&witness_idx, &coeff) in &expr.terms {
        a.push(LigeroTerm {
            coeff,
            constraint_idx: c,
            witness_idx,
        });
    }
    b.push(expr.known);
}

pub fn symbolic_sumcheck_round<F: Field + 'static>(
    claim: Expression<F>,
    round_pad: &[usize; 2],
    hp_evals: &[F; 2],
    ts: &mut Transcript,
) -> (Expression<F>, F) {
    let challenge_val = round_poly(ts, hp_evals);
    let lag = lagrange_basis(challenge_val);

    let p0 = Var(round_pad[0]) + hp_evals[0];
    let p2 = Var(round_pad[1]) + hp_evals[1];
    let p1 = claim - p0.clone();

    let next_claim = p0 * lag[0] + p1 * lag[1] + p2 * lag[2];

    (next_claim, challenge_val)
}

fn verify_layer<F: Field + 'static>(
    a: &mut Vec<LigeroTerm<F>>,
    b: &mut Vec<F>,
    claims_state: &mut ClaimsState<F>,
    pad: &LayerPad<usize>,
    clr: &CircuitLayer<F>,
    plr: &SumcheckLayerProof<F>,
    ts: &mut Transcript,
) {
    let (alpha, beta) = begin_layer(ts);
    let mut lchal_hc = [Vec::new(), Vec::new()];

    let mut claim = claims_state.claim[0].clone() + claims_state.claim[1].clone() * alpha;

    for round in 0..clr.logw {
        for hand in 0..2 {
            let hp = &plr.hp[hand][round];
            let round_pad = &pad.rounds[round].hp[hand];
            let (next_claim, challenge_val) =
                symbolic_sumcheck_round(claim, round_pad, &hp.evals, ts);
            claim = next_claim;
            lchal_hc[hand].push(challenge_val);
        }
    }

    let eqq = eval_bound_quad(
        &clr.quad,
        claims_state.logv,
        &claims_state.hc[0],
        &claims_state.hc[1],
        &lchal_hc[0],
        &lchal_hc[1],
        clr.logw,
        alpha,
        beta,
    );

    let prod_expr = (Var(pad.claims.c0) * plr.claims[1]
        + Var(pad.claims.c1) * plr.claims[0]
        + Var(pad.claims.cr)
        + (plr.claims[0] * plr.claims[1]))
        * eqq;

    claim -= prod_expr;

    constrain_to_be_zero(a, b, &claim);

    end_layer(ts, &plr.claims);

    *claims_state = ClaimsState {
        logv: clr.logw,
        claim: [
            Var(pad.claims.c0) + plr.claims[0],
            Var(pad.claims.c1) + plr.claims[1],
        ],
        hc: lchal_hc,
    };
}

fn input_constraint<F: Field>(
    a: &mut Vec<LigeroTerm<F>>,
    b: &mut Vec<F>,
    num_public_inputs: usize,
    num_inputs: usize,
    pub_inputs: &[F],
    claims_logv: usize,
    claims_hc0: &[F],
    claims_hc1: &[F],
    got_expr: Expression<F>,
    alpha: F,
) {
    let mut eq_vec = Vec::with_capacity(num_inputs);
    for i in 0..num_inputs {
        eq_vec.push(eq2(i, claims_logv, claims_hc0, claims_hc1, alpha));
    }

    let mut pub_binding = F::zero();
    for i in 0..num_public_inputs {
        pub_binding += eq_vec[i] * pub_inputs[i];
    }

    let mut mle_expr = Expression::from(pub_binding);
    for w in 0..(num_inputs - num_public_inputs) {
        mle_expr += Var(w) * eq_vec[num_public_inputs + w];
    }

    mle_expr -= got_expr;

    constrain_to_be_zero(a, b, &mle_expr);
}

pub fn symbolic_sumcheck_verifier_core<F: Field + 'static>(
    mut pad_index: usize,
    pub_inputs: &[F],
    circuit_data: &Circuit<F>,
    proof: &[SumcheckLayerProof<F>],
    ts: &mut Transcript,
) -> SymRes<F> {
    let mut a = Vec::new();
    let mut b = Vec::new();

    let num_inputs = circuit_data.ninput;
    let num_public_inputs = circuit_data.npublic_input;

    let logv_output = ceil_lg2(circuit_data.noutput);
    let (_, g_ch) = begin_circuit::<F>(ts);
    let hc_init = g_ch[0..logv_output].to_vec();

    let mut claims_state = ClaimsState {
        logv: logv_output,
        claim: [Expression::zero(), Expression::zero()],
        hc: [hc_init.clone(), hc_init],
    };

    let circuit_pad = CircuitPad::generate_indices(circuit_data, &mut pad_index);

    for ly in 0..circuit_data.layers.len() {
        verify_layer(
            &mut a,
            &mut b,
            &mut claims_state,
            &circuit_pad.layers[ly],
            &circuit_data.layers[ly],
            &proof[ly],
            ts,
        );
    }

    let alpha_input = ts.get_elt_field();
    let got_expr = claims_state.claim[0].clone() + claims_state.claim[1].clone() * alpha_input;

    input_constraint(
        &mut a,
        &mut b,
        num_public_inputs,
        num_inputs,
        pub_inputs,
        claims_state.logv,
        &claims_state.hc[0],
        &claims_state.hc[1],
        got_expr,
        alpha_input,
    );

    SymRes { a, b }
}
]]></sourcecode>
</section>
</section>

<section anchor="serializing-objects"><name>Serializing objects</name>
<t>This section explains how a proof consists of smaller, related objects, and how to serialize each such component.  As in the preceding sections, the procedures are given as excerpts from the reference implementation.</t>
<t>A size, meaning either the length of an array or an index into one, is serialized as exactly four bytes in little endian order:</t>

<sourcecode type="rust"><![CDATA[/// Serializes a size as exactly 4 bytes.
pub fn write_size_4bytes<W: std::io::Write>(writer: &mut W, val: usize) -> std::io::Result<()> {
    writer.write_all(&(val as u32).to_le_bytes())
}

/// Reads a size written by `write_size_4bytes`.
pub fn read_size_4bytes<R: Read>(reader: &mut R) -> std::io::Result<usize> {
    let mut buf = [0u8; 4];
    reader.read_exact(&mut buf)?;
    let val = u32::from_le_bytes(buf);
    Ok(val as usize)
}
]]></sourcecode>
<t>Almost every array in a proof has a length that both parties compute
from the circuit and from the agreed parameters, and such an array is
serialized as its elements in order, with no length prefix.  The two
exceptions are the runs of the opened columns and the Merkle path; both
are preceded by a size, and both are described below.</t>
<t>A digest is serialized as the <tt>HASH_LEN</tt> bytes produced by the hash
function <tt>H</tt> of the chosen profile; for SHA-256, <tt>HASH_LEN</tt> is 32.  The
Merkle column blinding nonces are also <tt>HASH_LEN</tt> bytes each.</t>

<section anchor="serializing-structs"><name>Serializing structs</name>
<t>When a section includes just a struct definition, it is serialized in the natural way, starting from the top-most component and proceeding to the last one, each component is serialized in order.</t>
</section>

<section anchor="serializing-field-elements"><name>Serializing Field elements</name>
<t>This section describes a method to serialize field elements, particularly when the field structure allows efficient encoding for elements of subfields.</t>
<t>Before a field element can be serialized, the context must specify the finite field. In most cases, the Circuit structure will specify the finite field, and all other aspects of the protocol will be defined by this field.</t>
<t>A finite field or <tt>FieldID</tt> is specified using a variable-length encoding. Common finite fields have been assigned special 1-byte codes. An arbitrary prime-order finite field can be specified using the special <tt>0xF_</tt> byte followed by a variable number of bytes to specify the prime in little-endian order. For example, the 3 byte sequence <tt>f10101</tt> specifies F<sub>257</sub>. Similarly, a quadratic extension using the polynomial x^2 + 1 can be specified using the <tt>0xE_</tt> designators.</t>
<table><name>Finite field identifiers.
</name>
<thead>
<tr>
<th>Finite field</th>
<th align="right">FieldID</th>
</tr>
</thead>

<tbody>
<tr>
<td>p256</td>
<td align="right">0x01</td>
</tr>

<tr>
<td>p384</td>
<td align="right">0x02</td>
</tr>

<tr>
<td>p521</td>
<td align="right">0x03</td>
</tr>

<tr>
<td>GF(2<sup>128</sup>)</td>
<td align="right">0x04</td>
</tr>

<tr>
<td>GF(2<sup>16</sup>)</td>
<td align="right">0x05</td>
</tr>

<tr>
<td>2<sup>128</sup> - 2<sup>108</sup> + 1</td>
<td align="right">0x06</td>
</tr>

<tr>
<td>2^64 - 59</td>
<td align="right">0x07</td>
</tr>

<tr>
<td>2^64 - 2^32 + 1</td>
<td align="right">0x08</td>
</tr>

<tr>
<td>F_{2^64 - 59}<sup>2</sup></td>
<td align="right">0x09</td>
</tr>

<tr>
<td>secp256k1</td>
<td align="right">0x0a</td>
</tr>

<tr>
<td>F_{2<sup>{0--15}</sup>-byte prime}<sup>2</sup></td>
<td align="right">0xe{0--f}</td>
</tr>

<tr>
<td>F_{2<sup>{0--15}</sup>-byte prime}</td>
<td align="right">0xf{0--f}</td>
</tr>
</tbody>
</table><t>The GF(2<sup>128</sup>) field uses the irreducible polynomial x<sup>128</sup> + x<sup>7</sup> + x<sup>2</sup> + x + 1.
The p256 prime is equal to 115792089210356248762697446949407573530086143415290314195533631308867097853951, which is the base field used by the NIST P256 elliptic curve.
The p384 prime is equal to 39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319 which is the base field used by the NIST P384 curve.  The p512 prime is equal to 2<sup>521</sup> - 1.  The F_p64^2 field is the quadratic field extension of the base field defined by prime 18446744073709551557 using polynomial x^2 + 1, i.e. by injecting a square root of -1 to the field.</t>

<section anchor="serializing-a-single-field-element"><name>Serializing a single field element</name>
<t>Unless specified otherwise, a field element, referred to as an <tt>Elt</tt>, is serialized to bytes in little-endian order. For example, a 256-bit element of the finite field F<sub>p256</sub> is serialized into 32-bytes starting with the least-significant byte.</t>
<t>An element of field <tt>F</tt> is written as <tt>F::to_bytes()</tt>, which always
produces <tt>F::serialized_size()</tt> bytes, and is read back as follows:</t>

<sourcecode type="rust"><![CDATA[pub fn read_elt_field<F: Field, R: Read>(reader: &mut R) -> std::io::Result<F> {
    let mut buf = vec![0u8; F::serialized_size()];
    reader.read_exact(&mut buf)?;
    F::from_bytes(&buf)
        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
}
]]></sourcecode>
</section>

<section anchor="serializing-an-element-of-a-subfield"><name>Serializing an element of a subfield</name>
<t>In some cases, when both Prover and Verifier can explicitly conclude that a field element belongs to a smaller subfield, then both parties can use a more efficient sub-field serialization method.   This optimization can be used when the larger field <tt>F</tt> is a field extension of a smaller field, and both parties can conclude that the serialized element belongs to the smaller subfield.</t>
<t>Such an element is written as <tt>sf.to_subfield_bytes()</tt>, which produces
<tt>sf.subfield_serialized_size()</tt> bytes, and is read back as follows:</t>

<sourcecode type="rust"><![CDATA[pub fn read_subfield_elt<F: Field + 'static, R: Read>(
    reader: &mut R,
    sf: &F::Subfield,
) -> std::io::Result<F> {
    let size = sf.subfield_serialized_size();
    let mut buf = vec![0u8; size];
    reader.read_exact(&mut buf)?;
    sf.from_subfield_bytes(&buf)
        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))
}
]]></sourcecode>
</section>
</section>

<section anchor="serializing-a-sumcheck-transcript"><name>Serializing a Sumcheck Transcript</name>
<t>The padded sumcheck transcript is the array of <tt>SumcheckLayerProof</tt>
values produced by <tt>sumcheck_prove</tt>, one per circuit layer, written in
layer order:</t>

<sourcecode type="rust"><![CDATA[pub fn write_sumcheck_proof<F: Field, W: Write>(
    io: &mut W,
    sumcheck_proof: &[SumcheckLayerProof<F>],
) -> std::io::Result<()> {
    for ly in sumcheck_proof {
        for r in 0..ly.hp[0].len() {
            io.write_all(&ly.hp[0][r].evals[0].to_bytes())?;
            io.write_all(&ly.hp[1][r].evals[0].to_bytes())?;
            io.write_all(&ly.hp[0][r].evals[1].to_bytes())?;
            io.write_all(&ly.hp[1][r].evals[1].to_bytes())?;
        }
        io.write_all(&ly.claims[0].to_bytes())?;
        io.write_all(&ly.claims[1].to_bytes())?;
    }
    Ok(())
}
]]></sourcecode>
<t>Thus each layer contributes <tt>4 * log_w</tt> field elements followed by the
two padded claims, for a total of <tt>4 * log_w + 2</tt> elements.  Within a
round, the four elements are the evaluations of the left and right
polynomials at the two points <tt>P0</tt> and <tt>P2</tt>, in the order <tt>l(P0)</tt>,
<tt>r(P0)</tt>, <tt>l(P2)</tt>, <tt>r(P2)</tt>.  The padded transcript incorporates the
optimization in which the eval at 1 is omitted and reconstructed from
the expected value of the previous challenge.</t>
<t>Nothing in this structure is length-prefixed: the number of layers and
the value of <tt>log_w</tt> for each layer are given by the circuit, which is
why the corresponding reader, <tt>read_sumcheck_proof</tt>, takes the circuit
as an argument.</t>
</section>

<section anchor="serializing-a-ligero-proof"><name>Serializing a Ligero Proof</name>
<t>A Ligero proof consists of the three response polynomials, the Merkle
column blinding nonces, the opened columns, and the Merkle
authentication path:</t>

<sourcecode type="rust"><![CDATA[pub fn write_ligero_proof<F: Field + 'static, W: Write>(
    io: &mut W,
    cp: &LigeroProof<F>,
    sf: &F::Subfield,
) -> std::io::Result<()> {
    for val in &cp.ldt_poly {
        io.write_all(&val.to_bytes())?;
    }
    for val in &cp.linear_poly {
        io.write_all(&val.to_bytes())?;
    }
    for val in &cp.quad_poly_low {
        io.write_all(&val.to_bytes())?;
    }
    for val in &cp.quad_poly_high {
        io.write_all(&val.to_bytes())?;
    }
    for nonce in &cp.column_nonces {
        io.write_all(nonce)?;
    }

    let req = &cp.queried_columns;
    let mut subfield_run = false;
    let mut i = 0;
    let total_elts = req.len();
    while i < total_elts {
        let mut runlen = 0;
        while i + runlen < total_elts {
            if sf.contains_subfield(req[i + runlen]) == subfield_run {
                runlen += 1;
            } else {
                break;
            }
        }
        write_size_4bytes(io, runlen)?;
        for j in i..(i + runlen) {
            if subfield_run {
                io.write_all(&sf.to_subfield_bytes(req[j]))?;
            } else {
                io.write_all(&req[j].to_bytes())?;
            }
        }
        i += runlen;
        subfield_run = !subfield_run;
    }

    write_size_4bytes(io, cp.merkle_paths.len())?;
    for p in &cp.merkle_paths {
        io.write_all(p)?;
    }
    Ok(())
}
]]></sourcecode>
<t>The lengths of the four polynomial arrays are <tt>BLOCK</tt>, <tt>DBLOCK</tt>,
<tt>NREQ</tt>, and <tt>DBLOCK - BLOCK</tt> respectively, and so none of them is
length-prefixed.  The last two are the non-zero segments of the
quadratic test polynomial; its middle <tt>WR</tt> elements are identically
zero and are not sent.  The <tt>column_nonces</tt> array holds the <tt>NREQ</tt>
Merkle column blinding nonces, one per opened column, each of
<tt>HASH_LEN</tt> bytes.  The opened columns <tt>queried_columns</tt> are the
<tt>NREQ * NROW</tt> field elements of the opened columns, in row-major order.</t>
<t>The concept of a <tt>run</tt> allows saving space when a long run of field
elements belong to a subfield of the Finite field.  Runs consist of a
4-byte size element, and then that many Elt elements that are either in
the field or the subfield. Runs alternate, beginning with full field
elements. In this way, rows that consist of subfield elements can save
space.  The maximum run length is set to 2<sup>25</sup>.</t>
<t>Because the runs strictly alternate and the first run is a full-field
run, the encoder has no way to signal that the opened columns <em>begin</em>
with a subfield element other than by emitting a full-field run of
length zero.  A zero-length run is therefore permitted, but only as the
very first run of the sequence and only once; anywhere else it would be
either redundant, since two adjacent runs of the same kind can always
be merged, or an attempt to encode the same byte string in more than
one way.  A reader <bcp14>MUST</bcp14> reject a zero-length run that is not the
first run, and <bcp14>MUST</bcp14> reject a second consecutive zero-length run.
Both cases occur in practice: the P-256 test vector in
<xref target="test-vectors"></xref> begins with a zero-length run because its first opened
element lies in the subfield, whereas the GF(2<sup>128</sup>) vector begins
directly with a full-field run of all <tt>NREQ * NROW</tt> elements.</t>
<t>Finally, the Merkle authentication path is the one part of a proof
whose length is not determined in advance, since it depends on which
columns were opened; it is therefore preceded by a size.  Each element
of the path is a digest of <tt>HASH_LEN</tt> bytes.</t>
</section>

<section anchor="serializing-a-sequence-of-proofs"><name>Serializing a Sequence of proofs</name>
<t>In general, a proof string carries a sequence of <tt>N</tt> proofs, for <tt>N &gt;= 1</tt>:</t>

<artwork><![CDATA[struct {
   Public pub;        // Public arguments to all circuits
   ZkProof proofs[N]; // one ZkProof per circuit, in the order
                      // given by the circuit identifier
} Proofs;
]]></artwork>
<t>The proofs are concatenated with no separators and no count, because
the circuit identifier names the ordered list of circuits that
together make up the statement, and thus fixes both the number of proofs
<tt>N</tt> and the circuit that each <tt>proofs[i]</tt> belongs to.  The ordered list of circuits makes a proof self-delimiting: it supplies the field,
the number of layers, the value of <tt>log_w</tt> for each layer, and the
Ligero geometry, which determine every length in the two preceding sections.</t>
<t>A reader parses the proofs in order, using the <tt>i</tt>-th circuit to parse
<tt>proofs[i]</tt>.  Because each circuit carries its own <tt>FieldID</tt>, successive
proofs in one string are in general over different fields, and their
field elements differ in width; a reader <bcp14>MUST</bcp14> establish the field
context afresh at each proof boundary rather than assume a single
element size for the whole string.  A reader <bcp14>MUST</bcp14> reject a proof
string that ends before all <tt>N</tt> proofs have been parsed.  If <tt>Proofs</tt> constitutes the entire message, then a reader <bcp14>MUST</bcp14> reject any trailing bytes.</t>
<t>The arguments in <tt>pub</tt> are the public inputs to all <tt>N</tt> circuits and
are not repeated per proof.</t>
<t>Proving one statement with several circuits normally also requires
data that binds the proofs to each other, so that the verifier learns
that the circuits were evaluated on consistent inputs.  Such linking
data is specific to the application and is outside the scope of this
document.</t>
<t>A single proof is the commitment to the witness, followed by the padded
sumcheck transcript, followed by the Ligero proof:</t>

<sourcecode type="rust"><![CDATA[#[derive(Clone, Debug)]
pub struct ZkProof<F> {
    pub root: [u8; 32],
    pub sumcheck_proof: Vec<SumcheckLayerProof<F>>,
    pub ligero_proof: LigeroProof<F>,
}

pub fn write_zk_proof<F: Field + 'static, W: Write>(
    io: &mut W,
    proof: &ZkProof<F>,
    sf: &F::Subfield,
) -> std::io::Result<()> {
    io.write_all(&proof.root)?;
    write_sumcheck_proof(io, &proof.sumcheck_proof)?;
    write_ligero_proof(io, &proof.ligero_proof, sf)?;
    Ok(())
}
]]></sourcecode>
<t>Here <tt>root</tt> is the root of the Merkle tree described in
<xref target="ligero-zk-proof"></xref>, and is <tt>HASH_LEN</tt> bytes long.  The public arguments
to the circuits are carried alongside the proofs rather than inside
them:</t>

<artwork><![CDATA[struct {
  char* arguments[];   // array of strings representing
                       // public arguments to the circuit
} Public;
]]></artwork>
</section>
</section>

<section anchor="security-considerations"><name>Security Considerations</name>
<t>Both the Ligero and Longfellow systems satisfy the standard properties of a zero-knowledge argument system: completeness, soundness, and zero-knowledge.</t>
<t>Frigo and shelat <xref target="longfellow"></xref> provide an analysis of the soundness of the system, as it derives from the Soundness of the Ligero proof system and the sumcheck protocol.  Similarly, the zero-knowledge property derives almost entirely from the analysis of Ligero <xref target="ligero"></xref>.  A mechanically verifiable proof for the soundness
and zero-knowledge properties of the joint scheme is in preparation.</t>
</section>

<section anchor="iana-considerations"><name>IANA Considerations</name>
<t>This document does not make any requests of IANA.</t>
</section>

</middle>

<back>
<references><name>References</name>
<references><name>Normative References</name>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.4086.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6919.xml"/>
</references>
<references><name>Informative References</name>
<reference anchor="GMR" target="">
  <front>
    <title>THE KNOWLEDGE COMPLEXITY OF INTERACTIVE PROOF SYSTEMS</title>
    <author fullname="Shafi Goldwasser" initials="S." surname="Goldwasser"></author>
    <author fullname="Silvio Micali" initials="S." surname="Micali"></author>
    <author fullname="Charles Rackoff" initials="C." surname="Rackoff"></author>
    <date year="1989"></date>
  </front>
</reference>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml3/reference.I-D.draft-irtf-cfrg-fiat-shamir-03.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6234.xml"/>
<reference anchor="additivefft" target="https://arxiv.org/abs/1404.3458">
  <front>
    <title>Novel polynomial basis and its application to Reed-Solomon&#xA;erasure codes</title>
    <author fullname="Sian-Jheng Lin" initials="S." surname="Lin"></author>
    <author fullname="Wei-Ho Chung" initials="W." surname="Chung"></author>
    <author fullname="Yunghsiang S. Han" initials="Y." surname="Han"></author>
    <date year="2014"></date>
  </front>
</reference>
<reference anchor="krs" target="https://eprint.iacr.org/2025/118">
  <front>
    <title>How to Prove False Statements: Practical Attacks on Fiat-Shamir</title>
    <author fullname="Dmitry Khovratovich" initials="D." surname="Khovratovich"></author>
    <author fullname="Ron D. Rothblum" initials="R. D." surname="Rothblum"></author>
    <author fullname="Lev Soukhanov" initials="L." surname="Soukhanov"></author>
    <date year="2025"></date>
  </front>
</reference>
<reference anchor="ligero" target="https://eprint.iacr.org/2022/1608">
  <front>
    <title>Ligero: Lightweight Sublinear Arguments Without a Trusted Setup</title>
    <author fullname="Scott Ames" initials="S." surname="Ames"></author>
    <author fullname="Carmit Hazay" initials="C." surname="Hazay"></author>
    <author fullname="Yuval Ishai" initials="Y." surname="Ishai"></author>
    <author fullname="Muthuramakrishnan Venkitasubramaniam" initials="M." surname="Venkitasubramaniam"></author>
    <date year="2022"></date>
  </front>
</reference>
<reference anchor="longfellow" target="https://eprint.iacr.org/2024/2010">
  <front>
    <title>Anonymous credentials from ECDSA</title>
    <author fullname="Matteo Frigo" initials="M." surname="Frigo"></author>
    <author fullname="abhi shelat" initials="a." surname="shelat"></author>
    <date year="2024"></date>
  </front>
</reference>
<reference anchor="rbr" target="https://eprint.iacr.org/2018/1004">
  <front>
    <title>Fiat-Shamir From Simpler Assumptions</title>
    <author fullname="Ran Canetti" initials="R." surname="Canetti"></author>
    <author fullname="Yilei Chen" initials="Y." surname="Chen"></author>
    <author fullname="Justin Holmgren" initials="J." surname="Holmgren"></author>
    <author fullname="Alex Lombardi" initials="A." surname="Lombardi"></author>
    <author fullname="Guy N. Rothblum" initials="G." surname="Rothblum"></author>
    <author fullname="Ron D. Rothblum" initials="R." surname="Rothblum"></author>
    <date year="2018"></date>
  </front>
</reference>
</references>
</references>

<section anchor="acknowledgements"><name>Acknowledgements</name>
</section>

<section anchor="test-vectors"><name>Test Vectors</name>
<t>This section contains test vectors. Each test vector in specifies the configuration information and inputs. All values are encoded in hexadecimal strings.</t>

<section anchor="test-vectors-for-merkle-tree"><name>Test Vectors for Merkle Tree</name>
<t>These vectors exercise the tree construction and the batch inclusion
proof only.  The leaves below are the leaf digests themselves, so no
nonces are involved; the nonce-blinded leaf construction used by the
Ligero commitment is exercised by the vectors in
<xref target="test-vectors-for-explicit-circuit-sumcheck-and-ligero"></xref>, which list
their nonces explicitly.</t>

<section anchor="vector-1"><name>Vector 1</name>

<ul spacing="compact">
<li>Leaves:
4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a
dbc1b4c900ffe48d575b5da5c638040125f65db0fe3e24494b76ea986457d986
084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5
e52d9c508c502347344d8c07ad91cbd6068afc75ff6292f062a09ca381c89e71
e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db</li>
<li>Root: f22f4501ffd3bdffcecc9e4cd6828a4479aeedd6aa484eb7c1f808ccf71c6e76</li>
<li>Proof for leaves (0,1):
084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5
f03808f5b8088c61286d505e8e93aa378991d9889ae2d874433ca06acabcd493</li>
<li>Proof for leaves (1,3):
e77b9a9ae9e30b0dbdb6f510a264ef9de781501d7b6b92ae89eb059c5ab743db
084fed08b978af4d7d196a7446a86b58009e636b611db16211b65a9aadff29c5
4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a</li>
</ul>
</section>
</section>

<section anchor="test-vectors-for-fiat-shamir"><name>Test Vectors for Fiat-Shamir</name>
<t>Let <tt>p=115792089237316195423570985008687907853269984665640564039457584007908834671663</tt> and <tt>Fp</tt> be the 4-word field defined by <tt>p</tt>.</t>

<section anchor="vector-1-writebytes"><name>Vector 1: WriteBytes</name>

<ul spacing="compact">
<li><t>Description: Using <tt>Fp</tt>, the test steps are to (a) initialize the transcript object with the 4-byte string <tt>test</tt>; (b) write an array of bytes of size 100 that contains the integers 0, 1, 2, ..., 99;  (c) generate 16 field elements:</t>

<ul spacing="compact">
<li>0x8b297f0bffd583c6c6b6796385d5fd20a08665733b833970ebdd1054bbbc1b14</li>
<li>0x0667c08ad7f38efec5f30dc8aa4f20d749cdcf96d63a770f9810ac5c0ca8dcb1</li>
<li>0xc8037fc12d4da00b5dc7597e3042f33f72a06f970cb71fb6b103ebb5419d8a6b</li>
<li>0xfbbcfa1eac48728fbfdacc1c21e2f78119457e0846337e46140e38e62856c4c5</li>
<li>0x5358ae603691cc759faeb572fb6642654ea1c3dbc8f81d00276dd8c4df95aa58</li>
<li>0x5266158c3c895dede5a23b6ce85a9f564b8059ebfcd1741f54497ec58189873e</li>
<li>0x3ecea4b2343c007fc32f2aff40dc7320945f101ecae5d52494db21ad326e9739</li>
<li>0x6462dd575e6b874118607212feec7ce5417ae3bf0f2e86604596f35d48bbaea2</li>
<li>0x6d56c703c369edea3595db6b958241580ae9b4a76fead961413ed9e9e5852dcd</li>
<li>0x6d31073cee650212a71b7b13e9f951e00ef3b14a008a79dd95047b26a4a83d06</li>
<li>0x1b9e2a6666da63c43e52227d91a8a7f0bd5311f63c2e3a18839133375639e6cb</li>
<li>0x332ea49dd23dd4745631ecbb15696192b1fa127256baf7a0483fd27db6f09a48</li>
<li>0x43e735927ccbdc4d5ce912675d638d6d3dc8eef3def34504304e938846f157d6</li>
<li>0xdc4a8868ae75e733a7257a8589230392a98d78594836dfccd01304742b5b3ad5</li>
<li>0x976353931711c634f2691e507b119fd7f6e653d419a2620676122db08db18765</li>
<li>0x332729ab436dca654866a9382deaee0add6fb7e90a80261f1488e56598e8bc99</li>
</ul></li>
</ul>
</section>

<section anchor="vector-2-writefieldelement"><name>Vector 2: WriteFieldElement</name>

<ul>
<li><t>Starting from the state at the end of Vector 1,
(a) write the field element '7' in Fp;
(b) generate 16 field elements</t>

<ul spacing="compact">
<li>0x609db3e9a8f548df038519fa46cef23eb8c6553d3c1f698604e60a51613a738e</li>
<li>0x1cb69cb31999eb88e83c7586aac53f5e3286b084b0cf9e43619b48df01e0a310</li>
<li>0x3bf36e3ddc690a1b12b417628c115959b373d056c90c42dc2417baf46f538868</li>
<li>0xe336594f29dcda52e48896517b5cdb2d062ffd861ab02db5f8ca197aacc635f6</li>
<li>0xc1f396a8bad16bb0f57da6d380402a25b571bd4691226d11449a741440e325c8</li>
<li>0x5195336ec73751de066e3a8939b40c3c5555f1a513486dfc50dcf4c2d47e6ff2</li>
<li>0x8dcf872f3ded2b7ed1d1ee9a2b125bedc6eacd3c09b3a4a5286d8fc2fc3a6634</li>
<li>0x950dd2ef7be25eab686a6688497962ee4ad521da12b9ff3d8e56ad9435885b12</li>
<li>0xe14389d1d8448678cac33fdbc9aab20dba019e75149d170dd2f353891cd4b84f</li>
<li>0xe84906c09cd6423865baf64e48027cc598d52bdb90b17524c87ea892e53b5200</li>
<li>0x493cea587f1ec5622c04221cd6e5a41c26c1c1c24c0375f7aaa367d9678d83bc</li>
<li>0x5aca0010aced30bcb3b84a7f10ea39c4269ab7c92fcb6cff52958d8921ef2cc5</li>
<li>0x4498fa8340f41467c0fa813bd0ca83ef6e1c4b85c7b1168a94339fd9e8296139</li>
<li>0xf9a95b738a8e775421b1baa503abbeed2d283b236ebba25e1954b3c993d30a3d</li>
<li>0x98178711d03a0b1204ebb56b37bd3a2724dfb08e4dc925609391768b126d21f2</li>
<li>0x79251f49534f5c4b10b798b2dbf6e80a3b07593f616ce6a9617ccc61040aac78</li>
</ul></li>
</ul>
</section>

<section anchor="vector-3-writefieldelementarray"><name>Vector 3: WriteFieldElementArray</name>

<ul spacing="compact">
<li>Starting from the state at the end of Vector 2,
(a) write the 2-long array of field elements <tt>[8, 9]</tt>;
(b) generate 16 field elements:</li>
</ul>

<artwork><![CDATA[0xae1a921288590205fc24543303ff527476359b8db4a983b2886a133b02f3217e
0x8c5d52a04b295f9fdb45ab66100fa00ca32c9634aa87cbbdb2bc3e1912459feb
0x12f82963b5b242156f6e9eb756eddee7652b60c7d6394403f7bd995e0b9bcd9c
0x880aa50b049b3939055deb7933749d338bb3fb5f64a9adf95019e6cfc232995c
0xf8558f693f0fa6df20a37147a898fb4c678831f566d80113bbe2cdcd18285da2
0xbbcc8d9b46f88bc8c6cec0ad2d5e49508b7db91d548548eddc61800de1329e1c
0x479a17244398caae8155a73438a22583df7de10a8a2e12ad53ddd3bc7305fac9
0x9ba1917f1227932250288a843f64b4e7b7f47a5fbc16c111f6e1f76235ccf38c
0xd1582138045d1636fb7f677c9e8a4a4143ce2b2bb54fb4f49fb0ad1fee5df6b4
0x05331e5b8508f79c017a8dfbbb805f3f8c5e3e4bc417e44849b9212439646331
0xb6b95862194ca52dcaa9ee651b7fc5b708f43feae108bb9a7f95213f4d069048
0xe86b1602f0a54c4e237867ebaf05e7581464fd238e50f6ed9c3cea63909c8e60
0xb7280439f3b21b113ff29cefe39292d5e2d137709c3d3cec36473a0f97a24e62
0xbeaa5e08257d232506fb3e46c6daa29e0859c34c7d0cd673bc6706ee261ae059
0x0691ead55728cd087a1952b22b6628ba4e26fbefc8debeec5e6fbc3a16f637be
0x47dc31f6d8bc9c44290781176df3e4b95ac8793a4a42fa5859c564d92d6d5af5
]]></artwork>
</section>

<section anchor="vector-4-nat"><name>Vector 4: Nat</name>

<ul spacing="compact">
<li><t>Starting from the state at the end of Vector 3,</t>

<ul spacing="compact">
<li>(a) write the 4-byte string &quot;nats&quot;;</li>
<li><t>(b) call <tt>nat(n)</tt> with the following list of parameters:</t>

<ul spacing="compact">
<li><tt>[1, 1, 1, 2, 2, 2, 7, 7, 7, 7, 32, 32, 32, 32, 256, 256, 256, 256, 1000, 10000, 60000, 65535, 100000, 100000]</tt>.</li>
</ul></li>
<li><t>The result of each call corresponds to:</t>

<ul spacing="compact">
<li><tt>[0, 0, 0, 0, 0, 0, 3, 0, 4, 5, 10, 30, 27, 22, 100, 189, 3, 92, 999, 3105, 40886, 51590, 56367, 10678]</tt></li>
</ul></li>
</ul></li>
</ul>
</section>

<section anchor="vector-5-choice"><name>Vector 5: Choice</name>

<ul spacing="compact">
<li><t>Starting from the state at the end of Vector 4, (a) write the 6-byte string &quot;choose&quot;; (b) call <tt>choose(m, 20)</tt> for the following values of <tt>m</tt>:</t>

<ul spacing="compact">
<li><tt>m=31, k=20</tt>: <tt>[10, 29, 30, 11, 4, 15, 16, 28, 19, 21, 25, 18, 17, 3, 5, 23, 24, 22, 6, 1]</tt></li>
<li><tt>m=32, k=20</tt>: <tt>[3, 17, 18, 8, 30, 7, 14, 19, 25, 23, 12, 4, 31, 16, 0, 6, 20, 27, 11, 10]</tt></li>
<li><tt>m=63, k=20</tt>: <tt>[9, 56, 61, 45, 35, 53, 51, 3, 39, 32, 31, 6, 59, 58, 54, 22, 27, 62, 55, 19]</tt></li>
<li><tt>m=64, k=20</tt>: <tt>[12, 52, 39, 17, 51, 38, 58, 2, 28, 27, 46, 63, 61, 50, 40, 55, 47, 13, 56, 32]</tt></li>
<li><tt>m=1000, k=20</tt>: <tt>[157, 668, 572, 138, 913, 994, 797, 249, 440, 723, 489, 241, 383, 108, 710, 341, 406, 585, 42, 692]</tt></li>
<li><tt>m=65535, k=20</tt>: <tt>[40745, 48408, 17108, 44500, 53993, 10008, 24910, 52200, 61265, 54989, 41237, 25958, 28697, 61187, 34729, 3525, 9005, 38627, 9724, 12169]</tt></li>
</ul></li>
</ul>
</section>
</section>

<section anchor="test-vectors-for-explicit-circuit-sumcheck-and-ligero"><name>Test Vectors for Explicit Circuit, Sumcheck, and Ligero</name>
<t>The following test vectors verify the complete zero-knowledge proof system using explicitly constructed layered quadratic circuits without requiring external binary circuit files or formats.</t>

<section anchor="randomness-used-by-these-vectors"><name>Randomness used by these vectors</name>
<t>A Ligero commitment consumes randomness for the three blinding rows,
for the <tt>NREQ</tt> random elements that prefix every witness and quadratic
row, and for the Merkle leaf nonces.  A proof is therefore reproducible
only with respect to a fixed source of randomness.  Both vectors below
draw from the following deterministic generator, seeded with <tt>12345</tt>,
which is defined for test purposes only and <bcp14>MUST NOT</bcp14> be used to
produce real proofs:</t>

<sourcecode type="rust"><![CDATA[pub struct SimpleRng {
    pub state: u64,
}

impl Rng for SimpleRng {
    fn bytes(&mut self, len: usize) -> Vec<u8> {
        let mut out = Vec::with_capacity(len);
        let mut state = self.state;
        for _ in 0..len {
            state = state
                .wrapping_mul(6364136223846793005)
                .wrapping_add(1442695040888963407);
            out.push(((state >> 32) & 0xff) as u8);
        }
        self.state = state;
        out
    }
}
]]></sourcecode>
<t>The <tt>NREQ</tt> Merkle nonces of the opened columns are reproduced below for
each vector, so that an implementation can check its commitment
independently of the generator above.  They also appear verbatim in the
complete proof serialization, immediately after the four response
polynomials and immediately before the run-length coded columns.</t>
</section>

<section anchor="vector-1-s-gonal-circuit-over-p-256-field-id-2"><name>Vector 1: <tt>s</tt>-gonal Circuit over P-256 (Field ID 2)</name>

<ul>
<li><strong>Description</strong>: Circuit <tt>C(n, m, s) = 0 \iff (s - 2)m^2 - (s - 4)m - 2n = 0</tt> verifying that <tt>n</tt> is the <tt>m</tt>-th <tt>s</tt>-gonal polygonal number over the NIST P-256 scalar field <tt>\mathbb{F}_p</tt>.</li>
<li><strong>Field</strong>: P-256 Scalar Field (Field ID 2, order <tt>p = 115792089210356248762697446949407573530086143415290314195533631308867097853951</tt>)</li>
<li><strong>Witness Inputs</strong>: <tt>W = [w_0, w_1, w_2, w_3] = [1, 45, 5, 6]</tt> with public input <tt>w_0 = 1</tt>, and private inputs <tt>n = 45</tt>, <tt>m = 5</tt>, <tt>s = 6</tt>.</li>
<li><t><strong>Explicit Circuit Definition</strong>:</t>

<ul spacing="compact">
<li><strong>Inputs</strong>: <tt>N_{in} = 4</tt>, <tt>N_{pub} = 1</tt>.</li>
<li><t><strong>Layer 1</strong> (Intermediate wires <tt>U = [u_0, u_1, u_2, u_3, u_4]</tt> from input <tt>W</tt>):</t>

<ul spacing="compact">
<li><tt>u_0 = 1 = w_0 \cdot w_0</tt></li>
<li><tt>u_1 = m^2 = w_2 \cdot w_2</tt></li>
<li><tt>u_2 = s - 2 = w_3 \cdot w_0 - 2 \cdot w_0 \cdot w_0</tt></li>
<li><tt>u_3 = (s - 4)m = w_3 \cdot w_2 - 4 \cdot w_0 \cdot w_2</tt></li>
<li><tt>u_4 = 2n = 2 \cdot w_0 \cdot w_1</tt></li>
</ul></li>
<li><t><strong>Layer 0</strong> (Output wire <tt>y_0 = 0</tt> from intermediate <tt>U</tt>):</t>

<ul spacing="compact">
<li><tt>y_0 = u_2 \cdot u_1 - u_3 \cdot u_0 - u_4 \cdot u_0 = (s-2)m^2 - (s-4)m - 2n = 0</tt></li>
</ul></li>
</ul></li>
<li><t><strong>Proof System Parameters</strong>:</t>

<ul spacing="compact">
<li><tt>RATE_INV</tt>: 4 (<tt>\rho = 1/4</tt>)</li>
<li><tt>NREQ</tt>: 6 (query count)</li>
<li><tt>NCOL</tt>: 64 (encoded codeword length)</li>
<li>Derived geometry: <tt>BLOCK</tt> = 10, <tt>DBLOCK</tt> = 19, <tt>WR</tt> = 4, <tt>NW</tt> = 29,
<tt>NQ</tt> = 2, <tt>NWROW</tt> = 8, <tt>NQT</tt> = 1, <tt>NROW</tt> = 14</li>
</ul></li>
<li><strong>Statement hash</strong>: the 23-byte ASCII string <tt>sgonal_test_vector_p256</tt></li>
<li><strong>RNG seed</strong>: 12345</li>
<li><t><strong>Merkle nonces</strong> (one per opened column, in the order the columns
were chosen):</t>

<ul spacing="compact">
<li><tt>d406ef8c46fd239545520a5363d67d61bad1750d37b087c1e3611a12d47e477f</tt></li>
<li><tt>5fe986cae604828cae4bfbbc7415d64cd5e755848755a0d9d3120ad46eea8d86</tt></li>
<li><tt>0fa8593d45fef897767ce05ec5feac76d8f21910bc0ada856aeaa777629048ef</tt></li>
<li><tt>63619b9a06c668a2414e46f859a2fdb1ba63e8718fa6ce4bf532b2fb0b8ff4ef</tt></li>
<li><tt>c9e35514c0ff4b62cb9730116947bef69325f3a4c1c6553b7bd161754f8008d8</tt></li>
<li><tt>057ec47e747c61954419b634dec16eb6adfeafb1a0e9fafbb2a9c85fcf9bced4</tt></li>
</ul></li>
<li><strong>Commitment Root</strong>:
<tt>803aba51698a4bc4dddaa74b1d9971b8ec7c49c4847a7ff18e41dd476edf9b04</tt></li>
<li><strong>Sumcheck Proof Size</strong>: 768 bytes</li>
<li><t><strong>Sumcheck Proof (hex)</strong>:</t>

<sourcecode type="txt"><![CDATA[ed0282f97ed9c5be6ced2c1092a34579d9786d1de4dc5404c4d3435f84bbc1a0
ea352432ab6ad5b2e46e1b126c18103748824fb252ac17406898c6c27d70fb3b
87b96f0d4c7992b03e160e17b0c98efba101daca039575c793b6bedd4dd10541
e2d6701be3e75c019a63fa1595741b3e25c7e111014a4dd792bb47e6aec01aa3
9da1d23e9ac2386889efe3a024a6622bede3f862a41d8267dadbfb491901a6e6
845b0503dab880fc756684f4bf3e71acf1eacb596aa6bc9364eaa0e2c3093dfe
bde19129e45b77c16d27405766a2bd5db5203ab0c116a253127f666354b1cf97
c71fa6fc3f90016fa778aea298eaf379f34b043b5da3bd161fec2530eddbfb0e
247e4e615f4469abd04fa996ecf232e6fde51763eacb87dacc10b5b2aabf9979
0c745c33d07537d414be750e6c0dedc14b72b4e5e86be92a7aa87606ad2e8d01
d1b1306672a1ab21cd6f676272baadfac7b2fb453daef8415e2f38128f833f6c
7b8579a9a4c6f1cc6517bba16e904726ed462896a797dae48f817b81be630a95
629f60d183eedd9a4c433ef2f0a4a4294ad5690f2c07b3385e3f2e73fb2abbe3
e75148bc1051abd7ce21c46f42517911c2c790cc2101ee85fa50620b644f12b8
fc0c9f8522fecaee7c284b7e7006c88df35d1c4f6bae0268751507872f47780c
4bfe833a7a0b6f4f403d0d1b58bf891a48083a78ac5e473f7e012b388fe2f443
3099d256c6b153cfc422d6f48f6229a7107ca4d5dda7d7ffc033ef3ba2269179
089e4889f379d1f97f03b3f0b3206245781dd6841f820fd931ad42fd8a1f4407
7dbd42f7ea2d3c4d5ecc85e5688c2a5f67abe0a456390b85f9fc2d13a5ca8640
23e611c6b1ba75967bcbb2f2a8c142a7044e9c54b96a09981fe5b674815e9fd7
d8a8d76169e11800edceb9928ec62feb70f0e3593fed2ab0d545f29b1d2ea20c
fd8320f828609d5e45cf0dff488401e170d2945ed7559c49f5de2bc7f063f4d6
3d400922fd141c40d4a74abf045e1b7af4b50ea1fe5fb215cdd286e4ba0e9708
bde5320999b8682f9ef6c05744395290815697ca993103f0c0a7704730a05212
]]></sourcecode>
</li>
<li><t><strong>Ligero Proof Size</strong>: 4652 bytes</t>
</li>
<li><t><strong>Total ZK Proof Size</strong>: 5452 bytes</t>
</li>
<li><t><strong>Complete Proof Serialization (hex)</strong>:</t>

<sourcecode type="txt"><![CDATA[803aba51698a4bc4dddaa74b1d9971b8ec7c49c4847a7ff18e41dd476edf9b04
ed0282f97ed9c5be6ced2c1092a34579d9786d1de4dc5404c4d3435f84bbc1a0
ea352432ab6ad5b2e46e1b126c18103748824fb252ac17406898c6c27d70fb3b
87b96f0d4c7992b03e160e17b0c98efba101daca039575c793b6bedd4dd10541
e2d6701be3e75c019a63fa1595741b3e25c7e111014a4dd792bb47e6aec01aa3
9da1d23e9ac2386889efe3a024a6622bede3f862a41d8267dadbfb491901a6e6
845b0503dab880fc756684f4bf3e71acf1eacb596aa6bc9364eaa0e2c3093dfe
bde19129e45b77c16d27405766a2bd5db5203ab0c116a253127f666354b1cf97
c71fa6fc3f90016fa778aea298eaf379f34b043b5da3bd161fec2530eddbfb0e
247e4e615f4469abd04fa996ecf232e6fde51763eacb87dacc10b5b2aabf9979
0c745c33d07537d414be750e6c0dedc14b72b4e5e86be92a7aa87606ad2e8d01
d1b1306672a1ab21cd6f676272baadfac7b2fb453daef8415e2f38128f833f6c
7b8579a9a4c6f1cc6517bba16e904726ed462896a797dae48f817b81be630a95
629f60d183eedd9a4c433ef2f0a4a4294ad5690f2c07b3385e3f2e73fb2abbe3
e75148bc1051abd7ce21c46f42517911c2c790cc2101ee85fa50620b644f12b8
fc0c9f8522fecaee7c284b7e7006c88df35d1c4f6bae0268751507872f47780c
4bfe833a7a0b6f4f403d0d1b58bf891a48083a78ac5e473f7e012b388fe2f443
3099d256c6b153cfc422d6f48f6229a7107ca4d5dda7d7ffc033ef3ba2269179
089e4889f379d1f97f03b3f0b3206245781dd6841f820fd931ad42fd8a1f4407
7dbd42f7ea2d3c4d5ecc85e5688c2a5f67abe0a456390b85f9fc2d13a5ca8640
23e611c6b1ba75967bcbb2f2a8c142a7044e9c54b96a09981fe5b674815e9fd7
d8a8d76169e11800edceb9928ec62feb70f0e3593fed2ab0d545f29b1d2ea20c
fd8320f828609d5e45cf0dff488401e170d2945ed7559c49f5de2bc7f063f4d6
3d400922fd141c40d4a74abf045e1b7af4b50ea1fe5fb215cdd286e4ba0e9708
bde5320999b8682f9ef6c05744395290815697ca993103f0c0a7704730a05212
76b5b1ff2c0f08b364e0c90463874b7a76aead588a0578abd97995a7acc018fa
a3616f94202b67601ace16cfa22118b6d3c358a9602db7b4c41cf3cad18f0c45
016e975539c3308d01c6398983c249a7d7ef16b56c075d2e5768ce4548dcc4e8
9a69487862c72b530ad0c86d0254b5324e20a9fbbe424505cf354e3e8ef74eca
ca42ce218f8ebddeaf17a46e31016bff10c73496d511ca71d3ee15fdbb7a3904
bfae54b1581beb69cc2b0c890fbb74baa431eec4cef4ad02df96f82bd5ac46c4
e26c97007a5377b096adaf4679a39b6322eefeaf4d2a596964457f7e40d658af
1a489d73d0f0d9aebd01703f10423f096f352b2eceba178e81e9917eaa5f1df0
fc7b7dff769848968a09789ab02a54b6c1176e34a9a8f1da2cf19ae2c37d05a5
42407401e53e23a3c2f1c0427578f71933f335ef419a3804338629c5b05220ff
077d527eeb62fa14e6410ddd2bfd704eabf68ae2eff1c3df789acd0b651dddfa
7569c524daad94e3b0c16c5bfe9ae509c138e1c6164c18b17047b6aa08d4b490
b227ebe71d884fd6b6c64b3aedf6a184e8f2364ce58365210b22f9be3527f219
e97845a53f0d975f26e97125bb2125223f9a86202ac62ca3c4e650295d2eda82
4aaee83318308714b3c37ba11b7f4c92c1a53eb9279c473610e2ce9dacf55993
f1cb794852fe80c1be4f203c82422826a72eb7ce93a806f709d998504f785cbf
cce45c56c3ee3154074e99a3c2e53fea547119ea179ec7d0bfe784556b25d7d6
e56fe095b33f2d05e5ac1b8c3fd581811c57648bbef03dad260649070a2446c8
592f3e7693a2f1e9ee86473788840517e38d6bc99e2919b3994a6b6a35efb0b6
a37225353d4663a61636cfcd81ae2164027586165b804d3ff9e818774b6dce75
b32aa849cc964bdaef98457cf2abf90544087d3eaf9dff4897285934cfb6ce49
298d4479739de6f80581c0398ba30cffed7b093c3bc2074b558059a7db87ade7
6ad0348c4ec12499b8d0d3e03a354d27eb901485cea2b58edf01e0859e694c70
4bb12f2d28227166b476d676f009e4e302fda4ca5ec0692dede769ae800be3e6
14f3f5ff2552fc2528c94b24fcf74f8b61e0ea76903dca73afb62b35b9fac7dc
0bcaad0c17267683aac5e823cd6dbd796f3210dea30491d5bd2d8dd6daabda79
213dc3956200cacee1dc8319a3de252f3fb8df6079cc2f4be51c873be5f66681
03c9d8a4a5c505bcdd14174eeb1f3c82c156d2d3bc57f68501b9d5ba5af10372
797b1a93d738c013c0625833861ffdfaf7a9b0e00f8a33a41f87d72100f75b4b
70e8ded7b9a0f44f954915faf6bfc721b367698abcc6b33be195e2a99a326997
af0568a9f0e5b65984ec416b715d9f2b2b9ad742eb051fd7c29d0f1c955050b5
c38dd1c85c09b1e080ff0dc9c8f10de0d86d07ddbb98267f3c3bf27ae62bac92
2a62ecbe8c8e1a78b1c19ab25dbf95c52ac077a2610063ac19382d149a18e54a
b3b7a843250a186d8540184df41bd5a6be7e3660ef28bba8bb9f4407dbd3b638
a30e4a18c7f60404ec06872065179fc55a1195ced24ef2b3798a28acbfbc320a
c4d8b1850e6bfb3d3a5730d29bd3f93d6530cccc5f669a058bf14590ce383f60
93a386a01cfa72cc429a3d127939fbb73778a8ce92202c29ccdb61fedef35cb7
d6cbc98d559f81afd81a472dc8cfbf47074731f18ee4b8871ec5c7508328ad16
319c11de3dd35de85e9000a8c8dd77947a60221cc16b8278d4b03bf6858c252c
1c19b7fbb432c39fe6db2f04bc7e7ffbda7afef966eaffc1a10e271f688c6f5f
c95328f94196dff3a42b5e7c4960b5e457aa322afeaddf2dd4cc756e7379bf32
66faf76c4153d4bfbef3fcd2fad88b1e7e7d78246cbfa96b7f9edbedf02d210d
c247ed0fd14218243eb3c6cd0866cd2cd49f84a567c780b1d15ad44e5ecb916b
0819bb8d57ac002a7b007a35c4fdd3fa48694131a637482f781666a3d69c5010
d406ef8c46fd239545520a5363d67d61bad1750d37b087c1e3611a12d47e477f
5fe986cae604828cae4bfbbc7415d64cd5e755848755a0d9d3120ad46eea8d86
0fa8593d45fef897767ce05ec5feac76d8f21910bc0ada856aeaa777629048ef
63619b9a06c668a2414e46f859a2fdb1ba63e8718fa6ce4bf532b2fb0b8ff4ef
c9e35514c0ff4b62cb9730116947bef69325f3a4c1c6553b7bd161754f8008d8
057ec47e747c61954419b634dec16eb6adfeafb1a0e9fafbb2a9c85fcf9bced4
00000000540000002b520d64d1a48758b4e6b56b93cda7b71767e41aa21bc5a4
02f968032a5d990b35d916cce85bc34911ea5f3413ae54313eb9ac0de227c114
4dfc0835d6ac56fb37cda5179524c5390e72928bf48c4d7f2c6ead37d9cbf9b5
3f4219ed6ba49b8620a6a6ea318ea301b442a8be1c6198b0950ff8e748772dd2
08758a9b6d4ada5bb54472abd5aef2cf83f64227b8e8528f6883130523388ac2
49501c5678fce39ee6098cf92e675f98ca0fd7f16d83f18879a6a54196a2bf0c
c8222d791e9870ab8444ab45f7abf52736bd00b8e1583f31faf872d474aab750
3100adaa4eabed15dd194b4ce7576b58e33ca6c044b104545764d9af7b7708cb
8a9d7d2eb82564c24b9ec487aca878ac6f243cf89dccff327fa5c61698a0f14f
33ddfadccd7748482c72c5234b5a2663256ac36644692aaeab1038b87c40be58
2d28d495f9042be70a724e89c5462323ca1dca74273a6f2bd8f8b1f51c7da4f0
f66732d4040d29c916ac01b6e1f7cd411a8783caf37f0b8fc60c330c6fe1377f
956273ebd37c6aa2acb55ed76b7589c4e4ab006085057f301369f9b4407745ec
25420dad03bc2c134efe1ad4dd9ef9f03a617f79c69a4d90e44c2ccf48ac1ea1
f7450fe7b1603ec96e70400ec7ed1d645e13161a9c42a7aee7922a3910d9c7a4
a2375849375f8c408d7ce8eedc7fd61082f91483d4ae597530423109fae058b9
0f43f63f397e1afe4ad76b9d73be712c9ce50c5feaa4ccff48f9053f130a04ce
e5121781f4ca524e9bc381bf627dcac1aabb1cb6781b4510874f60a8e1c18801
6e6b49f67827c9512b3f2b3d412b35b3e371e6f40014d975f54c54e0f404d6cb
999196504009a891b0f2710df5054a387cdc5cb7bb147f154e1d335197824102
34e6e1f001e511e3896b61ac9e75d111da33a0c3c91414f37ba8b169809e7354
53e9698f643af6f1b76feb3f50f8e65f42e8803d2f128e36928c021a8a8e8411
a9f02d36efc09a1f71cee349a36dc6162254eb04210eb08cae8e955a5720891f
d89f5b7bd61c0e0bf62471a70e8f8ca8addde0aba195388d60452fa98ec72d5a
a4751a3a820db5e869cc80f2f9a62d18f7367d1c1ad339dfd5760dc9c11b244a
52d7275349a721d80939f2143bed6891a68b950946453c7efb8c885974e6e250
9b63aa59278e30b78659f736c5a6f84e9c0e5cfb04de3a06a35e3be04e7d9bf8
5ee025cf5b4ed1a5038ab30e6d304245c6d5573a3ead0da8de3375985a8c32ca
06658532f827ca216d8d24c4ce16ae39361b877bf4cd0994394144bb8e52737e
d70732eea60cf5aec5f474bed7baa5124ec1e454c818da069aee55b9e36b4680
7b460d5ee2684d650c3ebbe7d5425186e6a63ef2497b4e9028976991850310fa
ab526bf6a3f0c9800c655aa5bff7cabc560f82d4e885c496d3111025215121ad
ff4a55487783399b76d3a0e7376fede3865e3558f219d82fc60207d921549c85
8dfc8c08fddb61e51c776e9650ef8c757f9d52c64793e3b30c87e18bdf0de670
ddfdfe3785026ea7acabc069bca44ee228d707a03d78d0ef64a1a5c9bf5a66d9
a5fcc8168954fb699cd74f4568007e9d7d147ea2cabe9e85e00425f60dc89530
9529d6251a295d883cae2f92cc200a5bcd6616d8125517d95366ce99e151d51a
3b43784537c55ca470dc003aa92f72a706d9558711fe3cce3603f40d90d041cf
58b8f3b93d2079b55104ccf9694dcc2dc7b38cd7b3395db8a0b6e570c6f7fbad
44d44111ef8cbc38c66a26571835f13d66eabd68de56a707508e850b1d8057d3
bb0389833dd4b0060c0437a75418bcea0ad4d0907c22ebac99ea69f04015635f
fd217672b5531e2d6484572c4093462c7464f9af152fabe10b8515a160750e24
6302e640d837778443985dabcab9a19e9ebfbabb1d511f8fbd1ce7d163c6bc85
513a3954876f064d855de13479202a87c513db6f43efcf3fe734c740606c1aa4
3d5c42a3807a8c06e25c4952bbb951b4d58269d67016e301b8660cae3bf4ebe5
56cbc7ba99b0b020cb52faffffc9ba2f9cd6d88e91cabbed8f9c325785b98851
13198b52bd28324c1f332b82accf2055642c4af0cae07c1bf46f9cafecd81d1a
efe8e3b777f19ecdd3a6e921faae6e1d8e20764e12dc1d02917ae0dfeb1cdad1
07c1744b980c9405d8d3ff53869e77c1e299d419216ad68894fcee7e4328b3b3
47abd813f9a833f32cf932591570bebfe056b5b98ffb614e576192a4d4af8b74
0216d833d7334eb9eab0334c635f86c681d6c9c9327f179963b6982b1853eb50
e7d57b760c190735ccd5835d669b10fb5ac684d3086abb77b5252aff7d2b7f80
8561a7a54af2573e605ecf373e1f3d5525b83dc45bf33a8cc79fd3114c1d4d94
395f0e9611ae3021839f6f81c072ef6b6044e735c5f269a23686f9d566a26782
c12bf5db4660330f123a54d70aa6e55d6293da388a73ecf15f6693cd86dd8352
5003b1cdd22bf5af0bb25ea5ded5c5bff67a06284c9236883eb6aa4b8ea0d94c
588a777b54da4bade065be29ce4d1c1a91d24624562f20cd7e6b4ec25fc0699c
2cc4c815b8c3bd973bb93d0811afd518e7db3c94ef99331a0eab493f88c47f14
c47dde5b647628baa1743fffba43b69fa5a7004ed8b061d0bf599cd7a880d204
85b51c51bd3de82f01123357546eddfdd002223cfb33f23e97b9df42a04379be
4447b2a6c30eaa849e9179e334561b05c83d9a489cc5b4e4ee00c5668a89631c
6cd1b5add22e5fdf680b4755841fb5e1e806b7cc6709d94e3d9fe184388f17f2
6a947d58a95881c73bf7ec078cb3c6c8bee6b48e898da3dade41a871977e54a1
047038edb550b7eadd781888e15d7796940d0999c3104bdebbfbbe716c584f3b
d31326de132ec74bcbd1294f9b6ab57eba429b36c242ef34d36bbaec8d428828
69f26949568a7bbee76b77f54f40002eaa717071e002a8a29dd7fd6ebd7df381
1e090ac3117df4e30e8868b26d4cf4c3437b1a53f14bf515798fe3a5001a7bfc
0a8caeef488175e9d8836a28ba65461462c16ab66c0a8d8148f3b84c0a6a4cba
fa900bc09c4f319f578644ff618d50496d2b2d1e63f8232b263308bb55afcb77
232f210c1677d84df6d9309fc40e551b15458c635b5c3edbf88aceb4973d9fdb
c2750b1fff5f96ac8c54dc83bdbdec3dc01d34e510509377e69eff08ad3d5b12
0c72e8a432e627982b11b6dec534a3423bb529c527637171825bc1990501ee48
0b23711803a4a3c48372fb266759c496ad4d6a56299f7b379a783d07aa30cc45
b1f0094995a5aa0791f4b5309f43295fbb71a20e0bc4addb75589d8e577650b1
109b969f8ffe6f26b957c263f38419bb67e4e651adba049a41fa861772f036c1
72ecf3d8742590996ee4951e608b384c1e6764c1618de6548d239a6325fbed92
33f6e0da4e1775d718a1e5f744b2858f67c585a54c912e7ea58dfb58d1ed2ebb
7149f6f1019e0ec86e64c3e70fba3f0b8462a5401655ecf7386e9f7040098995
fd83be5336c1c9b063458f3a327bd5d5753e73a96fc2f86a2610f18e3fb51731
09c65bb95bb2232785241f472597ba75969369ee893dfeaeee6307256db5a8fd
3294cee178863db9c8dbe711aef2f709da7cc9be00aba6e43266acc4870b69b4
74f3639e5d04c3064ee652fc5ea5afee2d5f7d7e2d1a3a95868c9f2524934ba5
3673cd1e76ea8390f07b9d73bedf84a753b37d8c5d3e64652c9fe6bd238e5433
eaac08ff61ea34c3cfcfb6882485766a2658338a60dac5d934899cad1a05688b
8244e51b29ecfb1c0b000000ed0aea43f9d6715158ff3048e550629bada9ff6c
99b48d9af59f83236ea427401c22d30673117dfd8c17e7caa1c432f72a661c5e
cdac243925674d3b168d33b9f29bda3f42a9180c278f91064af6b79e4a251721
7e457d488e88111c19eaf1aa239215789d94305bd5a975e397af5b5419443803
bb8a7f63c9450c80a96bffcde555370e8c6bb3a0f2d2ffa3e41d00c6ee362d64
fa8afa3ffeea6bd8a964cab8911b768452b297bb3bc7930eb61c5ad38dc7aa31
0c8368131733f4ce4bea67f4ad5b85bbedc01b9fc28afdee1d52d8204d6b3fb8
323b3836a858fdc1cf9e7e7ad6fd1e4e8bf95277824ab90310ae2402c4665cf7
6a478a4726ab63e0ff55ef853e6ed3030d04b66028e7e2b56d12c0fc42aef74d
0a19ca21bc77143b589e5bc8dffeb34ffa0084c67395ec2f3aea8e4c1c1f9644
482f364e8471931422be6eb6562d371f966b11e9e0d2c850537f83992f43de66
9d091b472a5a242731a0156c
]]></sourcecode>
</li>
</ul>
</section>

<section anchor="vector-2-quadratic-polynomial-circuit-over-gf-2-128-field-id-5"><name>Vector 2: Quadratic Polynomial Circuit over GF(2^128) (Field ID 5)</name>

<ul>
<li><strong>Description</strong>: Circuit verifying <tt>(s + 1)m^2 + (s + 1)m + n = 0</tt> over the binary field <tt>GF(2^{128})</tt>.</li>
<li><strong>Field</strong>: <tt>GF(2^{128})</tt> (Field ID 5, polynomial representation modulo <tt>x^{128} + x^7 + x^2 + x + 1</tt>)</li>
<li><strong>Witness Inputs</strong>: <tt>W = [w_0, w_1, w_2, w_3] = [1, 84, 5, 6]</tt> with public input <tt>w_0 = 1</tt>, and private inputs <tt>n = 84</tt>, <tt>m = 5</tt>, <tt>s = 6</tt>.</li>
<li><t><strong>Explicit Circuit Definition</strong>:</t>

<ul spacing="compact">
<li><strong>Inputs</strong>: <tt>N_{in} = 4</tt>, <tt>N_{pub} = 1</tt>.</li>
<li><t><strong>Layer 1</strong> (Intermediate wires <tt>U = [u_0, u_1, u_2, u_3, u_4]</tt>):</t>

<ul spacing="compact">
<li><tt>u_0 = 1 = w_0 \cdot w_0</tt></li>
<li><tt>u_1 = m^2 = w_2 \cdot w_2</tt></li>
<li><tt>u_2 = s + 1 = w_3 \cdot w_0 + w_0 \cdot w_0</tt></li>
<li><tt>u_3 = (s + 1)m = w_3 \cdot w_2 + w_0 \cdot w_2</tt></li>
<li><tt>u_4 = n = w_0 \cdot w_1</tt></li>
</ul></li>
<li><t><strong>Layer 0</strong> (Output wire <tt>y_0 = 0</tt>):</t>

<ul spacing="compact">
<li><tt>y_0 = u_2 \cdot u_1 + u_3 \cdot u_0 + u_4 \cdot u_0 = (s+1)m^2 + (s+1)m + n = 0</tt></li>
</ul></li>
</ul></li>
<li><t><strong>Proof System Parameters</strong>:</t>

<ul spacing="compact">
<li><tt>RATE_INV</tt>: 4 (<tt>\rho = 1/4</tt>)</li>
<li><tt>NREQ</tt>: 6 (query count)</li>
<li><tt>NCOL</tt>: 64 (encoded codeword length)</li>
<li>Derived geometry: <tt>BLOCK</tt> = 10, <tt>DBLOCK</tt> = 19, <tt>WR</tt> = 4, <tt>NW</tt> = 29,
<tt>NQ</tt> = 2, <tt>NWROW</tt> = 8, <tt>NQT</tt> = 1, <tt>NROW</tt> = 14</li>
</ul></li>
<li><strong>Statement hash</strong>: the 26-byte ASCII string <tt>sgonal_test_vector_gf2_128</tt></li>
<li><strong>RNG seed</strong>: 12345</li>
<li><t><strong>Merkle nonces</strong> (one per opened column, in the order the columns
were chosen):</t>

<ul spacing="compact">
<li><tt>7ef857289b65bef3539deca0df3f44c67bd0f86b02afddd2eaf2df6b9ba36497</tt></li>
<li><tt>33946263e6da1ec940a9047547a6296f465821313eeb867d988aed265b82eb53</tt></li>
<li><tt>87b402da5412750ec00175350a9579b1f458b668155ba582d31b5d71331f2d41</tt></li>
<li><tt>978ee556b7e529dea3e0fbbde3d83fcb11871c478dd3ddb15fc27e628487950e</tt></li>
<li><tt>1c320449d0bed51ad492d371d066d55c4db059a1c59c267094ae3087746b1e61</tt></li>
<li><tt>c809ce332b7354d1e4b593f335d980dc1f0579e171a0bd092761a12006f3b544</tt></li>
</ul></li>
<li><strong>Commitment Root</strong>:
<tt>30dafd53c4f2441bb8457cd84fa69b3a822fbda58a46527d5cec8ce1903bc504</tt></li>
<li><strong>Sumcheck Proof Size</strong>: 384 bytes</li>
<li><t><strong>Sumcheck Proof (hex)</strong>:</t>

<sourcecode type="txt"><![CDATA[]]></sourcecode>
</li>
<li><t><strong>Ligero Proof Size</strong>: 2632 bytes</t>
</li>
<li><t><strong>Total ZK Proof Size</strong>: 3048 bytes</t>
</li>
<li><t><strong>Complete Proof Serialization (hex)</strong>:</t>

<sourcecode type="txt"><![CDATA[30dafd53c4f2441bb8457cd84fa69b3a822fbda58a46527d5cec8ce1903bc504
6240c86a95637590efc064be3f0e9d28fc212291b18e5e3afed5099df504c647
8aaa530725347ed936fb115880fe03f394808f716d78e8dd8dde5b5bd3f2f489
a71c546509defd48dfb6650b3095fd6737451138f90326c07013cb02076ecab7
4c74af12fafa59cd8fdf6ae0315d1a5ee40de4c200b7d051bbfb98626e86ccdc
c4f2995317ebb77628e811cb190156e6b9cf8cfb4fc62b478e49e262e6f4453f
6e6c5298fc74b14cd2a9ef3ff9872f67452f9012a79e6517347af882d1e0bd70
113cb1307823bf2e57b45515cfce5e8b0c33e9a941524ba5355f0c444d961383
47da64051e89579e5ee002b8346d1b53c26c695f208d5f5b784f90e4ae33b7bb
e9e0d29e9e2ad103933ab99a684b7611ed1a15977f59e2d60d05a2cf24212ff2
0c59ea4f435404b9fa889934fd8d550807142c664890f7d0a9ba27ea040d03eb
fd4bf8ce06ff90509700bcde7042d213adb43c2fb1f46143226c273b09bd650b
ab9b3aec6cd5419edb1bcd616911c2b3898d07c5faf9fa121da18a01f5a1ea9b
3edfb458a51019d7ab9d3f0890c1c216c1e4e2b2c087cea41515e0dbe8ae74b8
923bc6f7ec3396c391e422f440d9b9f85a2e47a8c43682f41bea847fac6ddaa2
46dab1978ee40772bb75fa4571a773bb3a7e1947eeec8d74b7c42a800a0de3c8
463fcf42223b5204c34bb261d8708e96695648bbb4bd534dd3a852032a5a23c6
41e66955d62f95d2bf6acdc16e401aeb355b55f6c7df1fa5493e152633dd67b0
5451e156baed83c4b6b154e44779cd30d382b43cfb918db00d0f7e6ed000ede9
1460ec372a93c7263f54666cdf2d558e0a56f62914e46eaee62b19e1b5ba179b
b96044de94a5964acc4ff7c175f25be4fed23d1096414f307104eaf47e9c756a
54fbf66e974c607946b689560486601c6d351ce7925a6ffe3d960ace99b9b5fd
bbb2ca9d8b56736af217f21ed813e517fe4f3c78fcafa0b47a01307fef9dc4d2
c9fe979532e97d1452034d904db212ffea8c152cdab17bafd8037d519dabe985
091844be5d630ebba0ba17765839c35543e40162c66ae7b73f9d018c463aaa60
34545321a445e83fb706b03f4971bc2c28d529d36c5440c2942e9c22908e5a2f
2347e19b793e15e0852686e29a2029977b3ba6c346ecf3bceca062b5130ac652
925cbca1da5f99528742bfd37d32710296363d2d2dda271eafe8ffdab7f2c378
fb56de4804e8dfb0887e504f5b3c0a1c040a91e4ed07a35a487be0f1c1221d2d
3950c7db515c5e4c557d2508fc8d13c70efc9f99e667f8a0995b9d2b3f15a3b2
9493a370aa72bece76d5c1614ccac7f8a4427a0cbcf38d7b46249e13ff8a38de
34baf543b88990809ff36271f33a9c62c4d79ddfb271ec6554430c7eff2ddb38
0a11f45d772d5f61d051f6c9a4bf188fad665105add63a91c89f9847ea498fd4
d148fcb439d216593fd6cda35cdd541b70dc213c9ac9f172336743b5d9b9f009
2358436be52d50ba60e7d7c9c4d75415527f4c04647f433b02b7955421a405ed
7ef857289b65bef3539deca0df3f44c67bd0f86b02afddd2eaf2df6b9ba36497
33946263e6da1ec940a9047547a6296f465821313eeb867d988aed265b82eb53
87b402da5412750ec00175350a9579b1f458b668155ba582d31b5d71331f2d41
978ee556b7e529dea3e0fbbde3d83fcb11871c478dd3ddb15fc27e628487950e
1c320449d0bed51ad492d371d066d55c4db059a1c59c267094ae3087746b1e61
c809ce332b7354d1e4b593f335d980dc1f0579e171a0bd092761a12006f3b544
5400000084517983aa1a7acab37c5181b97ddb5d183e7a30b7e9884798a1ad13
33c2c14c61fd3a7d917c0e4537af67339705ac87e322061e9a147d45067342f4
18a655334b4588edccadd095e883e9aaecffd81c02e951c856dc5ec383fb28cc
228ccc269c71ba2bf849a9d61bae28f935d474eb5b163f1bee4bbe0dee075869
b0335e5fe98780182348b1ba71767cc88958065a0403e65c7f95d6de372aced9
4abd8bbd885d0f6d38451210d7fddbb8523ad24204fbd51152f5ac27a948e9e3
788f14e7179ad246b576448614fecb01c2d4dd83ee2f5adbaf7750e986c03086
a13f5be9a5e12e9ff31e8450b0c7623113a70837c5310991f2344e058687333d
18893813a17939ad8bac01008f045c5711e2bfff6b16d4ed928a078e4a55eb72
c65500f6f4ef4d02b4363836cdf4e9c82b761e1824c54d7285c1a5f0b3996af3
088c52df6b9e746c73043aa4262717e733d508761dcac98315686357b39624b9
912a2bb18e692e87b31ad3f26bd3b3246a678f6b9be9272611c21b6fbb1962e1
473c76089d9b039fd9b32adb3a614f9644edd80a7e7d9348c675feee9fdc7231
c50bf481d4b74328bdee990dc0af51f0a9ce68a34340cbf6067d65a1bc5b6406
9e11b8b1a3bbc72f3c5d517befd88b288ca28f8a2adcb8308eda9e97fb4ab325
b567524999b568c401c0d94ea358434d786b2349ad878b197df169e78bbbdb20
08611c7b0ee75ac194c86517ff4e592debce41c3972344dfadc6af0a1c84c1ae
dd5b6b3c0ab3500a12677c73f8c6aaf7f9526ee5020bb01874d32ee0449c6dd8
35282a15dea4fabad0dcf06dea8e7b9e55c9a70686756f1f6db32f01b4509685
21e67614b361c51a3673fb5cf4b313d35b041599d95a54d17de65204a289c04b
33a33f3d15a58a90b90fe9c0d05894f75c48000e8892866e516f605c58466c06
52df4df4245cf61ecb43ce23ac6d64259d89a0c02a156957159feef112d16398
51e0b1b2e97aa138b6b7657ade0eb2df303cba9e4a729e79061ccd89116c56f3
13a082edb0e311185b2820bec856acf95497f23b04be15e7059206f93db9a683
7071bff558f8d7ec8ff91c089b7c3baabbf8a8770741d353cb0541383867b0a9
6dce09199b453bcfeddb8f633797bca30f720faf649c4a67a488ecf4388c5e73
83cf508114629bf0d2bc0692bcb364748b6a728b2607125c74568624affa5174
e400c0c17235340b2e9f56010abaf23e594942196c7ff4d4856d264f5bcb330a
d31030347df134b7f40d68080358488029de0f12d707a15fa0737b04f1c9b3f1
bf8227cf67201ce2b347f47eb14d1377666c472b1a5fd34dc22002183103944f
1e277def38512a4c5dabd0b535d2bd02033df67499240c94b625212b1b57b26d
d82a6b126a9f9d6b92cad7920fa68e4dc4f74ffe46f31c05f2c2b675f6050fd3
3c865c8a03724f9ff9789500b4ddf43ffe7eda857f7670288c27641fb9dc7c63
c8a916920cc7c76f6eb3dc9b083444cfb1d77470290f3b671967030db402ffdb
49fd38f424639cf0aaff985144f29040a3ad1359bddaae36dbba740ee4f0c97b
194dc7d1742827db2f33965e30fa4f37c8217eaddfc34082526df5755b4338db
89e4221b919409fad65d1abf4810ef67183ee40a916de209eead26fa5156116f
9b72440eafde88be86e28a0a04e88fe29e4472b9ccd3a192ae00f6f52baee054
0251869ec728cd03bb2201bcffcf5bc3d9c345d2b14b4f544363cacaf0c088c5
6cb8dc0dc8f126136e1b82c0b7577c8a22b1448c1f588c9f34a1fc5a76986f4b
98a6c4f25a832f48feab33f45e0f48bc9e5a058a0d447d5e0d996b8930d0f824
8a98542f6e74cc99630b559b7c4c1ccc08e0f1b1bc053347822ea65b7a62c871
90fcace20c00000090481e0835d39fd8a96a8621f6b07090b791b96bc168be8b
925875f9651cef09b13b3d19fa1108a15833209497ce19b540a98823b2b717a3
2336eab1c03722b620c9edc47b837e7e9dbcdeacf6e21e6b7c32bb3667017684
860a0f3df8e24aeabb3ccd2f56fa81bf67f1cb75f09fa40b53d4f15f0392430a
00430902b3d88062d100629620fa4365b48a8b1a70d7bfc59766bb8f66746774
cf468ce1c10d2a494e4309fcf32119b14aaf3bb94b30d100410551a0de356601
da0289d1573931745b3d015b150bdf91ed0fa201b77bf13f928b7da499f4b9d8
4ca44d988515ca586768192b8340d3438938f37cd9e78206164622646df66ee8
e9da522c387dbd783dcead20c990b95f5ca3a75a2fded5094377f65585e0b39f
d656856db6f3bbcd74ac6c094e80ac1e7b437b0a45249f7f795e969b2f850669
70895e4bf70c168b2239ed30178e7cde0bb7b317d15f3e632e665cf53e883b8c
d1301635d74c479c1cea5be7574af47527b3fc7fa49563b3a4d1a19eb5935076
25b8ba55cbd4ff01
]]></sourcecode>
</li>
</ul>
</section>
</section>
</section>

</back>

</rfc>
