<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "&#160;">
  <!ENTITY zwsp   "&#8203;">
  <!ENTITY nbhy   "&#8209;">
  <!ENTITY wj     "&#8288;">
]>
<?xml-stylesheet type="text/xsl" href="rfc2629.xslt" ?>
<!-- generated by https://github.com/cabo/kramdown-rfc version 1.7.19 (Ruby 3.3.3) -->
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" ipr="trust200902" docName="draft-mouris-cfrg-mastic-03" category="info" consensus="true" submissionType="IRTF" tocInclude="true" sortRefs="true" symRefs="true" version="3">
  <!-- xml2rfc v2v3 conversion 3.23.1 -->
  <front>
    <title abbrev="Mastic">The Mastic VDAF</title>
    <seriesInfo name="Internet-Draft" value="draft-mouris-cfrg-mastic-03"/>
    <author fullname="Hannah Davis">
      <organization>Seagate</organization>
      <address>
        <email>hannah.e.davis@seagate.com</email>
      </address>
    </author>
    <author fullname="Dimitris Mouris">
      <organization>Nillion</organization>
      <address>
        <email>dimitris@nillion.com</email>
      </address>
    </author>
    <author initials="C." surname="Patton" fullname="Christopher Patton">
      <organization>Cloudflare</organization>
      <address>
        <email>chrispatton+ietf@gmail.com</email>
      </address>
    </author>
    <author fullname="Pratik Sarkar">
      <organization>Supra Research</organization>
      <address>
        <email>pratik93@bu.edu</email>
      </address>
    </author>
    <author fullname="Nektarios G. Tsoutsos">
      <organization>University of Delaware</organization>
      <address>
        <email>tsoutsos@udel.edu</email>
      </address>
    </author>
    <date year="2024" month="September" day="27"/>
    <area>IRTF</area>
    <workgroup>Crypto Forum</workgroup>
    <keyword>Internet-Draft</keyword>
    <abstract>
      <?line 108?>

<t>This document describes Mastic, a two-party VDAF for the following secure
aggregation task: each client holds an input and an associated weight, and the
data collector wants to aggregate the weights of all clients whose inputs begin
with a prefix chosen by the data collector. This functionality enables two
classes of applications. First, it allows grouping metrics by client attributes
without revealing which clients have which attributes. Second, it solves the
weighted heavy hitters problem, where the goal is to compute the subset of
inputs that have the highest total weight.</t>
    </abstract>
    <note removeInRFC="true">
      <name>About This Document</name>
      <t>
        Status information for this document may be found at <eref target="https://datatracker.ietf.org/doc/draft-mouris-cfrg-mastic/"/>.
      </t>
      <t>
        Discussion of this document takes place on the
        Crypto Forum Research Group mailing list (<eref target="mailto:cfrg@ietf.org"/>),
        which is archived at <eref target="https://mailarchive.ietf.org/arch/search/?email_list=cfrg"/>.
        Subscribe at <eref target="https://www.ietf.org/mailman/listinfo/cfrg/"/>.
      </t>
      <t>Source for this draft and an issue tracker can be found at
        <eref target="https://github.com/jimouris/draft-mouris-cfrg-mastic"/>.</t>
    </note>
  </front>
  <middle>
    <?line 119?>

<section anchor="introduction">
      <name>Introduction</name>
      <t>(RFC EDITOR: Remove this paragraph.) The source for this draft and the
reference code can be found at
https://github.com/jimouris/draft-mouris-cfrg-mastic.</t>
      <t>The private "heavy hitters" problem is to compute the most popular input
strings generated by clients without learning the inputs themselves. For
example, a browser vendor might want to know which websites are visited most
frequently without learning which clients visited which websites.</t>
      <t>This problem can be solved by combining a binary search with a subroutine
