<?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.29 (Ruby 3.4.4) -->
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" ipr="trust200902" docName="draft-irtf-cfrg-fiat-shamir-01" category="info" consensus="true" submissionType="IETF" tocInclude="true" sortRefs="true" symRefs="true" version="3">
  <!-- xml2rfc v2v3 conversion 3.31.0 -->
  <front>
    <title>Fiat-Shamir Transformation</title>
    <seriesInfo name="Internet-Draft" value="draft-irtf-cfrg-fiat-shamir-01"/>
    <author fullname="Michele Orrù">
      <organization>CNRS</organization>
      <address>
        <email>m@orru.net</email>
      </address>
    </author>
    <date year="2025" month="October" day="20"/>
    <area>IRTF</area>
    <workgroup>Crypto Forum</workgroup>
    <keyword>zero knowledge</keyword>
    <keyword>hash</keyword>
    <abstract>
      <?line 37?>

<t>This document describes how to construct a non-interactive proof via the Fiat–Shamir transformation, using a generic procedure that compiles an interactive proof into a non-interactive one by relying on a stateful hash object that provides a duplex sponge interface.</t>
      <t>The duplex sponge interface requires two methods: absorb and squeeze, which respectively read and write elements of a specified base type. The absorb operation incrementally updates the sponge's internal hash state, while the squeeze operation produces variable-length, unpredictable outputs. This interface can be instantiated with various hash functions based on permutation or compression functions.</t>
      <t>This specification also defines codecs to securely map elements from the prover into the duplex sponge domain, and from the duplex sponge domain into verifier messages.</t>
    </abstract>
    <note removeInRFC="true">
      <name>About This Document</name>
      <t>
        The latest revision of this draft can be found at <eref target="https://mmaker.github.io/draft-irtf-cfrg-sigma-protocols/draft-irtf-cfrg-fiat-shamir.html"/>.
        Status information for this document may be found at <eref target="https://datatracker.ietf.org/doc/draft-irtf-cfrg-fiat-shamir/"/>.
      </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/browse/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/mmaker/draft-irtf-cfrg-sigma-protocols"/>.</t>
    </note>
  </front>
  <middle>
    <?line 45?>

<section anchor="introduction">
      <name>Introduction</name>
      <t>The Fiat-Shamir transformation is a technique that uses a hash function to convert a public-coin interactive protocol between a prover and a verifier into a corresponding non-interactive protocol.
It depends on:</t>
      <ul spacing="normal">
        <li>
          <t>An <em>initialization vector</em> (IV) uniquely identifying the protocol, the session, and the statement being proven.</t>
        </li>
        <li>
          <t>An <em>interactive protocol</em> supporting a family of statements to be proven.</t>
        </li>
        <li>
          <t>A <em>hash function</em> implementing the duplex sponge interface, capable of absorbing inputs incrementally and squeezing variable-length unpredictable messages.</t>
        </li>
        <li>
          <t>A <em>codec</em>, which securely remaps prover elements into the base alphabet, and outputs of the duplex sponge into verifier messages (preserving the distribution).</t>
        </li>
      </ul>
    </section>
    <section anchor="the-duplex-sponge-interface">
      <name>The Duplex Sponge Interface</name>
      <t>The duplex sponge interface defines the space (the <tt>Unit</tt>) where the hash function operates in, plus a function for absorbing and squeezing prover messages. It provides the following interface.</t>
      <artwork><![CDATA[
class DuplexSponge:
  def new(iv: bytes) -> DuplexSponge
  def absorb(self, x: list[Unit])
  def squeeze(self, length: int) -> list[Unit]
]]></artwork>
      <t>Where:</t>
      <ul spacing="normal">
        <li>
          <t><tt>init(iv: bytes) -&gt; DuplexSponge</tt> denotes the initialization function. This function takes as input a 64-byte initialization vector <tt>iv</tt> and initializes the state of the duplex sponge.</t>
        </li>
        <li>
          <t><tt>absorb(self, values: list[Unit])</tt> denotes the absorb operation of the sponge. This function takes as input a list of <tt>Unit</tt> elements and mutates the <tt>DuplexSponge</tt> internal state.</t>
        </li>
        <li>
          <t><tt>squeeze(self, length: int)</tt> denotes the squeeze operation of the sponge. This function takes as input an integral <tt>length</tt> and squeezes a list of <tt>Unit</tt> elements of length <tt>length</tt>.</t>
        </li>
      </ul>
    </section>
    <section anchor="the-codec-interface">
      <name>The Codec interface</name>
      <t>A codec is a collection of:
