# Plonk related vulnerabilities ## Aztec Plonk Verifier: 0 Bug ### The Verification Equation The verification equation in PLONK is crucial to ensuring the soundness of the proof. It is given by the following expression: $$ e([W_\mathfrak{z}]_1 + u \cdot [W_{\mathfrak{z} \omega}]_1, [x]_2) \cdot e(-(\mathfrak{z} \cdot [W_\mathfrak{z}]_1 + u \mathfrak{z} \omega \cdot [W_{\mathfrak{z} \omega}]_1 + [F]_1 - [E]_1), [1]_2) \stackrel{?}{=} 1 $$ Here: - $e(P_1, P_2)$ denotes the pairing of two points $P_1$ and $P_2$ on elliptic curves $G_1$ and $G_2$. - $[W_\mathfrak{z}]_1$, $[W_{\mathfrak{z} \omega}]_1$, $[F]_1$, and $[E]_1$ are elliptic curve commitments. - $[x]_2$ is a commitment related to a secret value $x$ that is part of the trusted setup. - $u = \text{hash}(\text{transcript})$ is a challenge derived via the Fiat-Shamir transform, ensuring randomness in the protocol. For simplicity, we introduce two new notations: - $P[1] = [W_\mathfrak{z}]_1 + u \cdot [W_{\mathfrak{z} \omega}]_1$ - $P[0] = -(\mathfrak{z} \cdot [W_\mathfrak{z}]_1 + u \mathfrak{z} \omega \cdot [W_{\mathfrak{z} \omega}]_1 + [F]_1 - [E]_1)$ Thus, the verification equation becomes: $$ e(P[1], [x]_2) \cdot e(P[0], [1]_2) \stackrel{?}{=} 1 $$ #### Parameters in the Verification Equation To simplify the analysis, let's identify the parameters involved in the verification equation: 1. $[W_\mathfrak{z}]_1$, $[W_{\mathfrak{z} \omega}]_1$: These are elliptic curve commitments controlled by the prover, and their internal values are unknown to the verifier. 2. $u$: A challenge computed from the transcript, which is outside the control of the attacker but is deterministic once the transcript is available. 3. $[x]_2$: A commitment related to a secret value $x$, typically generated during the trusted setup phase, and unknown to the attacker. 4. $[F]_1$ and $[E]_1$: Commitments computed by the verifier during the verification process, which depend on previous steps and are difficult to manipulate. The attacker’s primary goal is to manipulate the commitments $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$ in such a way that the verification equation holds, even though the prover has not provided a valid witness. ### The Attack Strategy Given that $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$ are under the control of the prover, an attacker might attempt to manipulate them directly. Consider the case where the attacker sets: $$ [W_\mathfrak{z}]_1 = 0, \quad [W_{\mathfrak{z} \omega}]_1 = 0 $$ This would render $P[1]$ as: $$ P[1] = [W_\mathfrak{z}]_1 + u \cdot [W_{\mathfrak{z} \omega}]_1 = 0 + u \cdot 0 = 0 $$ The critical observation here is that this manipulation effectively nullifies the influence of the Fiat-Shamir challenge $u$, which would normally vary depending on the transcript. Now, the equation becomes simpler to evaluate: $$ e(P[1], [x]_2) = e(0, [x]_2) = 1 $$ Since $e(0, R) = e(R, 0) = 1$ for any point $R$, the first part of the equation is trivially satisfied. Next, the second part of the equation becomes: $$ e(P[0], [1]_2) = e\left( - (\mathfrak{z} \cdot [W_\mathfrak{z}]_1 + u \mathfrak{z} \omega \cdot [W_{\mathfrak{z} \omega}]_1 + [F]_1 - [E]_1), [1]_2 \right) $$ Substituting $[W_\mathfrak{z}]_1 = 0$ and $[W_{\mathfrak{z} \omega}]_1 = 0$, we get: $$ e(P[0], [1]_2) = e\left( - ([F]_1 - [E]_1), [1]_2 \right) $$ This results in: $$ e(P[0], [1]_2) \neq 1 $$ Because $[F]_1 - [E]_1 \neq 0$, the second pairing does not equal 1. Therefore, the verification equation becomes: $$ e(P[1], [x]_2) \cdot e(P[0], [1]_2) = 1 \cdot e(P[0], [1]_2) \neq 1 $$ ### Attack Vector: Exploiting Invalid Elliptic Curve Points The attack in question hinges on the manipulation of elliptic curve points, specifically the invalid points $[W_\mathfrak{z}]_1 = 0$ and $[W_{\mathfrak{z} \omega}]_1 = 0$, which are used in the verification process. The attacker does not directly control the coordinates $P[0]$ and $P[1]$, but manipulates these points through the values $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$. In the attack vector, both $[W_\mathfrak{z}]_1 = 0$ and $[W_{\mathfrak{z} \omega}]_1 = 0$ are set to the identity point, represented by $(0, 0)$ in affine coordinates. #### Invalid Point Validation In the first step, the verifier checks whether $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$ are valid elliptic curve points. Although these points are invalid (i.e., they are the identity point, $0 = (0, 0)$), the verifier does not immediately reject them. Instead, the program continues to process these points, excluding them from some operations, but including them in crucial pairing operations later on. If the verifier were to reject invalid points immediately, the attack would fail. #### Infinity Point Check Next, the verifier performs a check for the "point at infinity" (often represented as $\mathcal{O}$) to reject points that do not lie on the elliptic curve. The method used for this check involves testing the most significant bit of $P[1]$. In this case, since $P[1] = 0$, the most significant bit is 0, and the point is incorrectly passed as valid. If the verifier had properly identified this as a point at infinity, the attack would have failed. #### Inversion of Zero Modulo $p$ In elliptic curve computations, the inversion of points is a crucial operation. The verifier performs an inversion step that involves the computation of the modular inverse of a number $x \mod p$. According to Fermat's Little Theorem, the inverse of $x \mod p$ is computed as $x^{p-2} \mod p$. However, when $x = 0$, the inverse is computed as $0^{p-2} = 0$, which is mathematically incorrect because the inverse of 0 does not exist. The failure to check for this special case allows the attack to proceed undetected. #### Projective Coordinates Normalization In PLONK, elliptic curve points are often represented in projective coordinates $(x, y, z)$, which are normalized to affine coordinates $(x, y)$ after computation. However, the code performs batch normalization, which normalizes a batch of points simultaneously, rather than individually. This results in $P[1].z = 0$, which influences $P[0]$, incorrectly transforming a non-zero point into $(0, 0)$. This incorrect normalization leads to a faulty output that further enables the attack. Specifically, this is how the transformation occurs: ``` Before batch_normalize P[0]: {0x12270675066dbf202e8766f5fa48648f95032fbff46996a08e05e427ed0fffb9, 0x2cce89ca786bd0a3db55776a24aa3253bce3b8ef689849f93596b5b26afec90f, 0x04ae1f4cd5f84a484acc4ba115fbd02a879d2e30b8cd97e18f3865887213823b} P[1]: {0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000} After batch_normalize P[0]: {0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000001} P[1]: {0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000001} ``` This transformation results in $P[0] = (0, 0)$, effectively turning the point into the identity point, which is not a valid element on the curve. #### Pairing Function Misinterpretation Finally, the pairing function $e(P[1], [x]_2)$ is incorrectly evaluated when $P[1] = 0$. According to the pairing properties, for any elliptic curve point $R$, the pairing $e(0, R) = 1$. This is a crucial property because it allows the pairing function to treat $P[1] = 0$ as if it were a valid point at infinity, bypassing the verification process. As a result, the verifier computes: $$ e(P[1], [x]_2) \cdot e(P[0], [1]_2) = e(0, [x]_2) \cdot e(0, [1]_2) = 1 \cdot 1 = 1 $$ This results in the verifier returning a valid output, even though the points used in the computation were invalid. ### Root Cause Analysis The attack is successful due to a combination of subtle software vulnerabilities in the elliptic curve point validation and pairing logic: 1. **Improper handling of invalid points:** Invalid points (such as $(0, 0)$) are not immediately rejected, allowing them to be included in critical operations. 2. **Incorrect point at infinity check:** The verification function fails to correctly identify points at infinity based on the most significant bit of $P[1]$. 3. **Inversion of zero mod $p$:** The modular inversion of zero is not properly handled, leading to an incorrect result. 4. **Faulty projective coordinate normalization:** Batch normalization of projective coordinates incorrectly transforms valid points into the identity point. 5. **Misinterpretation of the pairing function:** The pairing function incorrectly treats the identity point $(0, 0)$ as a valid point at infinity, returning a result of 1 regardless of the inputs. ## The Frozen Heart vulnerability in PlonK ### **Overview of PlonK and Fiat-Shamir Transform** PlonK is a type of zk-SNARK used to prove statements about computations without revealing the computation's private inputs. It operates over arithmetic circuits where the computation is broken down into constraints that can be represented as polynomials. In the Fiat-Shamir approach, a challenge is generated by hashing certain public data, which typically includes the public inputs to the circuit, the circuit structure, and intermediate commitments. The Fiat-Shamir transform is used to turn an interactive proof into a non-interactive one by replacing the interaction with a hash of all relevant public data. For PlonK, the Fiat-Shamir challenge typically involves calculating a challenge value $\mathfrak{z}$, which is a function of public and private inputs and certain intermediate polynomial commitments. This challenge is then used to guide the verifier's checks for consistency, ensuring that the prover has correctly executed the computation and that the proof is valid. The core security of PlonK relies on including all public inputs in the Fiat-Shamir hash. Excluding these inputs allows a malicious prover to manipulate the challenge and falsify a proof, leading to the Frozen Heart vulnerability. ### **The Frozen Heart Vulnerability** The Frozen Heart vulnerability arises due to the failure to include all public inputs in the Fiat-Shamir challenge computation, particularly the challenge value $\mathfrak{z}$. When public inputs are omitted from the hash, the prover can manipulate the public inputs to generate a forged proof that will still pass the verifier's check. Below is a detailed step-by-step breakdown of how this vulnerability is exploited. ### **Exploiting the Frozen Heart Vulnerability: A Step-by-Step Attack** #### **Round 1: Creating the Polynomials $a$, $b$, and $c$** In the first step of the proof generation, the prover constructs the polynomials $a$, $b$, and $c$ based on the private and public inputs to the circuit. These polynomials are essential for ensuring that the prover has correctly executed the computation. However, in the case of a malicious prover exploiting the vulnerability, the actual private inputs are not known. Instead, the malicious prover creates random polynomials $a'$, $b'$, and $c'$, which are then committed to through evaluations at points $[a']_1$, $[b']_1$, and $[c']_1$. #### **Round 2: Skipping Copy Constraints** During the second step, PlonK enforces "copy constraints" to ensure that certain values are consistent at specific positions in the arithmetic circuit. Since the malicious prover uses random polynomials $a'$, $b'$, and $c'$, these constraints will not be satisfied. To bypass this step, the malicious prover simply sets these polynomials to zero and commits to the value $[0]_1$. This results in a valid commitment, bypassing the copy constraint check. **Note**: If the verifier checks for the point at infinity, the proof would be rejected due to the zero polynomial. However, assuming the verifier does not check for this (as is the case with SnarkJS), the proof can proceed. #### **Round 3: Creating the Random Polynomial $t'$** In the third step, the prover creates the polynomial $t$, which is used in the final consistency checks. The value of $t$ is derived from the private inputs and other circuit parameters. However, the malicious prover does not know these values, so they generate a random polynomial $t'$, and commit to its evaluations at points $[t'_{\text{lo}}]_1$, $[t'_{\text{mid}}]_1$, and $[t'_{\text{hi}}]_1$. #### **Round 4: Computing the Fiat-Shamir Challenge $\mathfrak{z}$** In this step, the challenge $\mathfrak{z}$ is computed using the Fiat-Shamir transform. The challenge is typically derived from the public and private inputs, as well as commitments to polynomials $a$, $b$, $c$, and $t$. However, the malicious prover will use their random polynomials $a'$, $b'$, $c'$, and $t'$ instead of the real polynomials. Since the verifier computes the challenge based on these committed values, and the values are consistent with the commitments, no inconsistencies are detected. This ensures that the challenge $\mathfrak{z}$ will be calculated in such a way that the malicious prover can proceed without raising suspicion. #### **Round 5: Finalizing the Proof** In the fifth step, the prover finalizes the proof by performing additional checks and creating the final proof commitments. Here, the prover uses the random polynomials $a'$, $b'$, $c'$, and $t'$ to finalize the proof. This part of the process follows the usual protocol but with the forged values, which the verifier will not be able to distinguish from valid commitments. #### **Manipulating the Public Inputs to Forge $r_0$** The critical step in exploiting the Frozen Heart vulnerability lies in manipulating the public inputs to force the verifier to compute a value $r_0$ that matches the forged value $r'_0$. Here’s how this is achieved: 1. **Recall the Public Input Polynomial $PI(X)$:** The verifier computes the polynomial $PI(X)$, which is a weighted sum of the public inputs using Lagrange basis polynomials. This polynomial is used in the final check of the proof. 2. **Force the Verifier to Compute $r'_0$:** The malicious prover substitutes the forged value $r'_0$ into the equation the verifier will compute in the final check. By solving the equation, the prover can find the value of $PI(\mathfrak{z})$, which is the public input polynomial evaluated at $\mathfrak{z}$. 3. **Adjust the Public Inputs to Satisfy $PI(\mathfrak{z})$:** Since $PI(\mathfrak{z})$ is a sum of public inputs multiplied by the corresponding Lagrange basis polynomials, there are multiple ways to adjust the public inputs to satisfy this condition. The simplest approach is to retain the first public input and adjust its value such that $PI(\mathfrak{z})$ matches the required value. 4. **Why This Works:** This manipulation works because the value $\mathfrak{z}$ is not influenced by the public inputs, allowing the malicious prover to know $\mathfrak{z}$ ahead of time. As a result, the prover can adjust the public inputs to ensure that $PI(\mathfrak{z})$ matches the forged value $r'_0$, leading the verifier to compute the incorrect value. ### **Conclusion** The Frozen Heart vulnerability arises when implementations of the PlonK protocol fail to include all public inputs in the Fiat-Shamir challenge computation. This oversight allows a malicious prover to forge valid-looking proofs that pass the verification process. The exploit involves manipulating the challenge $\mathfrak{z}$, constructing random polynomials, and adjusting the public inputs to ensure that the final proof is accepted by the verifier. ### **Mitigations** To prevent this vulnerability, it is crucial to ensure that the Fiat-Shamir challenge includes all relevant public inputs in the computation. This includes not only the private inputs and commitments but also all public inputs, the circuit structure, and intermediate values calculated during the proof generation. Additionally, implementers should carefully validate edge cases, such as checking for the point at infinity and rejecting zero polynomials, to prevent circumvention of constraints. By addressing these issues, the security of PlonK-based systems can be significantly improved, ensuring that they remain secure against the Frozen Heart vulnerability. ## The Last Challenge Attack on a PLONK Implementation ### **The PLONK Prover and Verifier** #### **PLONK Prover** In PLONK, the prover generates a proof $\pi_{\text{PLONK}}$ that convinces the verifier that a statement is true without revealing any sensitive information. The proof is constructed as a series of commitments and evaluations, using a sequence of polynomials $f_1(X), f_2(X), \dots$ that represent the arithmetic circuit and its evaluations at specific points. The general structure of the PLONK proof is given by: $$ \pi_{\text{PLONK}} = ([a]_1, [b]_1, [c]_1, [z]_1, [t_{lo}]_1, [t_{mid}]_1, [t_{hi}]_1, [W_\mathfrak{z}]_1, [W_{\mathfrak{z} \omega}]_1, \bar{a}, \bar{b}, \bar{c}, \bar{S}_{\sigma_1}, \bar{S}_{\sigma_2}, \bar{z}_\omega) $$ The proof consists of commitments to polynomials $a(x), b(x), c(x), \dots$, along with two auxiliary components $[W_{\mathfrak{z}}]_1$ and $[W_{\mathfrak{z} \omega}]_1$, which are part of the proof's transcript. The quotient polynomial $t(x)$ is committed to through the element $[t_{lo}]_1, [t_{mid}]_1, [t_{hi}]_1$, and $\bar{a}, \bar{b}, \bar{c}$ represent evaluations of the polynomials. #### **PLONK Verifier** The verifier in PLONK performs a sequence of checks to validate the proof. This includes verifying commitments and pairings to confirm that the prover's claim is valid. The final step of the verification process involves checking a bilinear pairing equation, which is represented as: $$ e([W_\mathfrak{z}]_1 + u \cdot [W_{\mathfrak{z} \omega}]_1, [\tau]_2) = e(\mathfrak{z} \cdot [W_\mathfrak{z}]_1 + u \mathfrak{z} \omega \cdot [W_{\mathfrak{z} \omega}]_1 + [F]_1 - [E]_1, [1]_2) $$ This pairing check ensures the consistency of the proof. To shorten the expression, let $A = [W_\mathfrak{z}]_1 + u \cdot [W_{\mathfrak{z} \omega}]_1$ and $B = \mathfrak{z} \cdot [W_\mathfrak{z}]_1 + u \mathfrak{z} \omega \cdot [W_{\mathfrak{z} \omega}]_1 + [F]_1 - [E]_1$. ### **The Last Challenge Attack** The "Last Challenge Attack" exploits an issue in the computation of the final challenge $u$, which is used in the last step of the verification process. In the standard PLONK protocol, the challenge $u$ is computed by hashing the full transcript of the proof up to that point. However, in the vulnerable implementation we analyze, the verifier omits the elements $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$ from the transcript, which affects the computation of $u$. This oversight allows a malicious prover to manipulate the proof by independently computing $u$ before $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$ are finalized. #### **Mathematical Setup** In the standard protocol, the final challenge $u$ is computed as: $$ u = \mathcal{H}(T_5) $$ where $T_5$ is the full transcript of the proof, including all previous proof elements. The omission of $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$ from the transcript allows an attacker to compute $u$ independently, which is critical for the attack. #### **Attack Description** The steps of the attack are as follows: ##### **Step 1: Bootstrapping the Attack** A malicious prover $P'_{\text{PLONK}}$ begins by obtaining a well-formed PLONK proof $\pi_{\text{PLONK}}$ based on any circuit of its choice. The prover then computes values $A$ and $B$ such that the following equation holds: $$ e(A, [\tau]_2) = e(B, [1]_2) $$ ##### **Step 2: Constructing a False Proof** The prover sets all components of the proof $\pi'_{\text{PLONK}}$, except for the elements $[W_{\mathfrak{z}}]_1$ and $[W_{\mathfrak{z} \omega}]_1$, to arbitrary values. These components include the public input $PI$ and the other proof elements as described earlier. ##### **Step 3: Computing $[F]_1$ and $[E]_1$** The malicious prover uses the components of $\pi'_{\text{PLONK}}$, excluding $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$, and computes the values $[F]_1$ and $[E]_1$ using the verification steps 9, 10, and 11 of the PLONK verifier. Note that at this stage, the prover does not need the values $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$, which are still undefined. ##### **Step 4: Exploiting the Omission of $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$** Since the challenge $u$ is computed independently of $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$, the prover is free to solve the system of linear equations without waiting for these values to be finalized. The system to be solved is: $$ \begin{cases} X + u Y = A\\ \mathfrak{z} X + u\mathfrak{z}\omega Y + C = B \end{cases} $$ where $X=[W_{\mathfrak{z}}]_1$, $Y=[W_{\mathfrak{z} \omega}]_1$ are unknowns and $A,B,C,u,\mathfrak{z},\omega$ are constants defined as follows: $$ e(\underbrace{\underbrace{[W_{\mathfrak{z}}]_1}_{X}+{u} \underbrace{\cdot[W_{\mathfrak{z} \omega}]_1}_{Y}}_{A},[x]_2) \stackrel{?}{=} e(\underbrace{\mathfrak{z} \cdot\underbrace{[W_{\mathfrak{z}}]_1}_{X} + {u} \mathfrak{z} \omega \cdot\underbrace{[W_{\mathfrak{z} \omega}]_1}_{Y}+\underbrace{[F]_1-[E]_1}_{C}}_{B},[1]_2) $$ This system of equations has a solution if and only if $\mathfrak{z} \cdot u \neq 0$, and since $u$ is computed independently, the prover can always find a solution for $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$. ##### **Step 5: Completing the Proof** The malicious prover then fills in the values for $[W_\mathfrak{z}]_1$ and $[W_{\mathfrak{z} \omega}]_1$ into the appropriate slots of the proof $\pi'_{\text{PLONK}}$, completing the false proof. ##### **Step 6: Verification of the False Proof** The vulnerable verifier accepts the false proof $\pi'_{\text{PLONK}}$ as valid with probability 1, since the linear system has been correctly solved, and the challenge $u$ was derived independently of the transcript. ## PLONK Prover Challenges and Transcript Generation During each round, the PLONK prover generates a set of challenges by hashing the **transcript** of the communication up to that round. The transcript up to a certain round is defined as "the concatenation of the common preprocessed input, and public input, and the proof elements written by the prover up to a certain point in time". The version of PLONK examined in this post is the latest one available at the time of writing (from the 23th of february 2024), in which the transcript and the corresponding challenges computed at each round are shown below: | Round | Transcript | Output challenge | |-------|---------------------------------------------|----------------------------------------| | 1 | $T_1 = (pp, [a]_1, [b]_1, [c]_1)$ | $\beta = \mathcal{H}(T_1, 0), \gamma = \mathcal{H}(T_1, 1)$ | | 2 | $T_2 = (T_1, [z]_1)$ | $\alpha = \mathcal{H}(T_2)$ | | 3 | $T_3 = (T_2, [t_{lo}]_1, [t_{mid}]_1, [t_{hi}]_1)$ | $\mathfrak{z} = \mathcal{H}(T_3)$ | | 4 | $T_4 = (T_3, \bar{a}, \bar{b}, \bar{c}, \bar{s}_{\sigma_1}, \bar{s}_{\sigma_2}, \tilde{z}_w)$ | $v = \mathcal{H}(T_4)$ | | 5 | $T_5 = (T_4, [W_{\mathfrak{z}}]_1, [W_{\mathfrak{z} \omega}]_1)$ | $u = \mathcal{H}(T_5)$ | where $pp$ contains the public parameters and the public inputs and $\mathcal{H}$ denotes a hash function. ## Dusk Network: Missing Blinding Factors ### **Blinding Factors in PlonK** Blinding factors are critical for ensuring the zero-knowledge property of the proof. These factors prevent an attacker from extracting the witness (the private inputs) by obscuring the prover’s polynomials. The blinding factors are added to the prover’s polynomials as secret shifts, and they are used to modify the polynomials in such a way that the resulting proof is still valid but does not reveal the underlying private inputs. ### **The Zero-Knowledge Property** The zero-knowledge property ensures that the proof does not leak any information about the private inputs. Without the blinding factors, the private inputs could potentially be recovered from the proof, which would violate this property. The addition of blinding factors is crucial in maintaining the privacy of the prover's inputs while still allowing the verifier to check the correctness of the proof. ### **The Vulnerability in Dusk Network's PlonK Implementation** The Dusk Network, a privacy-oriented blockchain, initially implemented PlonK without including the necessary blinding factors in their zk-SNARK proofs. As a result, an attacker could potentially extract the private inputs from the proof data by analyzing the unmodified polynomials. The Dusk Network’s implementation of PlonK aimed to preserve the privacy of the blockchain’s users by ensuring that private inputs, such as transaction details or identity information, would not be revealed. However, by failing to apply blinding factors, the system exposed these private inputs, undermining the privacy guarantees of the protocol. #### **Privacy Breach** This vulnerability is particularly concerning for privacy-oriented applications, where the ability to maintain confidentiality is paramount. Without the blinding factors, the zero-knowledge property of the proof is no longer upheld, as the proof can be used to infer sensitive information. ### **The Fix: Adding Blinding Factors** The fix for this vulnerability was relatively straightforward. The Dusk Network team added the missing blinding factors to the prover polynomials, ensuring that the private inputs remained obscured during the proof generation process. By adding these secret shifts, the Dusk Network’s implementation of PlonK now restores the zero-knowledge property of the protocol, ensuring that private inputs cannot be derived from the proof. The introduction of blinding factors does not significantly affect the protocol’s efficiency or validity. It is a simple modification to the polynomial commitments that preserves the core functionality of PlonK while upholding the zero-knowledge property. ## References 1. https://github.com/0xPARC/zk-bug-tracker/ 2. https://github.com/cryptosubtlety/00/blob/main/00.pdf 3. https://blog.trailofbits.com/2022/04/18/the-frozen-heart-vulnerability-in-plonk/ 4. https://eprint.iacr.org/2024/398.pdf 5. https://hackmd.io/@Oana/H1HZxcjmp