solving the simpler "prefix histogram" problem. The goal of this problem is to
count how many of the input strings begin with each of a sequence of candidate
prefixes. This problem can be solved using a Verifiable Distributed Aggregation
Function, or VDAF <xref target="VDAF"/>.</t>
      <ul empty="true">
        <li>
          <t>TODO Update the VDAF reference to draft 12 (issue #34). Note that we're
currently in sync with an unpublished version of the draft. The repository is
https://github.com/cfrg/draft-irtf-cfrg-vdaf and the commit is
ea39dccccc83988029fd667555aa45f6589195b2.</t>
        </li>
      </ul>
      <t>The Poplar1 VDAF specified in <xref section="8" sectionFormat="of" target="VDAF"/> describes how to
distribute this computation amongst two aggregation servers such that, as long
as one server is honest, no individual's input is observed in the clear. At the
same time, Poplar1 allows the servers to detect and remove any invalid
measurements that would otherwise corrupt the computation.</t>
      <t>This document describes Mastic <xref target="MPDST24"/>, a VDAF for the following, more
general functionality: each client holds an input and an associated weight, and
the data collector's goal is, for each candidate prefix, to aggregate the
weights of all clients whose inputs have the prefix in common. This
functionality gives rise to two types of applications:</t>
      <ol spacing="normal" type="1"><li>
          <t>"weighted heavy hitters": Rather than compute the most frequent inputs, as
in plain heavy hitters, the goal here is to compute the set of inputs with
the highest total weight. For example, a browser vendor might want to know
which web pages have the highest average load time, perhaps indicating a
performance issue in the browser. Because weighted heavy hitters is more
general, Mastic can be used as a drop-in replacement for Poplar1. It is is
also more efficient, requiring just one round of communication for
preparation (<xref section="5.2" sectionFormat="of" target="VDAF"/>) compared to Poplar1's two rounds.</t>
        </li>
        <li>
          <t>"attribute-based metrics": The Prio3 VDAF (<xref section="7" sectionFormat="of" target="VDAF"/>) can be
used for a variety of aggregation tasks, ranging from simple summary
statistics, like average or standard deviation, to more sophisticated
representations of data, like histograms or linear regression. In many
situations, it is desirable to group such metrics by client attributes such
as geolocation or user agent (<xref section="10.1.5" sectionFormat="of" target="RFC9110"/>). Mastic
provides this functionality without revealing any client's attribute to the
aggregation servers or data collector.</t>
        </li>
      </ol>
      <t>The main component of Mastic is the Verifiable Incremental Distributed Point
Function (VIDPF) of <xref target="MST24"/>. VIDPF extends IDPF (<xref section="8.3" sectionFormat="of" target="VDAF"/>),
the main building block of Poplar1. Both IDPF and VIDPF are a form of function
secret sharing <xref target="BGI15"/>, where a client generates shares of a secret function
<tt>F</tt> such that each server can computes shares of <tt>F(X)</tt> for a chosen <tt>X</tt>. In
our case, the function being shared is associated with a secret input string
<tt>alpha</tt> and weight <tt>beta</tt> for which <tt>F(X) = beta</tt> for every prefix <tt>X</tt> of
<tt>alpha</tt> and <tt>F(X) = 0</tt> for every <tt>X</tt> this is not a prefix of <tt>alpha</tt>. The
scheme is verifiable in the sense that, for any two candidate prefixes of the
same length, the servers can verify that at most one of them evaluates to
<tt>beta</tt> and the other(s) evaluate(s) to <tt>0</tt>.</t>
      <t>Mastic combines VIDPF with a method for checking that <tt>beta</tt> itself is a valid
weight. For example, if the weights represent page load times, it is important
to make sure each weight is within a sensible range, say within seconds rather
than hours or days. Otherwise, misbehaving clients would be able to poison the
computation by reporting out of range values. This range check is accomplished
with the Fully Linear Proof (FLP) system of <xref section="7.3" sectionFormat="of" target="VDAF"/>. An FLP
allows properties of secret shared data to be validated without revealing the
data itself. In Mastic, the client generates an FLP of its <tt>beta</tt>'s validity;
when the servers are ready to evaluate the VIDPF, they first compute shares of
<tt>beta</tt> and verify the FLP, which itself is secret shared. Then the VIDPF
ensures that the non-<tt>0</tt> output of the point function is the same for each
evaluation.</t>
      <t>This document specifies VIDPF in <xref target="vidpf"/> and the composition of VIDPF and FLP
into Mastic in <xref target="vidpf"/>. The appendix includes supplementary material:</t>
      <ul spacing="normal">
        <li>
          <t><xref target="motivation"/> discusses some use cases that motivated Mastic's functionality.</t>
        </li>
        <li>
          <t><xref target="additional-modes"/> describes extensions and optimizations for Mastic,
including a batched "preparation" (<xref section="5.2" sectionFormat="of" target="VDAF"/>) mode of
operation that reduces communication cost, and a 3-party variant of the
protocol that ensures robustness against poisoning attacks in the presence of
one malicious aggregation server.</t>
        </li>
      </ul>
    </section>
    <section anchor="conventions">
      <name>Conventions and Definitions</name>
      <t>The key words "<bcp14>MUST</bcp14>", "<bcp14>MUST NOT</bcp14>", "<bcp14>REQUIRED</bcp14>", "<bcp14>SHALL</bcp14>", "<bcp14>SHALL
NOT</bcp14>", "<bcp14>SHOULD</bcp14>", "<bcp14>SHOULD NOT</bcp14>", "<bcp14>RECOMMENDED</bcp14>", "<bcp14>NOT RECOMMENDED</bcp14>",
"<bcp14>MAY</bcp14>", and "<bcp14>OPTIONAL</bcp14>" in this document are to be interpreted as
described in BCP 14 <xref target="RFC2119"/> <xref target="RFC8174"/> when, and only when, they
appear in all capitals, as shown here.</t>
      <?line -18?>

<t>This document uses the same conventions and definitions of <xref section="2" sectionFormat="of" target="VDAF"/>. The following functions are as defined therein:</t>
      <ul spacing="normal">
        <li>
          <t><tt>gen_rand(len: int) -&gt; bytes</tt></t>
        </li>
        <li>
          <t>TODO List all functions we use in alphabetical order.</t>
        </li>
      </ul>
      <t>This document uses the following terms as defined in <xref target="VDAF"/>:
"Aggregator",
"Client",
"Collector",
"aggregate result",
"aggregate share",
"aggregation parameter",
"batch",
"input share",
"measurement",
"output share",
"prep message",
"prep share", and
"report".</t>
      <t>Mastic uses finite fields as specified in <xref section="6.1" sectionFormat="of" target="VDAF"/>. We usually
denote a finite field by <tt>F</tt> and its Python class object, a subclass of
<tt>Field</tt>, as <tt>field: type[F]</tt>.</t>
      <t>An instance of Mastic is determined by a desired bit-length of the input,
denoted <tt>BITS</tt>, and a validity circuit, an instance of <tt>Valid</tt> as defined in
<xref section="7.3.2" sectionFormat="of" target="VDAF"/>. The validity circuit is used to instantiate the FLP
and defines the type of the weights generated by Clients and the type of the
total weight for each prefix computed by the Collector.</t>
      <t>The Client's measurement has two components: the input string <tt>alpha: int</tt> in
<tt>range(2**BITS)</tt> (TODO The type may change once we solve issue #34) and its
weight. The weight's type is denoted by <tt>W</tt>. We use <tt>beta: list[F]</tt> to denote
the encoded weight, obtained by invoking the FLP's encoder (<xref section="7.1.1" sectionFormat="of" target="VDAF"/>).</t>
      <t>The aggregate result has type <tt>list[R]</tt>, where <tt>R</tt> is likewise a type defined
by the validity circuit. Each element of this list corresponds to the total
weight for one of the candidate prefixes.</t>
      <t>Finally, Mastic uses eXtendable Output Functions (XOFs) as specified in
<xref section="6.2" sectionFormat="of" target="VDAF"/>.</t>
    </section>
    <section anchor="vidpf">
      <name>Specification of VIDPF</name>
      <ul empty="true">
        <li>
          <t>NOTE This specification is based on <xref target="MST24"/>, which in turn draws on ideas
from <xref target="CP22"/>. We don't yet have a concrete security analysis of the complete
construction. Some details are likely to change as a result of such analysis.</t>
        </li>
      </ul>
      <table anchor="vidpf-params">
        <name>VIDPF parameters.</name>
        <thead>
          <tr>
            <th align="left">Parameter</th>
            <th align="left">Description</th>
            <th align="left">Value</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td align="left">
              <tt>KEY_SIZE: int</tt></td>
            <td align="left">the size of each VIDPF key</td>
            <td align="left">
              <tt>XofFixedKeyAes128.SEED_SIZE</tt> (<xref section="6.2.2" sectionFormat="of" target="VDAF"/>)</td>
          </tr>
          <tr>
            <td align="left">
              <tt>NONCE_SIZE: int</tt></td>
            <td align="left">the size of the VIDPF nonce</td>
            <td align="left">
              <tt>KEY_SIZE</tt></td>
          </tr>
          <tr>
            <td align="left">
              <tt>RAND_SIZE: int</tt></td>
            <td align="left">the number of random bytes consumed by <tt>gen()</tt></td>
            <td align="left">
              <tt>2 * KEY_SIZE</tt></td>
          </tr>
          <tr>
            <td align="left">
              <tt>BITS: int</tt></td>
            <td align="left">bit length of the input string <tt>alpha</tt></td>
            <td align="left">set by constructor</td>
          </tr>
          <tr>
            <td align="left">
              <tt>VALUE_LEN: int</tt></td>
            <td align="left">length of <tt>beta</tt></td>
            <td align="left">set by constructor</td>
          </tr>
          <tr>
            <td align="left">
              <tt>field: type[F]</tt></td>
            <td align="left">class object for the field (<xref section="6.1" sectionFormat="of" target="VDAF"/>)</td>
            <td align="left">set by constructor</td>
          </tr>
        </tbody>
      </table>
      <t>This section specifies the Verifiable Incremental Distributed Point Function
(VIDPF) of <xref target="MST24"/>. Its parameters are summarized in <xref target="vidpf-params"/>.</t>
      <t>VIDPF is a function secret sharing scheme <xref target="BGI15"/> for functions <tt>F</tt> for which:</t>
      <ul spacing="normal">
        <li>
          <t><tt>F(X) = [field(1)] + beta</tt> if <tt>X</tt> is a prefix of <tt>alpha</tt></t>
        </li>
        <li>
          <t><tt>F(X) = field.zeros(VALUE_LEN+1)</tt> if <tt>x</tt> is not a prefix of <tt>alpha</tt></t>
        </li>
      </ul>
      <t>where <tt>alpha</tt> and <tt>beta</tt> are the input and encoded weight of a Client. The
scheme is designed to allow each Aggregator to compute a share of <tt>F(X)</tt> for
any candidate prefix <tt>X</tt> without revealing any information about <tt>alpha</tt> or
<tt>beta</tt>. Furthermore, the output shares can be aggregated locally, allowing each
Aggregator to compute a share of the total weight for all inputs that have <tt>X</tt>
as a prefix.</t>
      <t>Along with encoded weight <tt>beta</tt>, the output includes a counter prefix, denoted
<tt>field(1)</tt>, so that the total weight also includes the prefix count. This
allows for the Collector to take this into account when decoding the aggregate
result for each prefix. This is required by <xref section="7.1.1" sectionFormat="of" target="VDAF"/>.</t>
      <t>The Aggregators evaluate a Client's VIDPF on a sequence of candidate prefixes.
Imagine arranging these prefixes in a binary tree where each path from the root
corresponds to a prefix <tt>X</tt> and each node corresponds to a payload <tt>F(X)</tt>. We
refer to this as the "prefix tree".</t>
      <t>When the Aggregators evaluate a Client's VIDPF, they verify three properties of
the prefix tree:</t>
      <ol spacing="normal" type="1"><li>
          <t>One-hotness: at every level of the tree, at most one node has a non-zero
payload.</t>
        </li>
        <li>
          <t>Path consistency: each payload is equal to the sum of the payloads of its
children. If one-hotness holds, then this ensures the payload is equal to
<tt>[field(1)] + beta</tt> for each node along the <tt>alpha</tt> path.</t>
        </li>
        <li>
          <t>Counter consistency: the counter of the non-zero payload is equal to
<tt>field(1)</tt>.</t>
        </li>
      </ol>
      <t>VIDPF is comprised of two algorithms.</t>
      <t>The key generation algorithm defined in <xref target="vidpf-key-gen"/> takes in a <tt>(alpha,
beta)</tt> and a nonce and outputs secret shares of <tt>F</tt>. The shares take the form
of a pair of "keys", one for each Aggregator, and a sequence of "correction
words" sent to both Aggregators. We define correction words in the next
section.</t>
      <t>The key evaluation algorithm defined in <xref target="vidpf-key-eval"/> takes in the
correction words, the Aggregator's key, the sequence of candidate prefixes,
and the nonce associated with the Client's report. It outputs secret shares of
<tt>beta</tt>, the share of the payload for each prefix, and a byte string known as
the "evaluation proof". To verify one-hotness, payload consistency, and
counter consistency, the Aggregators check that the proofs they computed are
equal.</t>
      <section anchor="vidpf-key-gen">
        <name>Key Generation</name>
        <t>The VIDPF-key generation algorithm run by each Client is listed below. The
specification invokes auxiliary functions defined in <xref target="vidpf-aux"/>. Its inputs
are the input string <tt>alpha</tt>, the encoded weight <tt>beta</tt>, a public nonce of
length <tt>NONCE_SIZE</tt>, and the randomness consumed by the algorithm of length
<tt>RAND_SIZE</tt>. Its outputs are the public sequence of "correction words", one for
each level of the tree, and the secret keys, one for each Aggregator.</t>
        <ul empty="true">
          <li>
            <t>TODO Give a high level overview of how IDPF works, in particular the
seed/control-bit invariant for each level. Define <tt>CorrectionWord</tt> and
explain the role of correction words and define node proofs, which are unique
to VIDPF.</t>
            <t>TODO Specify bounds on the inputs, namely that the nonce has to be random.
(This is inherited from IDPF security considerations.)</t>
            <t>TODO Explain functional differences between VIDPF and IDPF in
<xref section="8" sectionFormat="of" target="VDAF"/>. Namely, there is no distinction between inner and
leaf nodes and the payload is supposed to be the same at each level.</t>
          </li>
        </ul>
        <sourcecode type="python"><![CDATA[
def gen(self,
        alpha: int,
        beta: list[F],
        nonce: bytes,
        rand: bytes,
        ) -> tuple[list[CorrectionWord], list[bytes]]:
    '''
    The VIDPF key generation algorithm.

    Returns the public share (i.e., the correction word for each
    level of the tree) and two keys, one fore each aggregator.

    Implementation note: for clarity, this algorithm has not been
    written in a manner that is side-channel resistant. To avoid
    leading `alpha` via a side-channel, implementations should avoid
    branching or indexing into arrays in a data-dependent manner.
    '''
    if alpha not in range(2 ** self.BITS):
        raise ValueError("alpha out of range")
    if len(beta) != self.VALUE_LEN:
        raise ValueError("incorrect beta length")
    if len(nonce) != self.NONCE_SIZE:
        raise ValueError("incorrect nonce size")
    if len(rand) != self.RAND_SIZE:
        raise ValueError("randomness has incorrect length")

    keys = [rand[:self.KEY_SIZE], rand[self.KEY_SIZE:]]

    # [MST24, Fig. 15]: s0^0, s1^0, t0^0, t1^0
    seed = keys.copy()
    ctrl = [False, True]
    correction_words = []
    for i in range(self.BITS):
        idx = PrefixTreeIndex(alpha >> (self.BITS - i - 1), i)
        bit = bool(idx.node & 1)

        # [MST24]: if x = 0 then keep <- L, lose <- R
        #
        # Implementation note: the value of `bits` is
        # `alpha`-dependent.
        (keep, lose) = (1, 0) if bit else (0, 1)

        # Extend: compute the left and right children the current
        # level of the tree. During evaluation, one of these children
        # will be selected as the next seed and control bit.
        #
        # [MST24]: s_0^L || s_0^R || t_0^L || t_0^R
        #          s_1^L || s_1^R || t_1^L || t_1^R
        (s0, t0) = self.extend(seed[0], nonce)
        (s1, t1) = self.extend(seed[1], nonce)

        # Compute the correction words for this level's seed and
        # control bit. Our goal is to maintain the following
        # invariant, after correction:
        #
        # * If evaluation is on path, then each aggregator's will
        #   compute a different seed and their control bits will be
        #   secret shares of one.
        #
        # * If evaluation is off path, then the aggregators will
        #   compute the same seed and their control bits will be
        #   shares of zero.
        #
        # Implementation note: the index `lose` is `alpha`-dependent.
        seed_cw = xor(s0[lose], s1[lose])
        ctrl_cw = [
            t0[0] ^ t1[0] ^ (not bit),  # [MST24]: t_c^L
            t0[1] ^ t1[1] ^ bit,        # [MST24]: t_c^R
        ]

        # Correct.
        #
        # Implementation note: the index `keep` is `alpha`-dependent,
        # as is `ctrl`.
        if ctrl[0]:
            s0[keep] = xor(s0[keep], seed_cw)
            t0[keep] ^= ctrl_cw[keep]
        if ctrl[1]:
            s1[keep] = xor(s1[keep], seed_cw)
            t1[keep] ^= ctrl_cw[keep]

        # Convert.
        (seed[0], w0) = self.convert(s0[keep], nonce)
        (seed[1], w1) = self.convert(s1[keep], nonce)
        ctrl[0] = t0[keep]  # [MST24]: t0'
        ctrl[1] = t1[keep]  # [MST24]: t1'

        # Compute the correction word for this level's payload.
        #
        # Implementation note: `ctrl` is `alpha`-dependent.
        w_cw = vec_add(vec_sub([self.field(1)] + beta, w0), w1)
        if ctrl[1]:
            w_cw = vec_neg(w_cw)

        # Compute the correction word for this level's node proof. If
        # evaluation is on path, then exactly one of the aggregatos
        # will correct their node proof, causing them to compute the
        # same node value. If evaluation is off path, then both will
        # correct or neither will; and since they compute the same
        # seed, they will again compute the same value.
        proof_cw = xor(
            self.node_proof(seed[0], idx),
            self.node_proof(seed[1], idx),
        )

        correction_words.append((seed_cw, ctrl_cw, w_cw, proof_cw))

    return (correction_words, keys)
]]></sourcecode>
      </section>
      <section anchor="vidpf-key-eval">
        <name>Key Evaluation</name>
        <t>The VIDPF-key evaluation algorithm is listed below. See <xref target="vidpf-aux"/> for
deferred auxiliary functions. Its inputs are the Aggregator's ID (either <tt>0</tt> or
<tt>1</tt>), the correction words, the Aggregator's key, the level of the tree, the
sequence of prefixes, and the nonce associated with the report. Its outputs are
the Aggregator's share of <tt>beta</tt>, the sequence of output shares for each
prefix, and the evaluation proof.</t>
        <ul empty="true">
          <li>
            <t>TODO Provide an overview and define <tt>PrefixTreeIndex</tt> and <tt>PrefixTreeEntry</tt>.
Explain how the evaluation proof is constructed.</t>
          </li>
        </ul>
        <sourcecode type="python"><![CDATA[
def eval(self,
         agg_id: int,
         correction_words: list[CorrectionWord],
         key: bytes,
         level: int,
         prefixes: Sequence[int],
         nonce: bytes,
         ) -> tuple[list[F], list[list[F]], bytes]:
    """
    The VIDPF key evaluation algorithm.

    Return the aggregator's share of `beta`, its output share for
    each prefix, and its evaluation proof.
    """
    if agg_id not in range(2):
        raise ValueError("invalid aggregator ID")
    if len(correction_words) != self.BITS:
        raise ValueError("corrections words has incorrect length")
    if level not in range(self.BITS):
        raise ValueError("level too deep")
    if len(set(prefixes)) != len(prefixes):
        raise ValueError("candidate prefixes are non-unique")

    # Evaluate our share of the prefix tree and compute the path
    # proof.
    #
    # Implementation note: we can save computation by storing
    # `prefix_tree_share` across `eval()` calls for the same report.
    prefix_tree_share: dict[PrefixTreeIndex, PrefixTreeEntry] = {}
    root = PrefixTreeEntry.root(key, bool(agg_id))
    onehot_proof = ONEHOT_PROOF_INIT
    for i in range(level+1):
        for prefix in prefixes:
            if prefix not in range(2 ** (level+1)):
                raise ValueError("prefix too long")

            # Compute the entry for `prefix`. To do so, we first need
            # to look up the parent node.
            #
            # The index of the current prefix `prefix` is
            # `PrefixTreeIndex(prefix >> (level - i), i)`. Its parent
            # is at level `i - 1`.
            idx = PrefixTreeIndex(prefix >> (level - i + 1), i - 1)
            node = prefix_tree_share.setdefault(idx, root)
            for child_idx in [idx.left_child(), idx.right_child()]:
                # Compute the entry for `prefix` and its sibling. The
                # sibling is used for the counter and payload checks.
                if not prefix_tree_share.get(child_idx):
                    (child, onehot_proof) = self.eval_next(
                        node,
                        onehot_proof,
                        correction_words[i],
                        nonce,
                        child_idx,
                    )
                    prefix_tree_share[child_idx] = child

    # Compute the aggregator's share of `beta`.
    w0 = prefix_tree_share[PrefixTreeIndex(0, 0)].w
    w1 = prefix_tree_share[PrefixTreeIndex(1, 0)].w
    beta_share = vec_add(w0, w1)[1:]
    if agg_id == 1:
        beta_share = vec_neg(beta_share)

    # Counter check: check that the first element of the payload is
    # equal to 1.
    #
    # Each aggregator holds an additive share of the counter, so we
    # aggregator 1 negate its share and add 1 so that they both
    # compute the same value for `counter`.
    counter_check = self.field.encode_vec(
        [w0[0] + w1[0] + self.field(agg_id)])

    # Payload check: for each node, check that the payload is equal
    # to the sum of its children.
    payload_check = b''
    for prefix in prefixes:
        for i in range(level):
            idx = PrefixTreeIndex(prefix >> (level - i), i)
            w = prefix_tree_share[idx].w
            w0 = prefix_tree_share[idx.left_child()].w
            w1 = prefix_tree_share[idx.right_child()].w
            payload_check += self.field.encode_vec(
                vec_sub(w, vec_add(w0, w1)))

    # Compute the Aggregator's output share.
    out_share = []
    for prefix in prefixes:
        idx = PrefixTreeIndex(prefix, level)
        w = prefix_tree_share[idx].w
        out_share.append(w if agg_id == 0 else vec_neg(w))

    # Compute the evaluation proof. If both aggregators compute the
    # same value, then they agree on the onehot proof, the counter,
    # and the payload.
    proof = eval_proof(onehot_proof, counter_check, payload_check)
    return (beta_share, out_share, proof)

def eval_next(self,
              node: PrefixTreeEntry,
              onehot_proof: bytes,
              correction_word: CorrectionWord,
              nonce: bytes,
              idx: PrefixTreeIndex,
              ) -> tuple[PrefixTreeEntry, bytes]:
    """
    Extend a node in the tree, select and correct one of its
    children, then convert it into a payload and the next seed.
    """
    (seed_cw, ctrl_cw, w_cw, proof_cw) = correction_word
    keep = idx.node & 1

    # Extend.
    #
    # [MST24, Fig. 17]: (s^L, s^R), (t^L, t^R) = PRG(s^{i-1})
    (s, t) = self.extend(node.seed, nonce)

    # Correct.
    #
    # Implementation note: avoid branching on the value of control
    # bits, as its value may be leaked by a side channel.
    if node.ctrl:
        s[keep] = xor(s[keep], seed_cw)
        t[keep] ^= ctrl_cw[keep]

    # Convert and correct the payload.
    #
    # Implementation note: the conditional addition should be
    # replaced with a constant-time select in practice in order to
    # reduce leakage via timing side channels.
    (next_seed, w) = self.convert(s[keep], nonce)  # [MST24]: s^i,W^i
    next_ctrl = t[keep]  # [MST24]: t'^i
    if next_ctrl:
        w = vec_add(w, w_cw)

    # Compute and correct the node proof and update the onehot proof.
    # Each update resembles a step of Merkle-Damgard compression. The
    # main difference is that we XOR each block (i.e., corrected node
    # proof) with the previous hash (or IV) rather than compress.
    #
    #             corrected node proof
    #                 |
    #                 |
    #                 v
    #                 v
    # current      +-----+     +------+     +-----+      updated
    # proof  --+-->| XOR |---->| Hash |---->| XOR |----> proof
    #          |   +-----+     +------+     +-----+
    #          |                               ^
    #          |                               |
    #          +-------------------------------+
    #
    # [MST24]: \tilde\pi = H_1(x^{\leq i} || s^\i)
    #          \pi = \tilde \pi \xor
    #             H_2(\pi \xor (\tilde\pi \xor t^\i \cdot \cs^\i)
    #
    # Implementation note: avoid branching on the control bit here.
    node_proof = self.node_proof(next_seed, idx)
    if next_ctrl:
        node_proof = xor(node_proof, proof_cw)
    onehot_proof = xor(onehot_proof,
                       hash_proof(xor(onehot_proof, node_proof)))

    return (PrefixTreeEntry(next_seed, next_ctrl, w), onehot_proof)
]]></sourcecode>
      </section>
      <section anchor="vidpf-aux">
        <name>Auxiliary functions</name>
        <sourcecode type="python"><![CDATA[
def extend(self,
           seed: bytes,
           nonce: bytes,
           ) -> tuple[list[bytes], Ctrl]:
    '''
    Extend a seed into the seed and control bits for its left and
    right children in the VIDPF tree.
    '''
    xof = XofFixedKeyAes128(seed, dst(USAGE_EXTEND), nonce)
    s = [
        bytearray(xof.next(self.KEY_SIZE)),
        bytearray(xof.next(self.KEY_SIZE)),
    ]
    # Use the least significant bits as the control bit correction,
    # and then zero it out. This gives effectively 127 bits of
    # security, but reduces the number of AES calls needed by 1/3.
    t = [bool(s[0][0] & 1), bool(s[1][0] & 1)]
    s[0][0] &= 0xFE
    s[1][0] &= 0xFE
    return ([bytes(s[0]), bytes(s[1])], t)

def convert(self,
            seed: bytes,
            nonce: bytes,
            ) -> tuple[bytes, list[F]]:
    '''
    Convert a selected seed into a payload and the seed for the next
    level.
    '''
    xof = XofFixedKeyAes128(seed, dst(USAGE_CONVERT), nonce)
    next_seed = xof.next(XofFixedKeyAes128.SEED_SIZE)
    payload = xof.next_vec(self.field, 1+self.VALUE_LEN)
    return (next_seed, payload)

def node_proof(self,
               seed: bytes,
               idx: PrefixTreeIndex) -> bytes:
    '''
    Compute the proof for this node.
    '''
    binder = \
        to_le_bytes(self.BITS, 2) + \
        to_le_bytes(idx.node, (self.BITS + 7) // 8) + \
        to_le_bytes(idx.level, 2)
    xof = XofTurboShake128(seed, dst(USAGE_NODE_PROOF), binder)
    return xof.next(PROOF_SIZE)

def hash_proof(proof: bytes) -> bytes:
    xof = XofTurboShake128(b'', dst(USAGE_ONEHOT_PROOF_HASH), proof)
    return xof.next(PROOF_SIZE)

def eval_proof(onehot_proof: bytes,
               counter_check: bytes,
               payload_check: bytes) -> bytes:
    binder = onehot_proof + counter_check + payload_check
    xof = XofTurboShake128(b'', dst(USAGE_EVAL_PROOF), binder)
    return xof.next(PROOF_SIZE)
]]></sourcecode>
      </section>
    </section>
    <section anchor="vdaf">
      <name>Specification of Mastic</name>
      <t>Mastic combines the VIDPF from <xref target="vidpf"/> with the FLP from <xref section="7.3" sectionFormat="of" target="VDAF"/>. An instance of Mastic is determined by a choice of length of the
input, denoted <tt>BITS</tt>, and a validity circuit, an instance of <tt>Valid</tt> as
defined in <xref section="7.3.2" sectionFormat="of" target="VDAF"/>.</t>
      <t>The validity circuit determines the type of the weights submitted by Clients,
denoted by <tt>W</tt>, the total weight computed by the Collector, denoted by <tt>R</tt>, and
the field (<xref section="6.1" sectionFormat="of" target="VDAF"/>) in which the weights are encoded and
aggregated. The field is denoted by <tt>F</tt>.</t>
      <t>The VIDPF is instantiated with bit length <tt>BITS</tt>, value length
<tt>valid.MEAS_LEN</tt>, and field <tt>valid.field</tt>, where <tt>valid</tt> is the validity
circuit. We denote this instance of the VIDPF by <tt>vidpf</tt>.</t>
      <t>In the remainder, we write <tt>xof</tt> as shorthand for <tt>XofTurboShake128</tt> (<xref section="6.2.1" sectionFormat="of" target="VDAF"/>).</t>
      <t>Mastic's implementation of the VDAF interface (<xref section="5" sectionFormat="of" target="VDAF"/>) is
sepcified in the following sections. <xref target="mastic-aux"/> defines some auxiliary
functions referenced in the sharding and preparation sections.</t>
      <section anchor="sharding">
        <name>Sharding</name>
        <t>The sharding algorithm takes in the measurement (the input and weight), the
nonce, and the sharding randomness. The size of the nonce is <tt>16</tt> bytes; the
size of the randomness is <tt>vidpf.RAND_SIZE + 2 * xof.SEED_SIZE</tt>, plus an
additional <tt>xof.SEED_SIZE</tt> if the validity circuit takes joint randomness as
input.</t>
        <t>The public share, denoted <tt>MasticPublicShare</tt>, is a tuple comprised of the list
correction words generated by the VIDPF key generation algorithm and the FLP
"joint randomness parts" (defined below) used during preparation to compute the
joint randomness.</t>
        <t>The contents of each input share, denoted <tt>MasticInputShare</tt>, depends on the
Aggregator who receives it. We refer to the first Aggregator as the "Leader";
we refer to the second Aggregator as the "Helper". The components of the input
share are:</t>
        <ol spacing="normal" type="1"><li>
            <t>The Aggregator's VIDPF key share.</t>
          </li>
          <li>
            <t>An optional FLP proof share, a vector of field elements. This is set for the
Leader only.</t>
          </li>
          <li>
            <t>An optional seed. This is always set for the Helper, who uses it to derive
its FLP proof share. This is set for the Leader of the circuit uses joint
randomness.</t>
          </li>
        </ol>
        <t>The behavior of the sharding algorithm depends on whether the circuit requires
joint randomness:</t>
        <sourcecode type="python"><![CDATA[
def shard(self,
          measurement: tuple[int, W],
          nonce: bytes,
          rand: bytes,
          ) -> tuple[MasticPublicShare, list[MasticInputShare]]:
    if self.flp.JOINT_RAND_LEN > 0:
        return self.shard_with_joint_rand(measurement, nonce, rand)
    return self.shard_without_joint_rand(measurement, nonce, rand)
]]></sourcecode>
        <t>When no FLP joint randomness is required, sharding involves the following
steps:</t>
        <ol spacing="normal" type="1"><li>
            <t>Encode the weight as <tt>beta</tt></t>
          </li>
          <li>
            <t>Generate the VIDPF correction words and keys for <tt>alpha</tt> and <tt>beta</tt></t>
          </li>
          <li>
            <t>Generate the FLP proof of <tt>beta</tt>'s validity</t>
          </li>
          <li>
            <t>Compute the Leader's share of the proof</t>
          </li>
        </ol>
        <t>The complete algorithm is listed below:</t>
        <sourcecode type="python"><![CDATA[
def shard_without_joint_rand(
        self,
        measurement: tuple[int, W],
        nonce: bytes,
        rand: bytes,
) -> tuple[MasticPublicShare, list[MasticInputShare]]:
    (vidpf_rand, rand) = front(self.vidpf.RAND_SIZE, rand)
    (prove_rand_seed, rand) = front(self.xof.SEED_SIZE, rand)
    (helper_seed, rand) = front(self.xof.SEED_SIZE, rand)

    (alpha, weight) = measurement
    beta = self.flp.encode(weight)

    # Generate VIDPF keys.
    (correction_words, keys) = \
        self.vidpf.gen(alpha, beta, nonce, vidpf_rand)

    # Generate FLP and split it into shares.
    prove_rand = self.prove_rand(prove_rand_seed)
    proof = self.flp.prove(beta, prove_rand, [])
    helper_proof_share = self.helper_proof_share(helper_seed)
    leader_proof_share = vec_sub(proof, helper_proof_share)

    public_share = (correction_words, None)
    input_shares = [
        (keys[0], leader_proof_share, None),
        (keys[1], None, helper_seed),
    ]
    return (public_share, input_shares)
]]></sourcecode>
        <t>When FLP joint randomness is required, the Client must compute it from the
shares of <tt>beta</tt> sent to each Aggregator:</t>
        <ol spacing="normal" type="1"><li>
            <t>Encode the weight as <tt>beta</tt></t>
          </li>
          <li>
            <t>Generate the VIDPF correction words and keys for <tt>alpha</tt> and <tt>beta</tt></t>
          </li>
          <li>
            <t>Compute each Aggregator's FLP joint randomness part by hashing its share of
<tt>beta</tt> with its FLP seed, its VIDPF key, and the VIDPF correction words</t>
          </li>
          <li>
            <t>Compute the FLP joint randomness seed by hashing the joint randomness parts</t>
          </li>
          <li>
            <t>Derive the FLP joint randomness from the joint randomness seed</t>
          </li>
          <li>
            <t>Generate the FLP proof of <tt>beta</tt>'s validity using the derived joint
randomness</t>
          </li>
          <li>
            <t>Compute the Leader's share of the proof</t>
          </li>
        </ol>
        <t>The joint randomness is also needed to verify the FLP and must therefore be
recomputed during preparation (<xref target="preparation"/>). The Client includes the parts
in the public share for this purpose.</t>
        <t>The complete algorithm is listed below:</t>
        <sourcecode type="python"><![CDATA[
def shard_with_joint_rand(
        self,
        measurement: tuple[int, W],
        nonce: bytes,
        rand: bytes,
) -> tuple[MasticPublicShare, list[MasticInputShare]]:
    (vidpf_rand, rand) = front(self.vidpf.RAND_SIZE, rand)
    (prove_rand_seed, rand) = front(self.xof.SEED_SIZE, rand)
    (leader_seed, rand) = front(self.xof.SEED_SIZE, rand)
    (helper_seed, rand) = front(self.xof.SEED_SIZE, rand)

    (alpha, weight) = measurement
    beta = self.flp.encode(weight)

    # Generate VIDPF keys.
    (correction_words, keys) = \
        self.vidpf.gen(alpha, beta, nonce, vidpf_rand)

    # Generate FLP joint randomness.
    joint_rand_parts = [
        self.joint_rand_part(
            0, leader_seed, keys[0], correction_words, nonce),
        self.joint_rand_part(
            1, helper_seed, keys[1], correction_words, nonce),
    ]
    joint_rand = self.joint_rand(
        self.joint_rand_seed(joint_rand_parts))

    # Generate FLP and split it into shares.
    prove_rand = self.prove_rand(prove_rand_seed)
    proof = self.flp.prove(beta, prove_rand, joint_rand)
    helper_proof_share = self.helper_proof_share(helper_seed)
    leader_proof_share = vec_sub(proof, helper_proof_share)

    public_share = (correction_words, joint_rand_parts)
    input_shares = [
        (keys[0], leader_proof_share, leader_seed),
        (keys[1], None, cast(Optional[bytes], helper_seed)),
    ]
    return (public_share, input_shares)
]]></sourcecode>
      </section>
      <section anchor="preparation">
        <name>Preparation</name>
        <t>Each Aggregator initializes preparation with: the verification key shared by
both Aggregators; its own ID, either <tt>0</tt> for the Leader and <tt>1</tt> for the Helper;
the aggregation parameter; the report's nonce; the public share sent to each
Aggregator; and the Aggregator's own input share.</t>
        <t>The aggregation parameter has the following components:</t>
        <ol spacing="normal" type="1"><li>
            <t>the level of the VIDPF being evaluated</t>
          </li>
          <li>
            <t>the sequence of VIDPF prefixes being evaluated</t>
          </li>
          <li>
            <t>an indication of whether to verify the FLP</t>
          </li>
        </ol>
        <t>The FLP is verified exactly once, the first time the report is aggregated. See
<xref target="agg-param-validity"/>.</t>
        <t>The outputs of the initialization algorithm include the Aggregator's prep
state, denoted <tt>MasticPrepState</tt>, and its outbound prep share, denoted
<tt>MasticPrepShare</tt>. The prep share includes the Aggregator's FLP verifier share,
joint randomness part, and VIDPF proof. These are combined into the prep
message in the next step.</t>
        <t>Preparation initialization involves the following steps:</t>
        <ol spacing="normal" type="1"><li>
            <t>Evaluate the VIDPF share on the sequence of prefixes, obtaining our share of
each corresponding payload. This step also produces our share of <tt>beta</tt>, to be
used to verify the FLP, and the VIDPF proof.</t>
          </li>
          <li>
            <t>If applicable, run the FLP query generation algorithm on our share of <tt>beta</tt>
and proof to obtain our FLP verifier share. If joint randomness is required,
then compute our joint randomness part and derive the joint randomness seed
using our co-Aggregator's part provided by the Client. Note that the Client
may have provided the wrong part, so we need to check that the seed was
computed correctly before completing preparation.</t>
          </li>
          <li>
            <t>Truncate each payload share according to the FLP encoding scheme and flatten
them into a single vector of field elements. This constitutes Mastic's
output share.</t>
          </li>
        </ol>
        <t>The complete algorithm is listed below:</t>
        <sourcecode type="python"><![CDATA[
def prep_init(
        self,
        verify_key: bytes,
        agg_id: int,
        agg_param: MasticAggParam,
        nonce: bytes,
        public_share: MasticPublicShare,
        input_share: MasticInputShare,
) -> tuple[MasticPrepState, MasticPrepShare]:
    (level, prefixes, do_weight_check) = agg_param
    (key, proof_share, seed) = \
        self.expand_input_share(agg_id, input_share)
    (correction_words, joint_rand_parts) = public_share

    # Evaluate the VIDPF.
    (beta_share, out_share, eval_proof) = self.vidpf.eval(
        agg_id,
        correction_words,
        key,
        level,
        prefixes,
        nonce,
    )

    # Query the FLP if applicable.
    joint_rand_part = None
    joint_rand_seed = None
    verifier_share = None
    if do_weight_check:
        query_rand = self.query_rand(verify_key, nonce, level)
        joint_rand = []
        if self.flp.JOINT_RAND_LEN > 0:
            assert seed is not None
            assert joint_rand_parts is not None
            joint_rand_part = self.joint_rand_part(
                agg_id, seed, key, correction_words, nonce)
            joint_rand_parts[agg_id] = joint_rand_part
            joint_rand_seed = self.joint_rand_seed(
                joint_rand_parts)
            joint_rand = self.joint_rand(
                self.joint_rand_seed(joint_rand_parts))
        verifier_share = self.flp.query(
            beta_share,
            proof_share,
            query_rand,
            joint_rand,
            2,
        )

    # Concatenate the output shares into one aggregatable output,
    # applying the FLP truncation algorithm on each FLP measurement
    # share.
    truncated_out_share = []
    for val_share in out_share:
        truncated_out_share += [val_share[0]] + \
            self.flp.truncate(val_share[1:])

    prep_state = (truncated_out_share, joint_rand_seed)
    prep_share = (eval_proof, verifier_share, joint_rand_part)
    return (prep_state, prep_share)
]]></sourcecode>
        <t>Next, the Aggregators' prep shares are combined into the prep message, denoted
<tt>MasticPrepMessage</tt>:</t>
        <ol spacing="normal" type="1"><li>
            <t>Check that both Aggregators computed the same VIDPF proof. If so, then it is
presumed that the output share is one-hot, has path consistency, and has
counter consistency as defined in <xref target="vidpf"/>.</t>
          </li>
          <li>
            <t>If applicable, combine the FLP verifier shares into the FLP verifier and run
the FLP decision algorithm. If successful, then it is presumed that the
weight is valid.</t>
          </li>
          <li>
            <t>If applicable, compute the FLP joint randomness seed from the parts.</t>
          </li>
        </ol>
        <t>The prep message consists of the optional joint randomness seed. The complete
algorithm is listed below:</t>
        <sourcecode type="python"><![CDATA[
def prep_shares_to_prep(
        self,
        agg_param: MasticAggParam,
        prep_shares: list[MasticPrepShare],
) -> MasticPrepMessage:
    (_level, _prefixes, do_weight_check) = agg_param

    if len(prep_shares) != 2:
        raise ValueError('unexpected number of prep shares')

    (eval_proof_0,
     verifier_share_0,
     joint_rand_part_0) = prep_shares[0]
    (eval_proof_1,
     verifier_share_1,
     joint_rand_part_1) = prep_shares[1]

    # Verify the VIDPF output.
    if eval_proof_0 != eval_proof_1:
        raise Exception('VIDPF verification failed')

    if not do_weight_check:
        return None
    if verifier_share_0 is None or verifier_share_1 is None:
        raise ValueError('expected FLP verifier shares')

    # Verify the FLP.
    verifier = vec_add(verifier_share_0, verifier_share_1)
    if not self.flp.decide(verifier):
        raise Exception('FLP verification failed')

    if self.flp.JOINT_RAND_LEN == 0:
        return None
    if joint_rand_part_0 is None or joint_rand_part_1 is None:
        raise ValueError('expected FLP joint randomness parts')

    # Confirm the FLP joint randomness was computed properly.
    prep_msg = self.joint_rand_seed([
        joint_rand_part_0,
        joint_rand_part_1,
    ])
    return prep_msg
]]></sourcecode>
        <t>Finally, each Aggregator completes preparation by checking that the true FLP
joint randomness seed is equal to the value they computed in the initialization
step, <tt>prep_init()</tt>. This is only done if a weight check was required by the
aggregation parameter and joint randomness was required by the FLP:</t>
        <sourcecode type="python"><![CDATA[
def prep_next(self,
              prep_state: MasticPrepState,
              prep_msg: MasticPrepMessage,
              ) -> list[F]:
    (truncated_out_share, joint_rand_seed) = prep_state
    if joint_rand_seed is not None:
        if prep_msg is None:
            raise ValueError('expected joint rand confirmation')

        if prep_msg != joint_rand_seed:
            raise Exception('joint rand confirmation failed')

    return truncated_out_share
]]></sourcecode>
      </section>
      <section anchor="agg-param-validity">
        <name>Validity of Aggregation Parameters</name>
        <t>To guarantee secure execution of Mastic, care must be taken in choosing the
VIDPF prefixes and whether to verify the FLP. In particular, it is only safe to
consume the FLP once; and it is only safe to evaluate the VIDPF at most once at
any given level of the tree.</t>
        <ul empty="true">
          <li>
            <t>NOTE By "safe" we mean "covered by the analysis of <xref target="MPDST24"/>". It could be
that we have a little more wiggle room, but we're not certain. If we find
matching attacks, we should mention them in <xref target="security-considerations"/>.</t>
          </li>
        </ul>
        <t>We further restrict aggregation by requiring that the level strictly increases
at each step:</t>
        <sourcecode type="python"><![CDATA[
def is_valid(self,
             agg_param: MasticAggParam,
             previous_agg_params: list[MasticAggParam],
             ) -> bool:
    (level, _prefixes, do_weight_check) = agg_param

    # Check that the weight check is done exactly once.
    weight_checked = \
        (do_weight_check and len(previous_agg_params) == 0) or \
        (not do_weight_check and
            any(agg_param[2] for agg_param in previous_agg_params))

    # Check that the level is strictly increasing.
    level_increased = len(previous_agg_params) == 0 or \
        level > previous_agg_params[-1][0]

    return weight_checked and level_increased
]]></sourcecode>
      </section>
      <section anchor="aggregation">
        <name>Aggregation</name>
        <t>Each output share consists of the truncated payload for each VIDPF prefix,
flattened into a single vector. Aggregation involves simply adding these up:</t>
        <sourcecode type="python"><![CDATA[
def aggregate(self,
              agg_param: MasticAggParam,
              out_shares: list[list[F]],
              ) -> list[F]:
    agg_share = self.empty_agg(agg_param)
    for out_share in out_shares:
        agg_share = vec_add(agg_share, out_share)
    return agg_share
]]></sourcecode>
      </section>
      <section anchor="unsharding">
        <name>Unsharding</name>
        <t>The aggregate result consists of a list of total weights, each corresponding to
one of the prefixes. To compute it:</t>
        <ol spacing="normal" type="1"><li>
            <t>Add up the aggregate shares.</t>
          </li>
          <li>
            <t>For each prefix, decode the corresponding vector chunk using the FLP's
decoding algorithm (<xref section="7.1.1" sectionFormat="of" target="VDAF"/>). This requires the
the prefix count, which is also encoded by the chunk.</t>
          </li>
        </ol>
        <t>The complete algorithm is listed below:</t>
        <sourcecode type="python"><![CDATA[
def unshard(self,
            agg_param: MasticAggParam,
            agg_shares: list[list[F]],
            _num_measurements: int,
            ) -> list[R]:
    agg = self.empty_agg(agg_param)
    for agg_share in agg_shares:
        agg = vec_add(agg, agg_share)

    agg_result = []
    while len(agg) > 0:
        (chunk, agg) = front(self.flp.OUTPUT_LEN + 1, agg)
        meas_count = chunk[0].as_unsigned()
        agg_result.append(self.flp.decode(chunk[1:], meas_count))
    return agg_result
]]></sourcecode>
      </section>
      <section anchor="mastic-aux">
        <name>Auxiliary Functions</name>
        <sourcecode type="python"><![CDATA[
def expand_input_share(
        self,
        agg_id: int,
        input_share: MasticInputShare,
) -> tuple[bytes, list[F], Optional[bytes]]:
    if agg_id == 0:
        (key, proof_share, seed) = input_share
        assert proof_share is not None
    else:
        (key, _leader_proof_share, seed) = input_share
        assert seed is not None
        proof_share = self.helper_proof_share(seed)
    return (key, proof_share, seed)

def helper_proof_share(self, seed: bytes) -> list[F]:
    return self.xof.expand_into_vec(
        self.field,
        seed,
        dst(USAGE_PROOF_SHARE),
        b'',
        self.flp.PROOF_LEN,
    )

def prove_rand(self, seed: bytes) -> list[F]:
    return self.xof.expand_into_vec(
        self.field,
        seed,
        dst(USAGE_PROVE_RAND),
        b'',
        self.flp.PROVE_RAND_LEN,
    )

def joint_rand_part(
        self,
        agg_id: int,
        seed: bytes,
        key: bytes,
        correction_words: list[CorrectionWord],
        nonce: bytes,
) -> bytes:
    pub = self.vidpf.encode_public_share(correction_words)
    return self.xof.derive_seed(
        seed,
        dst(USAGE_JOINT_RAND_PART),
        byte(agg_id) + nonce + key + pub,
    )

def joint_rand_seed(self, parts: list[bytes]) -> bytes:
    return self.xof.derive_seed(
        zeros(self.xof.SEED_SIZE),
        dst(USAGE_JOINT_RAND_SEED),
        concat(parts),
    )

def joint_rand(self, seed: bytes) -> list[F]:
    return self.xof.expand_into_vec(
        self.field,
        seed,
        dst(USAGE_JOINT_RAND),
        b'',
        self.flp.JOINT_RAND_LEN,
    )

def query_rand(self,
               verify_key: bytes,
               nonce: bytes,
               level: int) -> list[F]:
    return self.xof.expand_into_vec(
        self.field,
        verify_key,
        dst(USAGE_QUERY_RAND),
        nonce + to_le_bytes(level, 2),
        self.flp.QUERY_RAND_LEN,
    )

def empty_agg(self, agg_param: MasticAggParam) -> list[F]:
    (_level, prefixes, _do_weight_check) = agg_param
    agg = self.field.zeros(len(prefixes)*(1+self.flp.OUTPUT_LEN))
    return agg
]]></sourcecode>
      </section>
    </section>
    <section anchor="security-considerations">
      <name>Security Considerations</name>
      <t>Mastic inherits its security considerations from <xref section="9" sectionFormat="of" target="VDAF"/>. A
security analysis of Mastic is provided in <xref target="MPDST24"/>.</t>
      <ul empty="true">
        <li>
          <t>TODO Contrast with Poplar1, especially <xref section="9.4.2" sectionFormat="of" target="VDAF"/> ("Safe
Usage of IDPF Outputs"). In particular it's perfectly safe to use Mastic's
intermediate outputs.</t>
        </li>
      </ul>
    </section>
    <section anchor="iana-considerations">
      <name>IANA Considerations</name>
      <t>TODO</t>
    </section>
  </middle>
  <back>
    <references anchor="sec-combined-references">
      <name>References</name>
      <references anchor="sec-normative-references">
        <name>Normative References</name>
        <reference anchor="VDAF">
          <front>
            <title>Verifiable Distributed Aggregation Functions</title>
            <author fullname="Richard Barnes" initials="R." surname="Barnes">
              <organization>Cisco</organization>
            </author>
            <author fullname="David Cook" initials="D." surname="Cook">
              <organization>ISRG</organization>
            </author>
            <author fullname="Christopher Patton" initials="C." surname="Patton">
              <organization>Cloudflare</organization>
            </author>
            <author fullname="Phillipp Schoppmann" initials="P." surname="Schoppmann">
              <organization>Google</organization>
            </author>
            <date day="22" month="August" year="2024"/>
            <abstract>
              <t>   This document describes Verifiable Distributed Aggregation Functions
   (VDAFs), a family of multi-party protocols for computing aggregate
   statistics over user measurements.  These protocols are designed to
   ensure that, as long as at least one aggregation server executes the
   protocol honestly, individual measurements are never seen by any
   server in the clear.  At the same time, VDAFs allow the servers to
   detect if a malicious or misconfigured client submitted an
   measurement that would result in an invalid aggregate result.

              </t>
            </abstract>
          </front>
          <seriesInfo name="Internet-Draft" value="draft-irtf-cfrg-vdaf-11"/>
        </reference>
        <reference anchor="RFC2119">
          <front>
            <title>Key words for use in RFCs to Indicate Requirement Levels</title>
            <author fullname="S. Bradner" initials="S." surname="Bradner"/>
            <date month="March" year="1997"/>
            <abstract>
              <t>In many standards track documents several words are used to signify the requirements in the specification. These words are often capitalized. This document defines these words as they should be interpreted in IETF documents. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.</t>
            </abstract>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="2119"/>
          <seriesInfo name="DOI" value="10.17487/RFC2119"/>
        </reference>
        <reference anchor="RFC8174">
          <front>
            <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
            <author fullname="B. Leiba" initials="B." surname="Leiba"/>
            <date month="May" year="2017"/>
            <abstract>
              <t>RFC 2119 specifies common key words that may be used in protocol specifications. This document aims to reduce the ambiguity by clarifying that only UPPERCASE usage of the key words have the defined special meanings.</t>
            </abstract>
          </front>
          <seriesInfo name="BCP" value="14"/>
          <seriesInfo name="RFC" value="8174"/>
          <seriesInfo name="DOI" value="10.17487/RFC8174"/>
        </reference>
      </references>
      <references anchor="sec-informative-references">
        <name>Informative References</name>
        <reference anchor="BGI15" target="https://www.iacr.org/archive/eurocrypt2015/90560300/90560300.pdf">
          <front>
            <title>Function Secret Sharing</title>
            <author initials="E." surname="Boyle">
              <organization/>
            </author>
            <author initials="N." surname="Gilboa">
              <organization/>
            </author>
            <author initials="Y." surname="Ishai">
              <organization/>
            </author>
            <date year="2015"/>
          </front>
          <seriesInfo name="EUROCRYPT 2015" value=""/>
        </reference>
        <reference anchor="CP22" target="https://iacr.org/cryptodb/data/paper.php?pubkey=31935">
          <front>
            <title>Lightweight, Maliciously Secure Verifiable Function Secret Sharing</title>
            <author initials="L." surname="de Castro">
              <organization/>
            </author>
            <author initials="A." surname="Polychroniadou">
              <organization/>
            </author>
            <date year="2022"/>
          </front>
          <seriesInfo name="EUROCRYPT 2022" value=""/>
        </reference>
        <reference anchor="MST24" target="https://ia.cr/2023/080">
          <front>
            <title>PLASMA: Private, Lightweight Aggregated Statistics against Malicious Adversaries</title>
            <author initials="D." surname="Mouris">
              <organization/>
            </author>
            <author initials="P." surname="Sarkar">
              <organization/>
            </author>
            <author initials="N. G." surname="Tsoutsos">
              <organization/>
            </author>
            <date year="2024"/>
          </front>
          <seriesInfo name="PETS 2024" value=""/>
        </reference>
        <reference anchor="MPDST24" target="https://ia.cr/2024/221">
          <front>
            <title>Mastic: Private Weighted Heavy-Hitters and Attribute-Based Metrics</title>
            <author initials="D." surname="Mouris">
              <organization/>
            </author>
            <author initials="C." surname="Patton">
              <organization/>
            </author>
            <author initials="H." surname="Davis">
              <organization/>
            </author>
            <author initials="P." surname="Sarkar">
              <organization/>
            </author>
            <author initials="N. G." surname="Tsoutsos">
              <organization/>
            </author>
            <date year="2025"/>
          </front>
          <seriesInfo name="PETS 2025" value=""/>
        </reference>
        <reference anchor="RZCGP24" target="https://eprint.iacr.org/2024/666">
          <front>
            <title>Private Analytics via Streaming, Sketching, and Silently Verifiable Proofs</title>
            <author initials="M." surname="Rathee">
              <organization/>
            </author>
            <author initials="Y." surname="Zhang">
              <organization/>
            </author>
            <author initials="H." surname="Corrigan-Gibbs">
              <organization/>
            </author>
            <author initials="R. A." surname="Popa">
              <organization/>
            </author>
            <date year="2024"/>
          </front>
          <seriesInfo name="IEEE S&amp;P 2024" value=""/>
        </reference>
        <reference anchor="W3C23" target="https://www.w3.org/TR/network-error-logging">
          <front>
            <title>Network Error Logging</title>
            <author initials="" surname="W3C Working Group">
              <organization/>
            </author>
            <date year="2023"/>
          </front>
        </reference>
        <reference anchor="RFC9110">
          <front>
            <title>HTTP Semantics</title>
            <author fullname="R. Fielding" initials="R." role="editor" surname="Fielding"/>
            <author fullname="M. Nottingham" initials="M." role="editor" surname="Nottingham"/>
            <author fullname="J. Reschke" initials="J." role="editor" surname="Reschke"/>
            <date month="June" year="2022"/>
            <abstract>
              <t>The Hypertext Transfer Protocol (HTTP) is a stateless application-level protocol for distributed, collaborative, hypertext information systems. This document describes the overall architecture of HTTP, establishes common terminology, and defines aspects of the protocol that are shared by all versions. In this definition are core protocol elements, extensibility mechanisms, and the "http" and "https" Uniform Resource Identifier (URI) schemes.</t>
              <t>This document updates RFC 3864 and obsoletes RFCs 2818, 7231, 7232, 7233, 7235, 7538, 7615, 7694, and portions of 7230.</t>
            </abstract>
          </front>
          <seriesInfo name="STD" value="97"/>
          <seriesInfo name="RFC" value="9110"/>
          <seriesInfo name="DOI" value="10.17487/RFC9110"/>
        </reference>
        <reference anchor="SHS">
          <front>
            <title>Secure hash standard</title>
            <author>
              <organization/>
            </author>
            <date year="2015"/>
          </front>
          <seriesInfo name="DOI" value="10.6028/nist.fips.180-4"/>
          <refcontent>National Institute of Standards and Technology (U.S.)</refcontent>
        </reference>
        <reference anchor="DAP">
          <front>
            <title>Distributed Aggregation Protocol for Privacy Preserving Measurement</title>
            <author fullname="Tim Geoghegan" initials="T." surname="Geoghegan">
              <organization>ISRG</organization>
            </author>
            <author fullname="Christopher Patton" initials="C." surname="Patton">
              <organization>Cloudflare</organization>
            </author>
            <author fullname="Eric Rescorla" initials="E." surname="Rescorla">
              <organization>Mozilla</organization>
            </author>
            <author fullname="Christopher A. Wood" initials="C. A." surname="Wood">
              <organization>Cloudflare</organization>
            </author>
            <date day="14" month="September" year="2023"/>
            <abstract>
              <t>   There are many situations in which it is desirable to take
   measurements of data which people consider sensitive.  In these
   cases, the entity taking the measurement is usually not interested in
   people's individual responses but rather in aggregated data.
   Conventional methods require collecting individual responses and then
   aggregating them, thus representing a threat to user privacy and
   rendering many such measurements difficult and impractical.  This
   document describes a multi-party distributed aggregation protocol
   (DAP) for privacy preserving measurement (PPM) which can be used to
   collect aggregate data without revealing any individual user's data.

              </t>
            </abstract>
          </front>
          <seriesInfo name="Internet-Draft" value="draft-ietf-ppm-dap-07"/>
        </reference>
      </references>
    </references>
    <?line 1250?>

<section numbered="false" anchor="acknowledgments">
      <name>Acknowledgments</name>
      <t>TODO</t>
    </section>
    <section numbered="false" anchor="motivation">
      <name>Motivating Applications</name>
      <t>The design of Mastic is informed primarily by two use cases, which we describe
here.</t>
      <section numbered="false" anchor="NEL">
        <name>Network Error Logging</name>
        <t>Network Error Logging (NEL) is a mechanism used by web browsers to report errors
that occur while attempting to establish a connection to a server <xref target="W3C23"/>. Some
of these errors are visible to the server, but not all: failures in DNS, TCP,
TLS, and HTTP can occur without the server having any visibility into the issue.
A small amount of connection errors is expected, even under normal operating
conditions; but a sudden, substantial increase in errors may be an indication of
an outage, or a configuration issue impacting millions of users. Without a
reporting mechanism like NEL, these events would only manifest in the server's
telemetry as a drop in overall traffic.</t>
        <t>NEL is particularly important for content delivery networks that handle HTTP
traffic for a large number of websites (typically millions). A content delivery
network acts as a reverse proxy between clients and origin servers that provides
a layer of caching and security services, such as DDoS protection.</t>
        <t>Reports are comprised of the URL the client attempted to navigate to (e.g.,
"https://example.com"), the type of error that occurred, and metadata related to
the attempt, such as the time that elapsed between when the connection attempt
began and the error was observed (e.g., Section 7 of <xref target="W3C23"/>). Clients may
also report successful connection attempts to give the server a sense of the
error rate. The exact client behavior is determined by the reporting policy
specified by the server (see Section 5.1 of <xref target="W3C23"/>).</t>
        <t>NEL data is privacy-sensitive for two reasons. First, it exposes information
that the server would not otherwise have access to, which can be abused to probe
the client's network configuration as described in Section 9 of <xref target="W3C23"/>.
Second, for operational reasons, the reporting endpoint may be organizationally
separated from the server (i.e., run on different cloud infrastructures),
leading to an increased risk of the client's browsing history being exposed
(e.g., in a data breach).</t>
        <t>MPC helps mitigate these risks by revealing to the endpoint only the information
it needs to fulfill its service level objectives. This means, of course, we must
be satisfied with limited functionality. Fortunately, Mastic allows us to
preserve the most important functionality of NEL while minimizing privacy loss.</t>
        <t>Mastic can be applied to a simplified version of NEL where each client reports
a tuple <tt>(dom, err)</tt> consisting of a domain name dom (e.g., "example.com") and
a value err that represents an error (e.g., "dns.unreachable") or an indication
that no error occurred (e.g., "ok"). Notably, this can be easily extended in
Mastic to represent more elaborate metrics. e.g., where each weight includes
the time it took each browser to report the error (and the aggregate is the
average error reporting time), user agent (browser type and version), etc.
However, our main goal is to understand 1) the distribution of errors and 2)
which domains are impacted.</t>
        <t>We expect there to be a large number of distinct domain names (millions in the
case of content delivery networks) and only a small number of error variants
(the NEL spec <xref target="W3C23"/> defines 30 variants). The following Mastic parameters
are suitable for this application.</t>
        <t>Each input would encode the domain <tt>dom</tt> encoded with a number of bits
sufficient to uniquely represent most of the domains; and each weight would
represent the error variant <tt>dom</tt>. To compute the distribution of errors, we
would encode each error variant as a distinct bucket of a histogram so that
<tt>[1, 0, 0, ...]</tt> represents "ok", <tt>[0, 1, 0, ...]</tt> represents
"dns.unreachable", and so on. (See ection 6 of <xref target="W3C23"/>.), This is similar to
Prio3Histogram (<xref section="7" sectionFormat="of" target="VDAF"/>.)</t>
      </section>
      <section numbered="false" anchor="attribute-based-telemetry">
        <name>Attribute-Based Browser Telemetry</name>
        <t>Web browsers collect telemetry generated by users as they navigate the web to
gain insights into trends that guide product decisions. In many cases, Prio3
(<xref section="7" sectionFormat="of" target="VDAF"/>) can be used to privately aggregate this telemetry.
However, this comes at the cost of flexibility.</t>
        <t>For example, Prio3 can be used to collect page load metrics from Browser for a
list of known popular sites (e.g., "example.com"). The purpose of these metrics
is to detect if changes to these sites cause regressions that might be
correlated with an increased average load time or error rate. A subtle, but
important requirement for this system is the ability to break down the metrics
by client attributes. Suppose for example that we want to aggregate by 1) the
software version, and 2) the information about the client's location.</t>
        <t>Mastic provides a simple solution to this problem. For the sake of presentation,