- functions that map prover messages into the hash function domain,
- functions that map hash outputs into a message output by the verifier in the Sigma protocol
In addition, the "init" function initializes the hash state with a session ID and an instance label.
For byte-oriented codecs, this is just the concatenation of the two prefixed by their lengths.</t>
      <t>A codec provides the following interface.</t>
      <artwork><![CDATA[
class Codec:
    def init(session_id, instance_label) -> hash_state
    def prover_message(self, hash_state, elements)
    def verifier_challenge(self, hash_state) -> verifier_challenge
]]></artwork>
      <t>Where:</t>
      <ul spacing="normal">
        <li>
          <t><tt>init(session_id, instance_label) -&gt; hash_state</tt> denotes the initialization function. This function takes as input a session ID and an instance label, and returns the initial hash state.</t>
        </li>
        <li>
          <t><tt>prover_message(self, hash_state, elements) -&gt; self</tt> denotes the absorb operation of the codec. This function takes as input the hash state, and elements with which to mutate the hash state.</t>
        </li>
        <li>
          <t><tt>verifier_challenge(self, hash_state) -&gt; verifier_challenge</tt> denotes the squeeze operation of the codec. This function takes as input the hash state to produce an unpredictable verifier challenge <tt>verifier_challenge</tt>.</t>
        </li>
      </ul>
      <t>The <tt>verifier_challenge</tt> function must generate a challenge uniformly from the underlying scalar field, from the public inputs given to the verifier. The default way to generate the challenge is by sampling a random <tt>(|log_2(p)| + 128)</tt>-bit string, parsing it as a big integer, and reducing it modulo the prime order of the group <tt>p</tt>.</t>
    </section>
    <section anchor="iv-generation">
      <name>Generation of the Initialization Vector</name>
      <t>The initialization vector is a 64-byte string that embeds:</t>
      <ul spacing="normal">
        <li>
          <t>A <tt>protocol_id</tt>: the unique identifier for the interactive protocol and the associated relation being proven.</t>
        </li>
        <li>
          <t>A <tt>session_id</tt>: the session identifier, for user-provided contextual information about the context where the proof is made (e.g. a URL, or a timestamp).</t>
        </li>
        <li>
          <t>An <tt>instance_label</tt>: the instance identifier for the statement being proven.</t>
        </li>
      </ul>
      <t>It is implemented as follows.</t>
      <artwork><![CDATA[
hash_state = DuplexSponge.init([0] * 64)
hash_state.absorb(I2OSP(len(protocol_id), 4))
hash_state.absorb(protocol_id)
hash_state.absorb(I2OSP(len(session_id), 4))
hash_state.absorb(session_id)
]]></artwork>
      <t>This will be expanded in future versions of this specification.</t>
    </section>
    <section anchor="fiat-shamir-transformation-for-sigma-protocols">
      <name>Fiat-Shamir transformation for Sigma Protocols</name>
      <t>We describe how to construct non-interactive proofs for sigma protocols.
The Fiat-Shamir transformation is parametrized by:</t>
      <ul spacing="normal">
        <li>
          <t>a <tt>SigmaProtocol</tt>, which specifies an interactive 3-message protocol as defined in <xref section="2" sectionFormat="of" target="SIGMA"/>;</t>
        </li>
        <li>
          <t>a <tt>Codec</tt>, which specifies how to absorb prover messages and how to squeeze verifier challenges;</t>
        </li>
        <li>
          <t>a <tt>DuplexSpongeInterface</tt>, which specifies a hash function for computing challenges.</t>
        </li>
      </ul>
      <t>Upon initialization, the protocol receives as input:
