Type: Cryptography
Timeline: August 27, 2025 → September 3, 2025
Languages: Sage
Findings
Total issues: 8 (0 resolved)
Critical: 0 (0 resolved)
High: 0 (0 resolved)
Medium: 1 (0 resolved)
Low: 4 (0 resolved)
Notes & Additional Information
3 notes raised (0 resolved)
OpenZeppelin audited the draft-irtf-cfrg-sigma-protocols repository at commit f427edd. Specifically, parts of the proof-of-concept implementation that accompanies the draft specification were audited.
In scope were the following files:
.
└── poc
├── codec.sage
├── duplex_sponge.sage
└── fiat_shamir.sage
In addition, the following files were reviewed, but not fully audited:
.
└── poc
├── sigma_protocols.sage
├── composition.sage
└── groups
└── groups.sage
A sigma protocol is a type of zero-knowledge (ZK) proof that follows a three-round interactive protocol consisting of prover commitment, verifier challenge, and prover response. Examples of sigma protocols include the Schnorr protocol and Discrete Logarithm Equality (DLEQ). See, for example, Ivan Damgard's notes for more information on sigma protocols.
The Fiat-Shamir transformation is a method for turning interactive ZK proofs into non-interactive ones. In general, to apply a Fiat-Shamir transformation means to replace each random verifier challenge with a value obtained from hashing the transcript (a series of messages between the prover and the verifier) of the proof up to that point. To preserve the security of the original interactive protocol, care must be taken to ensure that the full transcript, as well as all public parameters of the proof, are used to generate this hash. In addition, to prevent replay attacks, the instance (statement to be proved) and a nonce must factor into the transcript hash.
A sponge construction, or simply "sponge", is a mode of operation that turns a fixed-length permutation into a variable-length-input/variable-length-output function. It operates in two phases: absorbing and squeezing. In the "absorb" phase, the input is padded and arranged into blocks, and each block is processed in successive rounds using the current state of the sponge and the permutation. In the "squeeze" phase, the output is given one block at a time, with successive applications of the permutation in between output blocks. Afterwards, the output is truncated to the length specified by the user. A sponge can be used to build hash functions (e.g., Keccak) and extensible output functions - XOF (e.g., SHAKE), among others.
Duplex mode is a mode of operation of the sponge construction in which input absorption and output generation can be interleaved: for brevity, a sponge operated in duplex mode is called a duplex sponge. Instead of one absorbing and one squeezing phase, the duplex sponge alternates between absorbing and squeezing, allowing new information to affect the sponge state and giving the ability to produce multiple rounds of output. For this reason, the duplex sponge is particularly well-suited to emulating interactive protocols.
In a recent paper by Chiesa and Orrù that is highly relevant to the audited repository, the authors explicitly quantify the loss in security when applying the Fiat-Shamir transformation using a duplex sponge construction. This construction is additionally simplified by eliminating the padding step and using the duplex sponge in overwrite mode. This security proof provides the theoretical basis for the ongoing development of a standard for the secure implementation of the Fiat-Shamir transformation using duplex sponges on sigma protocols.
The codebase implements a comprehensive cryptographic framework for ZK proofs from sigma protocols consisting of IETF specification documents and a proof-of-concept (PoC) implementation.
The repository contains two main IETF draft specifications: Interactive Sigma Proofs and Fiat-Shamir Transformation. The sigma protocols' specification stands alone as the base layer, defining the interactive three-message protocols, while the Fiat-Shamir specification builds upon this foundation by referencing and transforming these sigma protocols into non-interactive versions.
The practical implementation is built around three core architectural layers:
Abstract sigma protocol interfaces defined in SigmaProtocol
along with concrete implementations like SchnorrProof
for linear relation proof.
The NIZK
class that applies Fiat-Shamir transformation to convert interactive protocols into non-interactive proofs.
Abstract interface DuplexSpongeInterface
for sponge-based hash functions in duplex mode, along with two concrete implementations: SHAKE128
and KeccakDuplexSponge
.
The framework also supports advanced proof compositions including AND
and OR
proofs, codec implementations for elliptic curve groups and mathematical primitives for representing linear relations as sparse matrix operations.
Currently, the implementation supports the NIST elliptic curves P256, P384, and P251, the Edwards curves Ristretto255 and Decaf448, and the BLS12-381 curve.
The codebase serves as both a standardization effort within the IETF Crypto Forum Research Group (CFRG) and a working implementation that demonstrates the practical application of the technical specifications. The modular design allows different sigma protocols, codecs, and sponge-based hash functions to be combined while maintaining consistent interfaces for proof generation and verification.
The specification and PoC assume that the prover has access to a high-quality source of entropy for cryptographically secure random number generation. In addition, it is assumed that, for each protocol, there will be a universally agreed-upon protocol ID, and that the user is able to generate a session ID which uniquely determines the context of the proof.
The codebase was found to have sufficient protection against several classes of common attack vectors.
Nonce-reuse attacks exploit a scenario in which the same nonce is used across multiple proofs, which results in full witness recovery.
The implementation prevents nonce-reuse attacks through fresh randomness generation for each proof in prover_commit
. Specifically, new random nonces are generated for every proof by calling Domain.random(rng)
for each scalar in the linear map, ensuring that the same nonce values are never reused across different proof instances.
In a proof-malleability attack, an adversary starts from a valid proof, modifies some parts of it, and obtains a new valid proof, typically without knowledge of the witness that generated the original proof.
The following effective measures that render the computed Sigma protocol proofs non-malleable were identified:
Use of unique initialization vector (IV) that combines three distinct identifiers protocol_id
, session_id
, and instance_label
that provide the following properties respectively:
Strict proof-length and format checks during verification.
Canonical serialization implying unique byte representation for each mathematical object.
Shared challenge during AND-proof composition. Since all sub-proofs must verify with the same challenge, any attempt to modify one sub-proof would likely break the others.
Constraining the challenges in OR-proof composition to sum to the verifier challenge limits the freedom of the attacker to modify any of the composed proofs.
Cryptographic binding between proof components through the absorption of group elements into the hash state and using sufficient entropy to derive the challenges prevents challenge manipulation.
AND- and OR-composition protocols initialize their protocols list from the input instances in fixed order: AND init and OR init. This establishes the canonical ordering that all subsequent operations must preserve, thus preventing malleability attacks in which, from a valid composition proof for statements [S1, S2, S3]
, another valid proof (e.g., [S2, S1, S3]
) can be derived after a reordering of the sub-proofs. For instance during AND-proof composition deserialization, the strict ordering requirement ensures that each commitment is verified against its corresponding protocol instance. Any tampering with the order breaks the proof validity. The same is true for OR-proof composition as well.
The implementation effectively validates that input coordinates correspond to valid points on the curve. The validation occurs whenever points are deserialized from byte arrays during proof verification when processing serialized commitments and responses. For instance:
y
coordinate from x
and checks its parity.The noted checks ensure that malformed or invalid points cannot be used to compromise the protocol's security properties.
In a proof-replay attack, an attacker could reuse a previously valid proof without actually possessing the secret witness at the time of the replay. This attack is effectively prevented by the use of the unique initialization vector (IV) (see Malleability protection measures).
The specification of the following security-critical components was found to be somewhat vague or incomplete:
IV: The IV-based sponge-initialization approach, while functional, lacks standardization justification. The specification states the IV "embeds" protocol_id
, session_id
, and instance_label
. The implementation gives two alternative methods to achieve this. While the two should be conceptually identical, one of them relies on a dedicated function get_iv_from_identifiers
that is left undefined.
RNG: The RNG requirements are underspecified, with the draft only mentioning the need for "access to a high-quality entropy source" without defining what this means or providing implementation guidance. The codebase delegates RNG operations to the random()
methods in Scalar and Group classes. The latter depend on the rng
object, which is passed in by the caller (e.g., SchnorrProof
) and its implementation is unspecified.
Session ID, Protocol ID, and Instance Label: These identifier components suffer from vague generation requirements that delegate their computation to users without proper guidance. The Protocol ID is hardcoded to a 64-byte string in the implementation, but the specification states that it is the "responsibility of the user to pick a unique protocol identifier", providing no guidance on collision avoidance or namespace management. Similarly, Session ID and Instance Label are described as "user controlled" without specifying uniqueness requirements and entropy needs. Proper specification of the identifiers is essential for preventing replay attacks and ensuring proof binding to specific contexts.
The above-listed gaps in the specification create implementation risks, as different implementers may make incompatible or insecure choices.
Consider amending the specification and the corresponding parts of the implementation with concrete recommendations, security requirements, and standardized generation procedures for all three components to ensure interoperability and security across implementations.
bytes
While Input Is of Type list[Unit]
The input to absorb
in the general sponge construction DuplexSpongeInterface
is declared as a list of Unit
s to provide the flexibility for both binary sponge constructions (such as Keccak-256 or SHAKE-128) as well as arithmetization-oriented (AO) sponge constructions (such as Poseidon). However, in the current version of the Sage implementation, the type of the IV has been declared as bytes
. This works well in the implemented examples of Keccak-256 and SHAKE-128, where the IV is stored in the capacity bytes and the last 64 bytes of the first block, respectively. However, to include the IV into the initial state of an AO sponge construction would require converting from bytes
to field elements before absorbing them.
Consider changing the type of the initialization vector to list[Unit]
to match the input to absorb
to cover both binary and AO sponge constructions. In addition, consider specifying a rule for incorporating the initialization vector into the sponge construction's initial state that applies to all cases.
Throughout the codebase, multiple instances of sensitive computations that may be susceptible to timing attacks were identified:
prover_response
computation performs nonces[i] + witness[i] * challenge for each scalar
. In principle, timing variations could leak information about the witness values.Consider adding protection against timing attacks such as constant-time random number generation, constant-time algorithms for scalar multiplication, modular reduction, etc.
The SchnorrProof
implementation stores sensitive data in a ProverState
namedtuple
containing both the witness and the nonces. This data is created during the commitment phase and used later in the response phase in sigma_protocols.sage
.
However, the memory storing the mentioned sensitive data is not cleared after computing the proof, which may potentially leak sensitive information to an adversary. While not necessarily an issue for the reference SageMath implementation, this may be relevant for a production implementation in Rust or Solidity.
Consider adding proper memory cleanup of unneeded memory containing sensitive data. For instance:
In addition, consider documenting these measures in the specification and in the reference implementation.
Small-subgroup attacks exploit the fact that elliptic curves may have points of small order that are not in the intended prime-order subgroup. Without subgroup membership testing, an attacker could potentially provide points of small order that could leak information about the discrete logarithm.
Currently, the implementation supports the NIST curves P256, P384, and P251, the Edwards curves Ristretto255 and Decaf448, and the BLS12-381 curve. To the best of our knowledge, the groups defined by the points of each of the listed curves do not have small subgroups, besides the trivial one. For that reason, the lack of small subgroups protection is not an issue at present, but may be a problem if the standard envisions support for other elliptic curves.
In the interest of making the standard applicable to a broader class of elliptic curves, consider adding checks against potential small subgroups attacks, for instance:
The Fiat-Shamir specification states that the IV for the duplex sponge is 32 bytes long, whereas both the SHAKE-128 and Keccak implementations in the PoC use a 64-byte IV.
Consider editing the specification to align with the PoC implementation.
class LinearRelation
In the Sigma Protocols specification, the following lines:
class LinearRelation:
Domain = group.ScalarField
Image = group.Group
are not rendered correctly in the Markdown file. In particular, they show up on the same line as the class definition, as in class LinearRelation: Domain = group.ScalarField Image = group.Group
, and appear outside the code block.
Consider changing the indentation of the first line so that this renders correctly in the markdown file.
SageMath v.9.5
Test building fails for SageMath v.9.5
, which is a relatively old version, but is the highest version supported by the package manager of Ubuntu 22.04.
Specifically, all three commands (make test
, sage test_sigma_protocols.sage
, and make vectors
) fail with the AttributeError: 'sage.rings.integer.Integer' object has no attribute 'bit_length'
error.
Consider specifying the SageMath version that the PoC implementation has been tested with.
The abstraction design in the audited codebase demonstrates a well-structured and natural approach towards producing a standard for applying the Fiat-Shamir (FS) transformation to produce a non-interactive argument of knowledge from an interactive protocol. Quite appropriately, it starts from one of the simplest classes of argument of knowledge protocols, namely sigma protocols.
The core SigmaProtocol
abstract class provides a clean interface that naturally maps to the three-message structure of sigma protocols (commitment, challenge, and response), with concrete implementations like SchnorrProof
that follow this pattern seamlessly. Similarly, the DuplexSpongeInterface
abstraction elegantly accommodates different hash functions, with implementations for both SHAKE128
and KeccakDuplexSponge
that can be easily extended to support other sponge-based constructions, most notably arithmetization-oriented ones like Poseidon.
The framework maintains an appropriate balance between abstraction and complexity, providing just enough structure to ensure modularity without over-engineering. The NIZK
class cleanly composes the three main components (Protocol
, Codec
, and Hash
) through dependency injection, making the system highly configurable while maintaining clear separation of concerns. Indeed, the composition capabilities, as demonstrated in the AndProof
and OrProof
implementations, show how the abstractions naturally extend to more complex protocol constructions.
The FS transformation is a fundamental technique for turning an interactive zero-knowledge protocol into a non-interactive one by deterministically deriving verifier challenges from a transcript hash. In practice, FS transformation has become the dominant paradigm for constructing non-interactive zkSNARKs (e.g., Groth16, PLONK) and virtually all FRI-based zkSTARKs deployed in DeFi. Yet, despite its ubiquity, FS has proven notoriously difficult to implement correctly. Subtle mistakes in transcript hashing and domain separation have enabled devastating real-world attacks, such as Frozen Heart and The Last Challenge Attack, which allowed proof forgery and posed serious financial risks.
A major reason for these recurring vulnerabilities is the absence of a standardized, unambiguous approach to implementing the FS transformation. Standardization is difficult: a specification must be abstract enough to cover the wide range of zkSNARK and zkSTARK protocols - with their varying round structures and underlying elliptic curves - while being concrete enough to guide implementers in avoiding pitfalls. The challenge lies in offering implementers just enough flexibility for practical engineering decisions while still minimizing the attack surface.
In view of the above, the reviewed draft standard is a promising step forward. While it currently focuses on a single class of protocols (Sigma protocols with commitment-challenge-response structure), its carefully designed API for transcript handling and challenge derivation is sufficiently general to extend to more complex systems, such as PLONK, that are already being used in DeFi. Its balance of abstraction and rigor has the potential to prevent the next generation of FS-transformation-related vulnerabilities.
On the downside, several limitations were noted. The protocol-ID, session-ID, and instance-label generation requirements were found to be vague, with the specification deferring these critical security parameters to user responsibility. Similarly, the RNG component was found to be underspecified, and the IV-based sponge initialization approach, while functional, lacks standardization justification. The standard and the implementation would further benefit from specifying measures against timing attacks.
Despite the noted limitations (whose presence is only natural given that the standard is still under development), the framework represents a solid foundation towards standardizing the widely used FS transformation. Due to its clean separation of mathematical abstractions from cryptographic implementations, backed up by the thorough theoretical analysis in Chiesa-Orrù 2025, it has the potential for wide adoption once maturity is reached.
We thank the author, Prof. Michele Orrù, who was very helpful throughout the audit process and addressed all our technical questions and concerns. We further extend our thanks to Ying Tong from the Ethereum Foundation, who drew our attention to the ongoing standardization effort of the FS transformation by the IETF CFRG.