we consider a simplified use case (the same approach can be applied to any
aggregation task for which Prio3 (<xref section="7" sectionFormat="of" target="VDAF"/>) is suitable). Each
client reports a tuple <tt>(ver, loc, site, time)</tt> where: <tt>ver</tt> is a string
representing the client's software version (e.g., "Browser/122.0"); <tt>loc</tt> is a
string encoding its country code (e.g., "GR", "US", "IN", etc.); <tt>site</tt> is one
of a fixed set of sites (e.g., "example.com", "example.org", etc.); and <tt>time</tt>
is the load time of the site in seconds. The version and location are included
in the Mastic input; the site and load time are encoded by the corresponding
weight. Notably, this is just one example of what Mastic can do; the same idea
can be applied to other types of metrics.</t>
        <t>Compared to the private NEL application in <xref target="NEL"/>, the number of possible
inputs here is relatively small: there are less than 200 country codes and a
handful of browser versions in wide use at any given time. This means the
aggregators can enumerate a set of inputs of interest and evaluate them
immediately. Consider the following parameters for Mastic, in its
attribute-based metrics mode of operation <xref target="attribute-based-metrics"/>:</t>
        <ul spacing="normal">
          <li>
            <t>Attributes: Two-letter country codes can easily be encoded in 2 bytes.