- <tt>session_id</tt>, which identifies the session being proven
- <tt>instance</tt>, the sigma protocol instance for proving or verifying</t>
      <artwork><![CDATA[
class NISigmaProtocol:
    Protocol: SigmaProtocol = None
    Codec: Codec = None
    Hash: DuplexSpongeInterface = None

    def __init__(self, session_id, instance):
        self.hash_state = self.Codec(iv)
        self.ip = self.Protocol(instance)

    def _prove(self, witness, rng):
        # Core proving logic that returns commitment, challenge, and response.
        # The challenge is generated via the hash function.
        (prover_state, commitment) = self.sigma_protocol.prover_commit(witness, rng)
        self.codec.prover_message(self.hash_state, commitment)
        challenge = self.codec.verifier_challenge(self.hash_state)
        response = self.sigma_protocol.prover_response(prover_state, challenge)
        return (commitment, challenge, response)

    def prove(self, witness, rng):
        # Default proving method using challenge-response format.
        (commitment, challenge, response) = self._prove(witness, rng)
        assert self.sigma_protocol.verifier(commitment, challenge, response)
        assert self.sigma_protocol.verifier(commitment, challenge, response)
        return self.sigma_protocol.serialize_challenge(challenge) + self.sigma_protocol.serialize_response(response)

    def verify(self, proof):
        # Before running the sigma protocol verifier, one must also check that:
        # - the proof length is exactly challenge_bytes_len + response_bytes_len
        challenge_bytes_len = self.sigma_protocol.instance.Domain.scalar_byte_length()
        assert len(proof) == challenge_bytes_len + self.sigma_protocol.instance.response_bytes_len

        # - proof deserialization successfully produces a valid challenge and a valid response
        challenge_bytes = proof[:challenge_bytes_len]
        response_bytes = proof[challenge_bytes_len:]
        challenge = self.sigma_protocol.deserialize_challenge(challenge_bytes)
        response = self.sigma_protocol.deserialize_response(response_bytes)

        commitment = self.sigma_protocol.simulate_commitment(response, challenge)
        return self.sigma_protocol.verifier(commitment, challenge, response)

    def prove_batchable(self, witness, rng):
        # Proving method using commitment-response format.
        # Allows for batching.
        (commitment, challenge, response) = self._prove(witness, rng)
        # running the verifier here is just a sanity check
        assert self.sigma_protocol.verifier(commitment, challenge, response)
        return self.sigma_protocol.serialize_commitment(commitment) + self.sigma_protocol.serialize_response(response)

    def verify_batchable(self, proof):
        # Before running the sigma protocol verifier, one must also check that:
        # - the proof length is exactly commit_bytes_len + response_bytes_len
        assert len(proof) == self.sigma_protocol.instance.commit_bytes_len + self.sigma_protocol.instance.response_bytes_len

        # - proof deserialization successfully produces a valid commitment and a valid response
        commitment_bytes = proof[:self.sigma_protocol.instance.commit_bytes_len]
        response_bytes = proof[self.sigma_protocol.instance.commit_bytes_len:]
        commitment = self.sigma_protocol.deserialize_commitment(commitment_bytes)
        response = self.sigma_protocol.deserialize_response(response_bytes)

        self.codec.prover_message(self.hash_state, commitment)
        challenge = self.codec.verifier_challenge(self.hash_state)
        return self.sigma_protocol.verifier(commitment, challenge, response)
]]></artwork>
      <t>Serialization and deserialization of scalars and group elements are defined by the ciphersuite chosen in the Sigma Protocol. In particular, <tt>serialize_challenge</tt>, <tt>deserialize_challenge</tt>, <tt>serialize_response</tt>, and <tt>deserialize_response</tt> call into the scalar <tt>serialize</tt> and <tt>deserialize</tt> functions. Likewise, <tt>serialize_commitment</tt> and <tt>deserialize_commitment</tt> call into the group element <tt>serialize</tt> and <tt>deserialize</tt> functions.</t>
      <section anchor="nisigmaprotocol-instances-ciphersuites">
        <name>NISigmaProtocol instances (ciphersuites)</name>
        <t>We describe noninteractive sigma protocol instances for combinations of protocols (SigmaProtocol), codec (Codec), and hash fuction (DuplexSpongeInterface). Descriptions of codecs and hash functions are in the following sections.</t>
        <artwork><![CDATA[
class NISchnorrProofShake128P256(NISigmaProtocol):
    Protocol = SchnorrProof
    Codec = P256Codec
    Hash = SHAKE128

class NISchnorrProofShake128Bls12381(NISigmaProtocol):
    Protocol = SchnorrProof
    Codec = Bls12381Codec
    Hash = SHAKE128

class NISchnorrProofKeccakDuplexSpongeBls12381(NISigmaProtocol):
    Protocol = SchnorrProof
    Codec = Bls12381Codec
    Hash = KeccakDuplexSponge
]]></artwork>
      </section>
    </section>
    <section anchor="group-prove">
      <name>Codec for Schnorr proofs</name>
      <t>We describe a codec for Schnorr proofs over groups of prime order <tt>p</tt> where <tt>Unit = u8</tt>.</t>
      <artwork><![CDATA[
class ByteSchnorrCodec(Codec):
    GG: groups.Group = None

    def prover_message(self, elements: list):
        hash_state.absorb(self.GG.serialize(elements))

    def verifier_challenge(self, hash_state):
        # see https://eprint.iacr.org/2025/536.pdf, Appendix C.
        uniform_bytes = hash_state.squeeze(
            self.GG.ScalarField.scalar_byte_length() + 16
        )
        scalar = OS2IP(uniform_bytes) % self.GG.ScalarField.order
        return scalar
]]></artwork>
      <t>We describe a codec for the P256 curve.</t>
      <artwork><![CDATA[
class P256Codec(ByteSchnorrCodec):
    GG = groups.GroupP256()
]]></artwork>
    </section>
    <section anchor="duplex-sponge-interfaces">
      <name>Duplex Sponge Interfaces</name>
      <section anchor="shake128">
        <name>SHAKE128</name>
        <t>SHAKE128 is a variable-length hash function based on the Keccak sponge construction <xref target="SHA3"/>. It belongs to the SHA-3 family but offers a flexible output length, and provides 128 bits of security against collision attacks, regardless of the output length requested.</t>
        <section anchor="initialization">
          <name>Initialization</name>
          <artwork><![CDATA[
new(self, iv)

Inputs:

- iv, a byte array

Outputs:

-  a hash state interface

1. initial_block = iv + b'\00' * 104  # len(iv) + 104 == SHAKE128 rate
2. self.hash_state = hashlib.shake_128()
3. self.hash_state.update(initial_block)
]]></artwork>
        </section>
        <section anchor="shake128-absorb">
          <name>SHAKE128 Absorb</name>
          <artwork><![CDATA[
absorb(hash_state, x)

Inputs:

- hash_state, a hash state
- x, a byte array

1. h.update(x)
]]></artwork>
        </section>
        <section anchor="shake128-squeeze">
          <name>SHAKE128 Squeeze</name>
          <artwork><![CDATA[
squeeze(hash_state, length)

Inputs:

- hash_state, the hash state
- length, the number of elements to be squeezed

1. return self.hash_state.copy().digest(length)
]]></artwork>
        </section>
      </section>
      <section anchor="duplex-sponge">
        <name>Duplex Sponge</name>
        <t>A duplex sponge in overwrite mode is based on a permutation function that operates on a state vector. It implements the <tt>DuplexSpongeInterface</tt> and maintains internal state to support incremental absorption and variable-length output generation.</t>
        <section anchor="initialization-1">
          <name>Initialization</name>
          <t>This is the constructor for a duplex sponge object. It is initialized with a 64-byte initialization vector.</t>
          <artwork><![CDATA[
new(iv)

Inputs:
- iv, a 64-byte initialization vector

Procedure:
1. self.absorb_index = 0
2. self.squeeze_index = self.permutation_state.R
3. self.rate = self.permutation_state.R
4. self.capacity = self.permutation_state.N - self.permutation_state.R
]]></artwork>
        </section>
        <section anchor="absorb">
          <name>Absorb</name>
          <t>The absorb function incorporates data into the duplex sponge state using overwrite mode.</t>
          <artwork><![CDATA[
absorb(self, input)

Inputs:
- self, the current duplex sponge object
- input, the input bytes to be absorbed

Procedure:
1. self.squeeze_index = self.rate
2. while len(input) != 0:
3.     if self.absorb_index == self.rate:
4.         self.permutation_state.permute()
5.         self.absorb_index = 0
6.     chunk_size = min(self.rate - self.absorb_index, len(input))
7.     next_chunk = input[:chunk_size]
8.     self.permutation_state[self.absorb_index:self.absorb_index + chunk_size] = next_chunk
9.     self.absorb_index += chunk_size
10.    input = input[chunk_size:]
]]></artwork>
        </section>
        <section anchor="squeeze">
          <name>Squeeze</name>
          <t>The squeeze operation extracts output elements from the sponge state, which are uniformly distributed and can be used as a digest, key stream, or other cryptographic material.</t>
          <artwork><![CDATA[
squeeze(self, length)

Inputs:
- self, the current duplex sponge object
- length, the number of bytes to be squeezed out of the sponge

Outputs:
- digest, a byte array of `length` elements uniformly distributed

Procedure:
1. output = b''
2. while length != 0:
3.     if self.squeeze_index == self.rate:
4.         self.permutation_state.permute()
5.         self.squeeze_index = 0
6.         self.absorb_index = 0
7.     chunk_size = min(self.rate - self.squeeze_index, length)
8.     output += bytes(self.permutation_state[self.squeeze_index:self.squeeze_index+chunk_size])
9.     self.squeeze_index += chunk_size
10.    length -= chunk_size
11. return output
]]></artwork>
        </section>
        <section anchor="keccak-f1600-implementation">
          <name>Keccak-f[1600] Implementation</name>
          <t><tt>Keccak-f</tt> is the permutation function underlying <xref target="SHA3"/>.</t>
          <t><tt>KeccakDuplexSponge</tt> instantiates <tt>DuplexSponge</tt> with <tt>Keccak-f[1600]</tt>, using rate <tt>R = 136</tt> bytes and capacity <tt>C = 64</tt> bytes.</t>
        </section>
      </section>
    </section>
    <section anchor="codecs-registry">
      <name>Codecs registry</name>
      <section anchor="elliptic-curves">
        <name>Elliptic curves</name>
        <section anchor="notation">
          <name>Notation and Terminology</name>
          <t>For an elliptic curve, we consider two fields, the coordinate fields, which indicates the base field, the field over which the elliptic curve equation is defined, and the scalar field, over which the scalar operations are performed.</t>
          <t>The following functions and notation are used throughout the document.</t>
          <ul spacing="normal">
            <li>
              <t><tt>concat(x0, ..., xN)</tt>: Concatenation of byte strings.</t>
            </li>
            <li>
              <t><tt>bytes_to_int</tt> and <tt>scalar_to_bytes</tt>: Convert a byte string to and from a non-negative integer.