Likewise, the number of distinct browser versions is easily less than 216, so
2 bytes are sufficient. Therefore, each attribute can be encoded with just
<tt>32</tt> bits.</t>
          </li>
          <li>
            <t>Values: Similar to private NEL, each weight is a <tt>0</tt>-vector except for a
single <tt>1</tt> representing a bucket in a histogram. We represent <tt>(site, time)</tt>
as a histogram bucket as follows. First, we quantize time (in seconds) into
one of four buckets: <tt>[0, 0.1)</tt>, <tt>[0.1, 1)</tt>, <tt>[1, 5)</tt>, and <tt>[5, inf)</tt>. Let <tt>0
&lt; t &lt;= 4</tt> denote the time bucket for <tt>time</tt>. Next, suppose we wish to track
metrics for <tt>25</tt> sites. Let <tt>0 &lt; s &lt;= 25</tt> denote the index of <tt>site</tt> in this
list. Then the index of 1 is simply <tt>t * s</tt>.</t>
          </li>
        </ul>
      </section>
    </section>
    <section numbered="false" anchor="additional-modes">
      <name>Modes of Operation</name>
      <section numbered="false" anchor="weighted-heavy-hitters">
        <name>Weighted Heavy-Hitters</name>
        <ul empty="true">
          <li>
            <t>TODO See <xref target="NEL"/> for a motivating application and