<tt>bytes_to_int</tt> and <tt>scalar_to_bytes</tt> are implemented as <tt>OS2IP</tt> and <tt>I2OSP</tt> as described in
<xref target="RFC8017"/>, respectively. Note that these functions operate on byte strings
in big-endian byte order.</t>
            </li>
            <li>
              <t>The function <tt>ecpoint_to_bytes</tt> converts an elliptic curve point in affine-form into an array string of length <tt>ceil(ceil(log2(coordinate_field_order))/ 8) + 1</tt> using <tt>int_to_bytes</tt> prepended by one byte. This is defined as  </t>
              <artwork><![CDATA[
ecpoint_to_bytes(element)
Inputs:
- `element`, an elliptic curve element in affine form, with attributes `x` and `y` corresponding to its affine coordinates, represented as integers modulo the coordinate field order.

Outputs:

A byte array

Constants:

field_bytes_length, the number of bytes to represent the scalar element, equal to `ceil(log2(field.order()))`.

1. byte = 2 if sgn0(element.y) == 0 else 3
2. return I2OSP(byte, 1) + I2OSP(x, field_bytes_length)
]]></artwork>
            </li>
          </ul>
        </section>
        <section anchor="absorb-scalars">
          <name>Absorb scalars</name>
          <artwork><![CDATA[
absorb_scalars(hash_state, scalars)

Inputs:

- hash_state, the hash state
- scalars, a list of elements of the elliptic curve's scalar field

Constants:

- scalar_byte_length = ceil(384/8)

1. for scalar in scalars:
2.     hash_state.absorb(scalar_to_bytes(scalar))
]]></artwork>
          <t>Where the function <tt>scalar_to_bytes</tt> is defined in <xref target="notation"/></t>
        </section>
        <section anchor="absorb-elements">
          <name>Absorb elements</name>
          <artwork><![CDATA[
absorb_elements(hash_state, elements)

Inputs:

- hash_state, the hash state
- elements, a list of group elements

1. for element in elements:
2.     hash_state.absorb(ecpoint_to_bytes(element))
]]></artwork>
        </section>
        <section anchor="squeeze-scalars">
          <name>Squeeze scalars</name>
          <artwork><![CDATA[
squeeze_scalars(hash_state, length)

Inputs:

- hash_state, the hash state
- length, an unsigned integer of 64 bits determining the output length.

1. for i in range(length):
2.     scalar_bytes = hash_state.squeeze(field_bytes_length + 16)
3.     scalars.append(bytes_to_scalar_mod_order(scalar_bytes))
]]></artwork>
        </section>
      </section>
    </section>
  </middle>
  <back>
    <references anchor="sec-combined-references">
      <name>References</name>
      <references anchor="sec-normative-references">
        <name>Normative References</name>
        <reference anchor="SIGMA">
          <front>
            <title>Interactive Sigma Proofs</title>
            <author fullname="Michele Orrù" initials="M." surname="Orrù">
              <organization>CNRS</organization>
            </author>
            <author fullname="Cathie Yun" initials="C." surname="Yun">
              <organization>Apple, Inc.</organization>
            </author>
            <date day="8" month="August" year="2025"/>
            <abstract>
              <t>   This document describes interactive sigma protocols, a class of
   secure, general-purpose zero-knowledge proofs of knowledge consisting
   of three moves: commitment, challenge, and response.  Concretely, the
   protocol allows one to prove knowledge of a secret witness without
   revealing any information about it.

              </t>
            </abstract>
          </front>
          <seriesInfo name="Internet-Draft" value="draft-irtf-cfrg-sigma-protocols-00"/>
        </reference>
        <reference anchor="RFC8017">
          <front>
            <title>PKCS #1: RSA Cryptography Specifications Version 2.2</title>
            <author fullname="K. Moriarty" initials="K." role="editor" surname="Moriarty"/>
            <author fullname="B. Kaliski" initials="B." surname="Kaliski"/>
            <author fullname="J. Jonsson" initials="J." surname="Jonsson"/>
            <author fullname="A. Rusch" initials="A." surname="Rusch"/>
            <date month="November" year="2016"/>
            <abstract>
              <t>This document provides recommendations for the implementation of public-key cryptography based on the RSA algorithm, covering cryptographic primitives, encryption schemes, signature schemes with appendix, and ASN.1 syntax for representing keys and for identifying the schemes.</t>
              <t>This document represents a republication of PKCS #1 v2.2 from RSA Laboratories' Public-Key Cryptography Standards (PKCS) series. By publishing this RFC, change control is transferred to the IETF.</t>
              <t>This document also obsoletes RFC 3447.</t>
            </abstract>
          </front>
          <seriesInfo name="RFC" value="8017"/>
          <seriesInfo name="DOI" value="10.17487/RFC8017"/>
        </reference>
      </references>
      <references anchor="sec-informative-references">
        <name>Informative References</name>
        <reference anchor="SHA3" target="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf">
          <front>
            <title>SHA-3 Standard: Permutation-Based Hash and Extendable-Output Functions</title>
            <author>
              <organization/>
            </author>
            <date>n.d.</date>
          </front>
        </reference>
      </references>
    </references>
    <?line 425?>

<section numbered="false" anchor="test-vectors">
      <name>Test Vectors</name>
      <t>Test vectors will be made available in future versions of this specification.
They are currently developed in the <eref target="https://github.com/mmaker/draft-irtf-cfrg-sigma-protocols/tree/main/poc/vectors">proof-of-concept implementation</eref>.</t>
    </section>
  </back>
  <!-- ##markdown-source:
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
hkcAAA==

-->

</rfc>