<tt>example_weighted_heavy_hitters_mode()</tt> in the reference implementation for
an end-to-end example.</t>
          </li>
        </ul>
        <t>The primary use case for Mastic is a variant of the heavy-hitters problem, in
which the prefix counts are replaced with a notion of weight that is specific to
some application. For example, when measuring the performance of an ad campaign,
it is useful to learn not only which ads led to purchases, but how much money
was spent.</t>
        <t>To support this use case, we view the Client's <tt>alpha</tt> value as its measurement
and the <tt>beta</tt> value as the measurement's "weight". The range of valid values
for <tt>beta</tt> are therefore determined by the FLP with which Mastic is
instantiated. Concretely, validity of <tt>beta</tt> is expressed by a validity circuit
(<xref section="7.3.2" sectionFormat="of" target="VDAF"/>).</t>
        <t>To compute the weighted heavy-hitters, the Collector and Aggregators proceed as
described in <xref section="8" sectionFormat="of" target="VDAF"/>, except that the threshold represents a
minimum weight rather than a minimum count. In addition:</t>
        <ol spacing="normal" type="1"><li>
            <t>The Aggregators <bcp14>MUST</bcp14> perform the range check (i.e., verify the FLP) at the
first round of aggregation and remove any invalid reports before proceeding.</t>
          </li>
          <li>
            <t>The level at which the reports are Aggregated <bcp14>MUST</bcp14> be strictly increasing.</t>
          </li>
        </ol>
        <section numbered="false" anchor="different-thresholds">
          <name>Different Thresholds</name>
          <ul empty="true">
            <li>
              <t>NOTE For an end-to-end example, see
<tt>example_weighted_heavy_hitters_mode_with_different_thresholds()</tt> in the
reference implementation.</t>
            </li>
          </ul>
          <t>So far, we have assumed that there is a single threshold for determining which
prefixes are "heavy". However, we can easily extend this to have different
thresholds for different prefixes. There exist use-cases where prefixes starting
with "000" may be significantly more popular than prefixes starting with "111".
Setting a low threshold may result in an overwhelmingly big set of heavy hitters
starting with "000", while setting a high threshold might prune anything
starting with "111". Consider the following examples:</t>
          <ol spacing="normal" type="1"><li>
              <t>Popular URLs: <tt>a.example.com</tt> receives a massive amount of traffic whereas
<tt>b.example.com</tt> may have lower traffic. To identify heavy-hitting search
queries on <tt>a.example.com</tt>, the Aggregators should set a high threshold,
while queries with different domain prefixes may require lower thresholds to
be considered popular.</t>
            </li>
            <li>
              <t>E-commerce: Grocery items are essential and have a high volume of sales. In
contrast, electronics, though popular, usually come with a higher price
compared to groceries. Meanwhile, luxury items command significantly higher
prices but generally experience lower sales volumes. To identify
heavy-hitting grocery items on an e-commerce website, Aggregators could use
different threshold for each of these categories. These thresholds are set to
ensure that only the top-selling grocery items qualify as heavy hitters while
electronics and luxury items are also considered heavy hitters on their own
categories.</t>
            </li>
          </ol>
          <t>To tackle this, Mastic can allow different prefixes having different thresholds.
When a specific prefix does not have an associated threshold, we first search if
any of its prefixes has a specified threshold, otherwise we use a default
threshold. For example, if the Aggregators have set the thresholds to be
<tt>{"000": 10, "111": 2, "default": 5}</tt> and the search for prefix "01", then
threshold 5 should be used. However, if the Aggregators search for prefix
"11101", then threshold 2 should be used.</t>
        </section>
      </section>
      <section numbered="false" anchor="attribute-based-metrics">
        <name>Attribute-based Metrics</name>
        <ul empty="true">
          <li>
            <t>NOTE See <xref target="attribute-based-telemetry"/> for a motivating application and
<tt>example_attribute_based_metrics_mode()</tt> in the reference implementation for
an end-to-end example.</t>
          </li>
        </ul>
        <t>In this mode of operation, we take the <tt>beta</tt> value to be the Client's
measurement and <tt>alpha</tt> to be an arbitrary "attribute". For a given sequence of
attributes, the goal of the Collector is to aggregate the measurements that
share the same attribute. This provides functionality similar to Prio3
<xref target="VDAF"/>, except that the aggregate is partitioned by Clients who share some
property. For example, the attribute might encode the Client's user agent
<xref target="RFC9110"/>.</t>
        <t>Mastic requires each <tt>alpha</tt> to have the same length (<tt>Vidpf.BITS</tt>). Thus, it
is necessary for each application to choose a scheme for encoding attributes as
fixed-length strings. The following scheme is <bcp14>RECOMMENDED</bcp14>. Choose a
cryptographically secure hash function, such as SHA256
<xref target="SHS"/>, compute the hash of the Client's input
string, and interpret each bit of the hash as a bit of the VIDPF index.</t>
        <ul empty="true">
          <li>
            <t>TODO Are we comfortable recommending truncating the hash? Collisions aren't so
bad since the Client can just lie about <tt>alpha</tt> anyway. The main thing is to
pick a value for <tt>BITS</tt> that is large enough to avoid accidental collisions.</t>
          </li>
        </ul>
        <t>The Aggregators <bcp14>MAY</bcp14> aggregate a report any number times, but:</t>
        <ol spacing="normal" type="1"><li>
            <t>They <bcp14>MUST</bcp14> perform the range check (i.e., verify the FLP) the first time the
reports are aggregated and remove any invalid reports before aggregating
again.</t>
          </li>
          <li>
            <t>The aggregation parameter <bcp14>MUST</bcp14> specify the last level of the VIDPF tree
(i.e., <tt>level</tt> <bcp14>MUST</bcp14> be <tt>Vidpf.BITS-1</tt>).</t>
          </li>
        </ol>
        <ul empty="true">
          <li>
            <t>TODO Figure out if these requirements are strict enough. We may need to
tighten aggregation parameter validity if we find out that aggregating at the
same level more than once is not safe.</t>
          </li>
        </ul>
      </section>
      <section numbered="false" anchor="plain-heavy-hitters-with-proof-aggregation">
        <name>Plain Heavy-Hitters with VIDPF-Proof Aggregation</name>
        <ul empty="true">
          <li>
            <t>TODO Account for "silently verifiable proofs" from <xref target="RZCGP24"/> into account
here, which allows us to aggregate the FLPs as well.</t>
          </li>
        </ul>
        <t>The total communication cost of using Mastic (or Poplar1 <xref target="VDAF"/>) for heavy
hitters is <tt>O(num_measurements * Vidpf.BITS)</tt> bits exchanged between the
Aggregators, where <tt>num_measurements</tt> is the number of reports being
aggregated. For plain heavy-hitters, this can be reduced to <tt>O(Vidpf.BITS)</tt> in
the best case.</t>
        <t>The idea is to take advantage of the feature of VIDPF evaluation whereby the
Aggregators compute identical VIDPF proofs if and only if the report is valid.
This allows the proofs themselves to be aggregated: if each report in a batch of
reports is valid, then the hash of their proofs will be equal as well; on the
other hand, if one report is invalid, then the hash of the proofs will not be
equal.</t>
        <t>To facilitate isolation of the invalid report(s), the proof strings are arranged
into a Merkle tree. During aggregation, the Aggregators interactively traverse
the tree to detect the subtree(s) containing invalid reports and remove them
from the batch.</t>
        <ul empty="true">
          <li>
            <t>TODO Decide if we should spell this out in greater detail. This feature
is not compatible with <xref target="DAP"/>; if we wanted to
extend DAP to support this, then we'd need to specify the wire format of the
messages exchanged between the Aggregators.</t>
          </li>
        </ul>
        <t>In the worst case, isolating invalid reports requires <tt>O(num_measurements *
Vidpf.BITS)</tt> bits of communication and many <tt>Vidpf.BITS</tt> rounds of communication
between the Aggregators. However, this behavior would only be observed under
attack conditions in which the vast majority of Clients are malicious.</t>
        <t>In the simple case where the <tt>beta</tt> value is a constant (e.g., 1) we can replace
the FLP check with a simpler check. FLPs are not compatible with proof
aggregation the way VIDPFs are. In order to perform the range check without
FLPs, we use an extension of VIDPF described by <xref target="MST24"/>. The high-level idea
here is that the Aggregators can evaluate the empty string and verify that they
have shares of the constant <tt>beta</tt>. Next, as described in <xref target="vdaf"/>, we use the
"one-hot verifiability" and "path verifiability" checks to verify that each
level is non-zero at only a single point and that the same constant <tt>beta</tt> is
propagated down the tree correctly. Note that this trick is not suitable for
weighted heavy-hitters, since it expects that each <tt>beta</tt> value is constant
(e.g., 1).</t>
        <ul empty="true">
          <li>
            <t>TODO Proof aggregation could work with plain Mastic, but we would need
to check the FLPs at the first round of aggregation, leading to best-case
communication cost would be <tt>O(num_measurements + Vidpf.BITS)</tt>. This would be
OK, but we would still want to support a mode for plain heavy-hitters that is
as good as we can get.</t>
            <t>One idea is to always do the PLASMA <tt>0</tt>/<tt>1</tt> check alongside the FLP. This
would be useful for another reason: Usually FLP decoding requires
<tt>num_measurements</tt> as a parameter. We currently don't support this because we
currently don't have a pure counter as part of the VIDPF output.</t>
          </li>
        </ul>
      </section>
      <section numbered="false" anchor="malicious-security-with-three-aggregators">
        <name>Robustness Against a Malicious Aggregator</name>
        <t>Next, we describe an enhancement that allows Mastic to achieve robustness in the
presence of a malicious Aggregator. The two-party Mastic (as well as Poplar1) is
susceptible to additive attacks by a malicious Aggregator. In more detail, if
one of the Aggregators starts acting maliciously, they can arbitrarily add to
the aggregation result (simply by adding to their own aggregation shares)
without the honest Aggregator noticing.</t>
        <t>We can solve this problem in Mastic by using a technique from <xref target="MST24"/> that
lifts the two-party semi-honest secure PLASMA to the three-party maliciously secure
setting. Rather than having two Aggregators as in the previous setting, this
flavor involves three Aggregators, where every pair of Aggregators communicate
over a different channel. In essence, each pair of Aggregators will run one
session of the VDAF with unique randomness but on the same Client measurement.
The following changes are necessary:</t>
        <ol spacing="normal" type="1"><li>
            <t>The Client needs to generate three pairs of VIDPF keys all corresponding to
the same <tt>alpha</tt> and <tt>beta</tt> values. We represent the keys based on the
session as follows:
            </t>
            <ol spacing="normal" type="1"><li>
                <t>Session 0 (between Aggregators 0 and 1): <tt>key_01, key_10</tt></t>
              </li>
              <li>
                <t>Session 1 (between Aggregators 1 and 2): <tt>key_12, key_21</tt></t>
              </li>
              <li>
                <t>Session 2 (between Aggregators 2 and 0): <tt>key_20, key_02</tt></t>
              </li>
            </ol>
            <t>
Each pair of Aggregators cannot check that the Client input is consistent
across two sessions without the involvement of the third Aggregator. To
address this, we let two Aggregators (i.e., Aggregators 0 and 1) to run all
three sessions so that they can check that the Client input is consistent
across three sessions. The third Aggregator (i.e., Aggregator 2) is involved
as an attestator in two of the sessions. The check involves field addition
and subtraction and then hash comparisons.</t>
          </li>
          <li>
            <t>The Client sends the following keys to the Aggregators:
            </t>
            <ol spacing="normal" type="1"><li>
                <t>Aggregator 0 receives: <tt>key_01</tt>, <tt>key_02</tt>, and <tt>key_21</tt></t>
              </li>
              <li>
                <t>Aggregator 1 receives: <tt>key_10</tt>, <tt>key_12</tt>, and <tt>key_20</tt></t>
              </li>
              <li>
                <t>Aggregator 2 receives: <tt>key_21</tt> and <tt>key_20</tt></t>
              </li>
            </ol>
          </li>
          <li>
            <t>The Aggregators need to verify that the Client's input is consistent across
the different sessions (i.e., that all the keys correspond to the same
<tt>alpha</tt> and <tt>beta</tt> values). Aggregators 0 and 1 check that:
            </t>
            <ol spacing="normal" type="1"><li>
                <t>Their output shares of Session 0 minus their output shares of Session 1
 are shares of zero</t>
              </li>
              <li>
                <t>Their output shares of Session 1 minus their output shares of Session 2
are shares of zero.</t>
              </li>
            </ol>
            <t>
The subtraction is a local operation and verifying that two Aggregators possess
a sharing of zero requires exchanging one hash.</t>
          </li>
        </ol>
        <t>Using a third Aggregator, we can lift the security of Mastic from the
semi-honest setting to malicious security. While more complex to implement than
2-party Mastic, this mode allows achieves both privacy and robustness against a
malicious Aggregator.</t>
      </section>
    </section>
    <section numbered="false" anchor="test-vectors">
      <name>Test Vectors</name>
      <t>TODO</t>
    </section>
  </back>
  <!-- ##markdown-source:
H4sIAAAAAAAAA+29/XbbVpIv+j+eAiOvNSZjkhblOJ0obXcrlhz7jD80lpx0
j9sRQRKS0CYBDgCKVjueZ7nPcp/s1K+q9hcAykp61pl71zpa3bEEYH/Vru+q
XXs4HEZ1Vi/S/Xjn9DKNXyZVnc3inw4Pnu5EyXRaplf0Rp7uRLOkTi+K8no/
zvLzIormxSxPltR2Xibn9XBZrMusGs7Oy4vhkpsMdx9E1Xq6zKoqK/L6ekXf
Pn9z+jTK18tpWu5Hc+pxP5oVeZXm1braj+tynUY05oMoKdOExsbnO9GmKD9c
lMV6RU+elNeruoifFuV6uRN9SK/p5Xw/iuNh/Dyv0zJP6+EhJhRdpfk6xZvu
pnEsM9p5k1ZpUs4u4x/xHV4sk2xBL7CUP2dpfT4qygs8x1f0/LKuV9X+/fv4
DI+yq3RkPruPB/elw/t/SvHJ2SKr6kfoDH1cZPXlekq9/D0TiN3fBr6dKErW
9WVBgIqH1DKOz9eLhYD8WZLnyWV8mFxlFb+ioZM8+0dSE6T345M0uSDY8ptU
VnPJLUbpaI42f67ki9GsWLZ7P8yWWU2ziV/ypDoGeJUtFvSLP8BcG/05l3d+
19Ltk0t6XRery7SMj5O61vZhz08WxXp+TnANZj9D0xW3uQdQ//kCz7tnf1xS
Vx/ik6T8kJRdwFmvyiQ2u+6PsuKW3z3483Q9Sufrdtev0g91UmZFFf84ik+r
Yl1XRRd43uaEE2WV1ddxcR4fpotk01hQrY3/vJ6nCx4syotySe2vCGcjUJj7
K45/+PH5+OE+d2AI9uk6n2E02uxZmdbxySXNLGccI0w1iMM/Q6JYoq6jUfxD
cb1Iw6evRvGP2WJaJOHjv47i59VlkvFTJtR4b3f8kP+s0jJLK8yROn375vWT
N389PnWvCUQXaU0op3Sy2WxGWTIrLX3Qqu6n67KYgSDR7P53uw+/2X2wu2t/
Ga3m51j4k+O9vXDdL7KLy3qT4r8D4liLbJYV62pxDTisyzT+iSZ3niXTRRr/
dhC9GMXzNH5C9FcW4ZuDUXxcLK4JEYs8S+bF2gImBmT29m6EjL5uQsZChSFR
zKf3qcPk/ipZpeVodbn602o9JRb36MH4uwcPAY6XJ6d7X4fwOH5xcPLyAGif
XdFsBrEHoPjg4qJMQejz+KQmdAJfqWIifVpT7aAXH8yBrwkmfwN0Dkc+R7CP
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
DAJadL4w4h5ncPL1OtuavRhsfev3uv2rptx5l73f/jEL+hv6Muvt/qTf+bQF
une2G3A4/sPwf3/fbxL5sk+b3S5EbfJVeH53++9HG2kzvlWbsd8GQ8p3nqW7
2WVD9t14/31Db3j0KB47fGg1hjnrHvbd0jXZAYi430xrEF4WnLXw45jah02K
Goci8Ch0QrrCQHIk9qqR86H0wcmCm1T78JqPianyyRKmQW7J2SDzOb3yEgyv
2UrV9t02pNC1Dqjbqn+dCQiUWiTDVZIbzgiMjmjebdhHd492Q/71XA8qEd9b
KB/75L4fZm4NWrkkjeQr7SNMOgMMbIaZyHtpZuc/1ajel2RplzBuMJbbM/gw
qMOY34n4oEJFc/thN1k1eXmrWTdltRl+o10Irntf3G/zY1xNZNU3aLLf7+In
gZXpWwuya/TEkqkXbLtpw27ajYHIabcFtwK/nYRxZGxCvrIrwSzrFeteacvA
gTeJHUa+W7/piLrjkaWLBFxTG+jcmkYiMsf4tnxmYdhEmGNhNGBRQlnkiW8n
EF4hyQ9ClOgHjhrHOQcOWurXIWAYo1oEa8Oy5h9Q+n5Tk25+5M+ubffyT0Ou
7sehKd4ettOENni030Sj5jeerd2ce6eRLdFNzs2c2+ov4saRQKNaUeoxFB+n
JsxafqZooB5uroKSB1nJ1u9jYpWhLf1lNxwUgBCQ3JAjyY9iPwBtTUReWCjf
wmD8H97v08i/vKCV/vKG+GCvxu81/Q5qffMjvfuUDcef+zpHetcMULItIL5Q
P0TZCMrcaGFyXoifE5KHEW4NkmkXCJXx6WOIE/kGJ1On8MMlH8zxYiSkxJqQ
MjJaB88V8HWMqQoDKFvjJ/WNoRMbNglQpUXcN0JBOERuCm/EpgaHyZ6ZGt6j
peBssSV2myV5PUQFHoOyzIUTwpUZozSfrteMaO4CFTQYYKjfgxweFPHAsRwP
bqrj94CyZ7LFm3YsJwzlhPH1X7LBz79IqX3uRNM46q6AzV39EPtkvnX75EdO
lDZaDL0Jexdb4FdrV7vU580jX/PTb1DjY8nFlQmTaiIwHGVPyw+LdHiYLC9Q
Ho4zyE0ht1MrGLgymEvek9IyXPo0/svrN6JBSb0wzRnTCadznq7vm+k7lzCN
dMUVRi6T6jLuwWf2U19LIsW2SCOmEyJaBxfWgWSIjs/w8+tvfH71hefG+Oef
e3xS9J73e/CH/K4bMffhEcf0djh8/CtD8ld8TL8/A0TMH+5F9/p+vcX4XW1u
+vnltzZoQfHeF07V3uvg4EQxf6tJ9KR/W2VEGs/Oxr2Pv3z62yL9zzj7zOkp
v/xNtVpvJPlYGvIff/uo7t9w856d7fXM67jnBuK/a+o5/ttsTgT0t5k3zO9g
8V76g1Z0QR8urmWYjRfp8pgRvAk3sIygG3B398ATq10eQHx8K5cByFHn1Wrj
jd9vxtAaeom/KLsQMNuGP8TG2A46kuJNoA3RsXb0xGQTNbQ8jNmlam3VwZqB
DFGoBvETmnEjrdbqVZzzwuqQBKjaeVriEsa/Jq1MwBWmlmVepS9JIgvG+8h7
1zox3hO4zqu69/bk4Mejs6O/nB69OuwHyQdVkM6CRXEeKu0qIZ/Rj21yY98L
sd722/dKHm8rkz+XVKQIZhc5n3nIa4GDZrT5hOG0voblkHNOEHRNUu/1KKDU
/01J/szgrVhcx+O9P0jXlh2ajHRSh9eumlYdnEY/ODpRZz28wqJVje8/EIjD
wf6O/ejVu933cCX8K/tI9dHYPpJF24/IKvv49EifjVvPDHUIUnHffVXZudf+
e+ifYrlY9aNlt2xD6RvsCg+p5a3JRG9gtFXyXAaiw+22qs/vjCeVD0qhE82T
/z2Y++T1q5+O3pyGqGt5BzMuRcEb6ib0fc+L14ZdB86hMIjH98KU59C69FiW
9qU7E2QltI3K7fsTd5t3rqpUczO8sBYzbpvM4uIT5uMpghElZJ/T6IuzRXqm
2GWCfoN4rx/f2/KVsbEGfmbwvfgP/fj+/fjbm9vxrqP3cMNP1+W0OLkku6Vr
v1+9PjySOBTogJcQbILdbolVye7yJniCybfMm7DcMo/p3bv+NIKA2LODk2d9
60W41WS2+DK2IUHg4dj2UeD32LI6u+mBeL/X8JreC/v6DXA5ItL4zdsjIrxd
TcfUtb+DSwo+t6u6OsmndXFMvUlXc/TFsXkXVhaN/Mqit6vONbssMvkmqAoi
96/Y4/K/vzZXFBzh21qbS3KFWsW57Gy3F9/i+xnrsPiWqykmhafEKxcc7d9a
bWsQFK16M3E3AXyxHggu++DjaP784D015w/Rk6ujoMX4uNdGraynk5GXPSUH
zmxtMvUHeJVczO6Ik8ScP2Rojl4eHZyAqevmyXD67lwrvGm1iyvZNC2SajYj
srWt+JBwXpirLPwNd0iL6TPGYgnP9VQf7gwE1XAMGieTaDQimomWeSxh2YoE
nTRp0a/+E6H6TwBzWz7vbtU4jGRnhZLzXL3yPKG5+mVIw82roipduVp7QTq8
qbxSjVCFVe4GleQ0UxuOa7Da5LTIaev24hTbK9yzUioVsVavBr8dhFX/E/1M
EME1sqlz/jnqoChczx5u9GpwS7pbJNFMp7mYbt3pID2S7lVOkgw25LyOv5kI
3/1eEt+8j7zjRfiSUcAdTCLmi1pGYJSurhMJlwWKqeaRK0PLeOHXftKC1C3W
IKv/Oxe28cYmjsMrN7creYf3PG4mOHPML084gWUgJWVYN2wUDYAOT1pi66h6
WPfPUcDW89EG6KgruNOaOQ7YVjtxzzBMzofsS5R+LkdofGRppM02u9P1w7zg
coSmHpdXxrIFj+d4Z8Ahec3mrKxf3GVzWRBWz1I2QJQvePU3TGDWa2HqcbxI
E2ICO99Hm0YLqf3d1eRZulhRE0FKV+UwKF4Vaby11BIcp83IltsXDW7hKxKR
xUqRDhJVlAaFTAIfJOYBbZNZpsaZK1eJBcWfVOmHMiCL4wK37QE4DmBbJosN
jj56HcSyzgEDl6v6ZbWURSwJzOgehl1jmp1TsfPQuLWSC/fJSILOWngitdQL
26yD3XgIQfJCHZJuAK1LU7Uwcb/lo+DOWzaDx8H21UZDTmb8c5Cbsc226zwq
HBh8LZpX46+J+8YWzM416rpYjf7X6+evTs+Ym5EojR/Hu15yn+iA/C2v7Azi
+YzBIGVzvZWpQScHKQMdstEecbxbdcFqJle2yQtGkBZj8YoGDdzGouyCubjP
O/cFJ7heGnTEWounzHAlWM41wWutFuHVfu8+tc8HSlm0t6pptbpxCG7TWrxq
9FItxhmDgul+Jow1EQ33k5qQ2/PNtyBn1xa4SFKAuLdB21ucbv8nELXHspYn
qUiB6mclcX6xXhui2Mc92IxXKTdVE7+jfSCQg9aXzLR+Y0tpKiVzjG5CzTw4
ik2HE9WPHAmKDt3TBiYiZLHHsngTy9pyeiHwDHjgQfUCnZOcylEic7Btjwls
5WMkq0XmIsGShG+D/ApesxT3pAn7vmnhvOFYN3/Vkzm5FoP4nZ7U0z0QN7dJ
1uDW7Tf+hvXVRwUSarQ2WSTq3m73o6AQ5co26wD5K5LW6rkH2p7pAQXfBYtc
3YoPv7Tnoh0MGh/j+Ate2Knxenzvq/Fd+TMcBHPwGeeXuSZbiFLRZrn2LrbI
alvVLPLqOkkJLFNtqVGD5f8kczXcsjGFu1X3mqGCQpeFU4llRO14K+CqC2Pz
0+gjGqGpPS3LmRfdM2/y8c65sKPTmws+7NaZ0d0h60nbe7O15zrH+Y1yKLan
11Q/m3dpVr9ZWnWhIFcgVMd8XTTuTGEwMz5CG0u5nMgUtfCsc6PDbiAD2PuT
LwE7ddgdFjtk6JrrMPwyKNYBu1qXqBUz+u+Qt/9X2N4obJU9/l8x/XvEdNs8
xkcO4c4Y1wPBxKM2vggzLnetzBLQWknWXpSEcQa/ofNxIN208/EXO3/fWJjZ
m2205U8CA/WaMOn/f0zvcfP7/4H+0wLmP6MLebh2g0o0I8bVe61+Bxu19xf+
+zSlO3cQrzNyI4qOGqWL+fIZEpD/SKtA3oCzayEbrlOtgRDrjIGQj5oFKb+X
44ubPH5+OIi9o7sNJwdrO+NJw4vyfeSflQiudPneO3jLh+CJcr5vyzdfdfNc
X99bxSZMnN7kvl+tcdlEML5UlQtcy97VHawZ1s2DxupXT70iOqKy1I1TwFo4
3Bwo7GjBYZq5F4yynpymbiFrAKXbOxNpq9z5/5m5K5J9fZyQ6CDLmosX5zhJ
0+jTJ3oipcOHRpGygR9z+tl69RSXWqfFRT1pbwEwLsJtrx1eXnp1gjcTd9yV
xuNSgLG7l8crDe21Y2eoaEju01BLaqnVCi09rDloOcRYrxp4F4ZqQvopV0NC
/xoL9HJpeH16n5BfkZWTFgmIHmk2odft6Il9R0/rFj+jo+YtHHPn2+VaFrlC
sQzMBLkU21ZtZg1Us2P10hBkWrJqS0uXpJTgcKs95l749wG3cLRpZ5gz7WNO
7tdbsKe41RKVSY3STGsptzjoQRPteWACEqqBiKJZyMr50/Z+89g3WpPor5Yc
crEO0FG3LSZH8K1p0228MHzMPsyKYUgY6Ebv9nXBTi1P/0pieXq0R56iO6RZ
cx1225Dt1LLgnQTy8ikoNkzkHpXgjBCbbhu5K9yaIiocF0jgZktFjYWGfSLb
d0obhruXwxLX6uafUVdSOL2wm8pqp3fbAIcTFwkqICq4lyZ5B6BapF/y73Ou
dVbzHbomwoiewkMyv9vqwYrPQKjbLB3B9LOuigWdxRDwkJnrvk6XsICvrfmS
YeQLftPWN4fsh55OYL5z5lGXTWX4rrmtyDJUY0ppqoxjKfPiTAwHPeVCypFd
V2SUHZPSqVybVZq2uZB+XEHr8iatB98C5aa/zQhpKW44qeRBqnU433IhtWu2
nMlxGTI2v14MGz4339hiB/vWBO0bAMT+IQB1W2srYwcoIH9atf7fmR8aQsp8
xtlpJdG8oWk2X2limn1luKJVj+0bGqKx0S6Ywsw5MBzck56jCWv3NQ6UBXbP
u6Cs1q1iOQz7qkLqn+T7SVFVO/XGNy37cdv3bRB+2QD08CC2JuB28++m8ap3
0hFOvzRebWum29lpIram2W3mdG7KVmPU/NzWKDXft/DMbjRjTjiAR5XBc5+j
BC8c9g22rCl8vtcqycQnhiDLcnsmJSjWw1IJZ82MwswX+cg3Ng2YKPLau/Uu
rkU+trQXFpf4oOmmueMf69TW6fxsywFPMCmj6Tru5cikq4N71INtRzbs+yBL
0u4sNsY077nvx/vmPDJLRtbkYVJ3jDRoomnfa2dMccdnBw0MafH2MNvVDT/w
ulQb+BVp3K1q+3c9y6C6QX0314F2Ghov5d1EFPInTp9qWsZOoWJNC6dTAyOC
lE+U+mD9km+3jAQ2Uq7eqmhBZSAu88aXDwzYPl01bhoRPfvSKHStCwhad7Sa
O667VHEFjkXlUHuuHMyCl5gAoYLqcvxuns6yKiyRxKtfz8ieqM7XCx8KbRCg
K3dBveTDbZnvLQIVNr7A7MkkH3m7buBlbVybGtLZoct34TsNf6tmKbA8q4sz
/LlNx7yFzuj1tu87xp0up5pfC5VVxztTJe/sllqe0RC0kpIZnKsr7d1QUenu
mszhlR57s4cbPMq8a1zajjWc7epCQw5hHzcYxRlXxfQmRVyu1eW4u8vxli7H
zS7H9qDpT87W1WuamGbtGVd/HYCOP4kmoI4+zlLGt95d6SvwxZ0n2SKdGwBp
dZetKppySl+da8IPWPqKz0+XLUCYdzftpd3JDgZxt98BIPpuFGidQfnNxua2
ptT3122lFPjLPLXNW+W8PJi6aW6D6DbtE/ULbgRtCwd92Law6TcDtzuietfX
Xc6zcrmd+5GZ70SSXI+F/DfLO5bVxTY10vm9W4scbH2lhPQ+kNpmJJHS9pLc
5u16hp2GDmrc3wgMF/3KXANXrsUN2s3um5dtSeJ1HVywk5lLRnxvHOdXDbhq
k9r//YlL40PuIO6+TdkMs6nqrA0A0P7dbxBh3S5myMrOfWo0x/q2yI+tlSKc
erQfN838rk9pU/bbsqGzkIOexFK5cSvFz/JOjN9BMU0bzitTcu7Qs0UzX6Ab
B1sIdZAH78BdrzKb3/2/PGpOqWskj5ts6b/BVBT1O+Bk4zU/mXwFHPPzUOXY
XfL56U6HV560lyK+WNND0vT0yuU0Tj/Sv+EhFkSb6A2nIOCmmOSDHN6cXRaF
SZGIGlEJLGtr1IG0L/+moYEqb0wXVXKOMxyR3r1kWZKEcMSz3/zY3e7nxKi7
dw9lWWu+UhOHKfOOuwvsXdc/XMc76HMHbk8yrnJc1URz966A8u6j/vTp5fGh
XKu6w3eMzUxhh8exKRKgF1svsromew+Xb8ab7AKeSRLhSzmxuUnvclFI6iAt
4XNm7ZTL+PE9PoQYcso6qetkhouacFW7VJGA6ZeJA38pWrk5DToM7ydiPf1n
6lOuAUVJhLrMUAzFQxlapHCPgEsKwOTzBW4bnZVkd6ZVZK4KArdrs5isOmNc
6+Iwt9BJDXfhQglntkGooJpmzcJucnSsKBahD/I3qad3fAPNy+ASRo2jPGDh
fqxMa7N53bJ3xdnHvcaojM+qAzfX2WetoQ8FwOugQ2cLboFg4ObXPdvPu733
cgGreaAFnVrD9besWnafwzkhAqD8oEbX6YszgxZY8Y1LClck3T/umtK7IZ8p
DhhhA7gCv2B4d6jeIbbGsQNzuGmpWRbbvvjP522DSAMO7pxwEGoYBTzYRuUq
HFu65iIw9k7VdQfR2Hhqp1y+Ldk4b44hF1uX+IsyGWMETrZ0uaqvsS0OqfrW
h+Q8Q74PyasU5ndndHX7zHOaB5qe/cBu5tvcpLSHYXeu7YKbdP3tTHhFvLHe
YcBq0BWyJFHjlbm3F+GinqpL/xR/zcF8bgqnuuFNPgw+eNq8KZIv//Uq9dtR
NSw1u1znH7w8QxJ0En+ytwY7l0Bv+zXAfdUtzeEM4/hwKxJ/jrlHz2QcmgOL
Ktt4Nv9EtGuddx/5uDXe2m2/GWvPyPI/81yfVbOsd4DXbxxe3wqjHcJmuT8j
H6NDXB64z5SJ4m/FS+txJdAv+NQmmvTDgESPQc/9NJL2YEy+fnt6/PaUDcl7
SBjDV7Yp4HAm900/kh0kljmiZ7QbfA15rx/QokzLFNnzzWAk9EkH4/33A6/j
fos2pZOO8iVPvfIl3gHKjvolrbjdDd6rVhj09kHKsP7DIG6kTLlDQF6lQW9f
tgcivSm4uUq8yE8za4aKUMSw2f9ZVxrYLcbZGru6XZacc6obr/iW1WoJgq4e
aKf8AhBtaeIfe0ICqt34ughrW3qVKrxnqfeXO6iv5++fHbw58iu33L3byLwE
Ysu3RDs2Hirmr81T/B9cxE9H7CK6zRr009ZCtsYWb0FFnZU7ulIRfusNDGEK
QrOMw2o9bQTFpdSpH3hv3xnQuRGSNNMIV24DuOeTOz5A1RUHdZqcKZhLLFYO
Qt/j5MV7mO42iPO4gj/sUNv3LyVtLvtWc0cBoKojXbv/hQXhw76/YVBlexJE
3TL7/zHEd9P+IuKHbtRgIV7CQGdZmhsyazqxtPHSXRfy3wwVL7uhAzb//vbo
zV+bsDH46NegsfVnOqDmOmlBzWk+svtbFbMOX91ZK5Hn7IuZPJ7SJWWNBcGD
CzS+6mllolDXaWkdttSKuYD5SeDgsCVW9NJmKSi65bbmZmGV74LLlg8i28z3
97jyKjZhjj0u1gvkLtp5gnpfKAjGh6iOi9UiKUlzS/lOdLit/bFHXwdVUuLe
zklyDifSWw4p0hu2P19L9uxOv+E+o3UiAzAtzyXxzjjF1mRi2py2x1IcY5nO
M7lkhPtCCYr4+cGrgxYssYooGg6H8TSZfcBnBzPccr9I5xesc0ef9iUAl84f
7ZzjGtedz6bZnfhlUWdXCWf9HUiU1WqF+qbIP2/pgs9bQXcNYZ7lpJ0vOfyQ
LZMyQ4rhNV+1jIXO4JAy9s2Ge5iV2TSNpAwhtNRXaY17zmN29MYviosLTO/T
nVdHL7ZMpbtFjxr0pY7EMkVp1axaSuoqzWeTTuNpWWwquF3rwmRKp+ihitip
UswIt9QcgCeBSFLTHNMKaSFZpVVgc0UQcTKkJbEOQpufHzzZewAsPSmWaWSv
V5URODHhKsP1D6mruoCW4mqEqkjot89e5rUE4uPDVyeD+PTJ8SA6fXEiWQDP
Tk+P+ZoYnayck/a6g2NTiptcy3hkANTXLqyfVRWuSTuIq2WCy9SWbKFI8V+z
Lp0yoizqdEfmXJqTIYms/5y2m4x3xJoYkyJbTbf6nhdDQFnP56jVXK2nWjdn
Yd2TWJmOoPWEm2nxUcIuC07WgOEnfviLtUmwxhJQbgZld2mpy2yxYCSmVayx
v6P4Z4VLEsk+82cWJxbZhzQmZBmYLbrimhYbdtyy/3pJH57TrtuaMQxbItea
81Rx+0cCPJuXxYodLPQW4CTecn6ezQivqXtmSJYbwD+3xFxQgpCvJZHaIEQR
C9I3qMdcsFoL2aIkD+EKNjzSbsUMjqmzC7+EIKF2lSGs1quvV9mMuZiBCfGk
g9ZIkY4UEwArWUhJMCgrzjj+eG0vrZ9JPSfGvKLMiMoUEjpHZbdVhEldy2xm
ifrEcULJcGu0ymZgBNUa9z5U8eFhcYL2teAcQewN75RN4QkrwLx980JcIXJO
UslT0p9zwnh2+dDvvXR0MRpEO5d1var2799PPybwl4yowx29cc5UsWIcjB3l
80ljPtKZ1gluRCegLBIZQ060yKBuDdybHL2Ax32RrJjZKPA25uJaj7K0i2ia
XuCqC3MvHM8EIcJiyvCd6zpi61iSsIayGNpUrbQFCorYZaT8zKXfdAzLjO/C
ZLMrvwAHyyvjaItkLjhnJjkw7EY3YLdlSlqVzNzhE04oL0iyXEeV1F5zX+iQ
sHDt0h6Kx8xbnBAP7wCLdBJLs+shZikXg/Bpow2WnFRcE+opDsFwsIr4VcHV
W1gmiYvZy4vn0YXMwXALBFw2CP9JNIhBRzAy4gp8Fuxpas4/EL5O5X5AAQiO
MCkphSyKM7JE0LEuEqgzTlREJ1x4ZyAe25VKeeKVurRBA65pPl9xdFIZZ1ES
GmloG2SPElqJlESyGVEG5lIFGycxity71Xm2KNaY4jm0ItwJCOFDyitcHyr9
mD2bGAJR5Qdb3MYAgQUrvr7McFXZtTn7xLsxjxSZ4baTbZ2W8MZy9bDjJ+zA
IEymzb3QUCHtCMapJO51lSYL77yBBQJzaonxu93O5HotRnWignPcGCr6JnMg
E2Sc/l3qtprzBogp4lAN5OCaGCGH8hBYjXANOnVdMR6z1rjIaK4AsfrUEkhY
djLXa2SZIvdB1aMEx31w1RN4CHLgsBk8Z46BegLB7wvTAAmIKkI0RgP+Qw5q
MC3gUvlq5EoXKppCnxM8TSSsIbQHdq1hY+kUxebE4S5ULfgFJi7ltya9OSKg
xAlwkZy47/l8Cxz49AqHb3JkPtLvhlHtBIxWKu1pWgb1IwySxgEERKAo0zPN
50TH65zRAnl/OxxeC/QCoeO80IaGZdseig9QwF8V0NT4vlCcIhHIICZGqCK1
mZkgDehEEZRZSRCYuPi04GO2EPPZjBBEBvDgZvIW9RhaZOUAF40qPmj1edE2
PWXT8fqeYf0uXiFl/6IEmsSF+c6RPvonAQb9hhpxmTk7AAQaOtSdps/SmnSQ
Z8UmZQUTB5N417x751mVg242j8d9qaWQIZA4tTkGRnGlL/b6kXBE2X2R0aKA
8RWiP6eqKUolBDk41qGpzBmTZrWPRaS2WAVOtK0INoO5j6JTQ+qLRgL6T1SR
dYMI5K7IEklgDnE1PiA+pJHjvbZy4INd+63WYnCH9BRLbG4P0QhOp64zyRK3
ZRgSZ0qNNKQpB1JF1qSu2IgufEL/TmyYR++XcEtAGemoWkPny/QorFxLubgO
8LWy927pxkgiho+kPIPINXI4qKuWuQRxte3YALYYBYviscIORTM2Wz1dzz6k
tTAPFg8XiHXrlVzR5B2uNeP/jUaj9xOfSYCkB/HkHb0bd34RtdiGKHAVMvpH
cQ93GZtaoaHgJRKxtduIucJYJwZ9XGbFg2d2jn5gz3dD9CW2Ugt80uEPLBh/
UGo8tRbCpzuJ/WaKb4bWethi1/7sG6kzKYkaO5MjKHjIlo5qodeeDsz5EFMs
h+/JzhBtuqxNYnfJNeSYlV6sM7meYr4GRWo6d8VOjCXMRzXdGSrRFmD0DY91
KhK8CEBUx9qYRuwyPMYkTLoAE1AdbaY4fb5IP6rtShTF8VsRMDqf5rAGWCvw
Tk4SUPYtipDZHDagIhOEht8kJ2V1xd4ataG65JmeQJZiK7E17XWISDgqFGJc
CHPO17pcpJUqLFWqXePadWhzF3qdie7DkumU9Ep2rS9chdlA7zJygdfGwgYw
8ZT1AxjbNQBECBc5zUJjz1yZ1DKs6rqq06UpNZuokwBsm4b7QNxkY8qaygqR
pGktL0FpQpST9YoBcu62x+ZXbRLhWw4LUNieJU1UFef1hh0iIrAGKmSayhxN
zDg3rKq5KCyfNdxZjVCj9RC4i8XaOGikTA7p7YR9kgggBzc+mOPUlalYO0BZ
TOONDFUo48iS0q586oNYflkknpHgaV/5dZAaWiekMp9z5U7IUMHfrfSE3VEB
Q3gHWRKFelrs9DQmIoLIgDFsIErCRFSV/XhCryfiDwMvzy+cGDB5DRaszS2x
dKCUc3+8tzfa3el/H09oPOk1kl7d4V++dxD+pBJuXWIuppMf3xBn3nl7gv8+
f7Uj+gn6wrQnegAmYhEB1/OcC2qizOZWkvT+JCPI9cgVKQCGSaTI7RGM1tZE
+WP2ZMD20nq7Ztmct1SYs12u1sDc1GGyvmySlN+7/qSdGSnxik6bHA4/zSQS
4dxUV+l/f19XcvWZoScuEkEk5en68+J7d/qIkDWJ2jhYSIInaYbsFjO6bBSh
JBaX/bBHo5hfs4bkqTHiQIcH9rMYod6hDjI+4MaUQr8V3ycjB+yJd8lVGKyS
7asyCGAs2LDG/Ul7u7sBjoiCmURwd8FrAe1HmbXuCauFG8gqkGFSxy5jFMD2
DbggK5tPa8HQoKlL4ZzE4JXOnH8jtQ6OPtabvGzVJbFQdcovrkfWDc+wcAqi
0wyZwE1aLuQuDKpQ/FuRtARtoLiZsfgJ1E1VQb/9/Hk/ir5yyka1H59uiuEi
reUQmA9IXqyYOlOHfzSXPYmlISnwRfYhhcejualOXWsBvzKdeps4/gY1CKg/
7ToWvdgorExTUhFNc7vs8qxN5uu+wHrqbPJgb8K67whr5uRvWu+JVdB8ZB2E
phiY3GR3MtT8rZTTuVXcxyYREKVqAhaYGO2UXRNWO9WSykZlnvR87hrFouA6
XVb7SCpFDOeRIoHyn2u4v/+hBmLP8Z0+a2RRbC46PIeVJl3Rolnr3R2N+xPW
gEekAOvv9NvDvtZSmbx7CGQ7xyGGFzSFyS7198e4jv/4KP564qrF6+g6Ua5X
yDySGBAfqaxUkENuI8rBmmLCdzRYNQqN9h5OhCeb4WiwCoPhhTeavZHcMPic
GRx1B9WLsSMPPxyrKo5czEkdfxVXk5HEq+bCwV47WrnjypUPQUrVFm2alPSf
GT0IzZ6lydX18BluKuC0+42+GF7yi0t5saUjjR3CnFCeqJ74pYum+bwz4ezw
ibLwMzPWGY91pmOdYeq9/sSEGGyh+mYhfRqKemNGRhZEMcRdT0b2mYOOCLxd
OzXF8SKhDGOZqQQM1myUIyBS5O5P8NMThbyb1yHSbpsqRkKErPphG/XKDdgg
UpffM4/jQJdnH7lkDBqVBPFSKIBa8IZvhKZVkdTKLkhHk0MGtFIIC8LTRZqU
uXhz4Q6QBSRzXHAl9si6JHWcDRmEpi6LTbyE+35JZHcdwedO0825Xn0hdMCO
GhmDoclkfJWlG57eE6MxmYqf4uTSazL9c9/GxaNVO+13olfb76irHQGgVlnn
q5axdE7Sl3ZVxPQnPWEzaltxsu2Hx6kM3iMBhkWEyL/EgkUaGRfirrzyjqro
KBL9g6li7ipp3gEQ2ISNG0X6AlDfo2DIIEQ/Le5qrv9gtuaftybsnPFFZrjK
xPOou6G/9YYdGM7vTpJd0hJwuXjgfozYqbpeGtT1b3hMYvOSkZ/tYcNxugrc
V/HLtyenBm+FlnkL5RCAOt7Dgzb92J2ClhJeJdfDKlyiudFGCUsKBCdyBHEF
JYwhoJV8FESc9m+mJ25umGKWoksvvnZgq4PJ5OHk7jpFQDz0TnxogwWnBphg
oTaGMLQw3s5A+RTPU/HptvkYZzvdkmVKrVI7+Jkb3DFT6mobO6U1nRTxeSJ3
oUjcpwqPpos6a48POAwCDRpyA7ti2EbuaBU13OEJEyVbH8cm9RUz8UGrR6SQ
8e1aIrcWGcsC3kt/Fzf0R7gwiEMN2Umjzmk7EyJz9hpHzAZ2dnd3d0zIyLsI
D2Fixh91gDD2t/oQVrIzHo93EK+qVXEiPceDDDrXvGqoUhIQp0ktcNct9NHs
wujeDKBYdzRqjIKZDjTuUdmxLgkZ/MGYZFflOmeyqC+lcH57utu0dsUyLb92
rMt/++YFFK9k5NmZE3fTBrEFQhTEH13OhInLM/ilPsNkGra3tbxobExEEwTg
bqWZETMmpuAYotx1k5DIQl9IpctSvvKhMa1WFQxz7AxAbgKME80EpqZHBpJD
L/VK272X7WS/kZm3w0y53HjqfCVI/REYEnHtjeKjIU2RTC4k8f0I3kSqCamB
S72MqWLtGxcvc1ULPobHE74qFmsx06tkkbIjMpKcSU7ZGsR8F2BZ5KSPAgDF
mhrpyIiMrDnzAe5Eo6KgW5o86UezVLpyxu8FzyzDOC/JdmT4DOLF+uPaThfL
YH9yQDPSKbrjfivWK8Q1u2AKX6FbcB4BHS9GF1cFG48+wr2/CMDFEiBOLThN
psegUZEEG0+8gM+n2D0N2RYbS9ZxieNUOD2iDKVK/f2VApy17nOaQ1HRNAkT
cK2L1bBKF4v2lHEqGyidVCGhC/5xh24TxWviQ5wLzCGdwcOtsB85UpmVqPrJ
W+qWwhoHDmIuxOU88J0mHInt4KgmX6oDcNQj16dPnEarWvG8SCW7X9AX8f6q
mMnlYI7s5KQohLuQdJyd82nXgm+192dQuTHCHlyGwkbdH4hbJTjjYb9qqNR6
YZOPITxL3tLLNKRkuJ0nn5jt7sdjMjmZce7HewjJykD018PPE++uTV4KcEqB
sbM73pFaL25O8UN3mzr76D2B2DHBVqcR5mH79XB5r9lvIxYjjpaXarS2IzDG
rXKjniKG3vbgzW8z/2w/Z9zPmU7hv8n+ey6mddunxOiHI+FtI0TCs741E/n3
l7FnQa0bjeTCFzrNiA0Tpe7YBe0I6iXqjfPqlDrXlyr4HHlW89Mp+6IE+YGi
wDSSAIleL+U876Zr9fxZ/3+YQ+HiehrB+vRpq5EQROE5ZQ/9BHca8s1QWp0Y
+Z1Sa0PzPhz1aZ6Y+rpEU/HCv9Z6dHF8mtef3jx98h3hOycqK8+yRwaZc3vb
wcRsgaE3EPYmP/GJDb6IkMNV6woJUXCC5ynSmvgOayMIfGzl+qFFwbxFq3fy
d8ap73YSNhi754c6qvj/q2bUXHuhod8cPXn98uXRq8OjQ1TVklGiWXm9YtfZ
6lKzFbXEAe6asLvo0uxOnh3sPfwGcDp5dvLo8PXz0Xh39M3u3rf3Xz0/OR09
fX58Mhp/uzv8GlvrW5zcn0E6A3m9soynrhWJ4QMmvqOH5nGxo3GUoAPmzt5D
vRMSniuXV35QMoum0c8Rd0N2AN8DQWgsaVRaMU6dHOj4T0wIEnSF4Mvv1vCp
Po6nqLWagZDcxFmGcXCA/tS4mLtz5HqTXMsusB5Xy+0hrKo9jlcZjqIr6bMb
gbHEumskSSPNWZ0COfIV8slsxmpKsuDYqsaGxd8U2L4Hf/XIJzGpLpBz6l2G
p1G8L9Z6vv5dJjMr8UG964jPITiz1hW9vqXpbI1tsiD4VASKPLASe+qxhbC+
C09dpLVMboHzBB0lw1HCAr3qcib8ycQa3B7JDseTvsOlp8gj5KMAKis5amyj
uKqhSZEI2TZ2WENp14rAqHPBBnS+ZQ3WlZPZehaxhFqT2geKcVM8NtwGi2Sj
kY1FczElF25KzjWf/3gBJAw9rqyQM1iGx1zH2T+M/+nOCk1CX+wQTYZ8qnHo
LeJmF+3BTE7cAs13yOBOWWOX2lBMlNxftWOOmbz5jyc/HuOEiJYMkObUGww6
kwbqJ/E1hBXhJedgbEgZVuKQk+0g/XVueKzJapAD5crhezRHPYASW9nU56kz
ICKj8uI+z9e95tnq+KvYIVBf4iYQbJx74LKQ6+DyyMpeNtvszt4762JCjlZA
Hn5BeYg83rK2L89l3cl992xr0fSDuWY558xNEXiDA0NBh1im6gSsuCTzKzK5
9KgNU3+a1OvSK7avEbtMLkUsUy3M1FGwUY0ukjd+wcaKTxibXDLVTF0RfS1L
yGqGYoH4xrkpooRkBV2llVGTLIj2uUIcpInpDGbEFPVioB0ZyJohrJobyKys
NCNtkMWKoBkXvVJ8+97cDSqx3ku+noOGRTzJLUEZX/cIQf8gYjIHeAyxpc6T
GRJDRC8qFsG9viFD7VWaXK/XY4piIDy5ZNaOADpnpr5MSzbQUN8nPhS/v0ff
bc8Gi+dkpqFl0kH5pIIkXVInXuoNa0XrKZ7ShNhxoCXym+zfkw4c67XZ0rxF
jhUfchE65ZLGxbJKcdIDOMEsOicTmPAyZd9gki1ULVVcxQEvYZDsfKj5ABBz
Q9JnDg+OHz0fHo7mZXJeD7O0Ph+uVsvhPFkNd//w+fP3OjByaQxfVwcitcTC
/YCFbvEmvTu3leF9MbXJ5OaoZWJUGZQwkopgWziHvw/uOulNUSrdDgxedIDY
KrCdvCtq8y5O+PS5Jp/FgPD2lVtxlLe/jrZNOw6zzewRBu+0DzLpzcELzouN
pKBT7A42hZeLX0HiL5O/F6XGTIyRwOW4CAwzVMxxENO8JI7NCf9t2WTsceaq
9wjUacrLuG/cxxp8i0x8R4vSiZtLui/l4UiFkqlc1cA6ufAsSE/ClpLuwGyR
G3LIoyjnkry8TUvTs2cRhhtY/0QuGGoyzoXXusjNFAcsX+qpTFay4E4bai0j
5LIY/7s1zg6a+Rx+TTE+N6v8xuRAC8JLa5Ki7PywlwNKLo6CWXbARMGbhzY+
fbqaJ+cwKnRxIJkdLdVrlQpOndvhsXe4cm/jBQOrCmqtaXmuyFZwyot8iAO4
sXGy2eCDnHQQ54s5xgJNrLEERPdgkSai/Nr8PWaQ9uqH8MYJwLjMpGIWK3Be
WnO0LVgndomctElnaqGrjdpAZzPFyCKz46uiA/pYKF5MPkojeMrqhUmokWps
5ugObt147F98YRSx2rMRugJqcomTniWB9sHhE+qrQ13bGDdTF/+6F+heyvE3
rsrc639rzJmWQULDJEUatp2I2+a8W50yFhq8P1V8URRzEf1MBRdpPYoeY6g8
UJz0buu5ZHcdvzg4eXmAzJj7SH7RsmSLgoRzNrcqrMyf+tp4rjXE19nNlYt+
IUeS9uO36mfXGtDiJLC3Tz/u0izZgrbGBxsrfHiDlfN5wYavH3mfppIqu+Gt
aXypEYPVmo8JSjHsRC9aCcwvU68XJsmbYkq2M1fiPICNh4QvQi5l1H6RUpTH
0cdDW7GPTRE4IVNrihRbs0WEmXhHncV3d4mMhqU4mJPaqJPuHAqOTRI/ILy1
U9VgpoStNR3CiRdv1sJJ602BUpLED42FoZoi4KOWBvJLo2pdcbVLPYsswe2r
1JQxlGh/9zjIDdesA1J1oG/6VboCn27NVzKYM7qmN0l2RKFWz6mYSQU2e9DS
4wsaVexpbtDUlWorXCAgaGGuTfPPRl/SJGnPvX1G9spMYtw/C0VVKAgX+/nC
seVAknAv0UhSNS/5MIYxIlWcibNykZ3XejLUbkeVLrOhTkFdXUqYmoQpqCUf
e5DSjyONhI7iN16agsYtcBDSB3ti0MYW7jOBVFGAUCXviu+Ls9dSQUZ0mIgp
H7lZJVnpFzBVk0q5JRkfcobUO1BIk8vTBaMKx/pmJgewqyu2PORQItZZVZ6J
AYtYhIEcfvHr6YK5muuxIA/N7cCO74yi0CtpkvJZMTIuUZfPoR3Ys4MX7j5a
wAdzr5xCw/f+4thRq2ZdHLtJtS8F1myeRoIhGnCPEr1Qs44rhwhAXGohl/wY
40o3ebPLV92w4uuDdTeWg1378YQ6Ptsd8+0lZ+Ndvk1rz7Ufd7cfa0a+th/v
Sfu9Mbd/4Nrvdbff4/a7pv3errTf3ZtwHbajbehAdMhKa1jp0l6OiyNVqlnw
3Qd8uUYyKwskphIhVOZghU/7iunMek1JycusnIf8k3eOWEspSa6ZqLQLBM0a
FKbuvC5w81m/NQcbBRFKrt+rk9IzT479/a5lBn0q428spz1FHK4QZwBAwVVJ
Ez6FiZPhKN/MHIGXarLkgxG0tKrhGXJll0mK4t4QJofpncys7cbmKHsbJO6e
VeJC3gsortITST6tMjEob/TgbJDfW9euTdCwqI4sWUU2zZL1UHcvaD1utiYC
GRiUD1rvGsT3YdpsTWOETaIH7WQxY5o3DJVGgCLcf918w1wcs7W4pTtuVAvH
UhyDsmVHiDVxpso27tQfdSG3h61mH05F/gb36RD6ON60zPJ1ZcT0ts/GUpCo
9K00GEO6XV8YZHy7Qfa0GlN7mBFzpFPjOlL8ZXMcB0EWXpa+sy9dGeYGc8Dh
iFR2KuGB9OA0W3cupifOFn6Xi0eOpvHWKBgNcrY5ZNAtlDi1roYrxGM8WFGo
bNSmhI1T50xjEkJyvtzdBvgRX9rwM+sZ0V6gU6oXhc0WVWFVca3krhxzRJ09
bE6VTYzWHXXqlUj1PsWMf+LA8E1FjP430qErHXQAAQA=

-->

</rfc>
