You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@milagro.apache.org by sa...@apache.org on 2020/02/07 12:57:22 UTC

[incubator-milagro-MPC] 02/11: Add receiver zk proof

This is an automated email from the ASF dual-hosted git repository.

sandreoli pushed a commit to branch add-mta-zk-proofs
in repository https://gitbox.apache.org/repos/asf/incubator-milagro-MPC.git

commit 9a02171b725cdbf5eb659b1e0e76a5b0d40ad0a6
Author: Samuele Andreoli <sa...@yahoo.it>
AuthorDate: Wed Feb 5 09:57:24 2020 +0000

    Add receiver zk proof
---
 include/amcl/mta.h | 175 +++++++++++++++++++++++++
 src/mta.c          | 369 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 544 insertions(+)

diff --git a/include/amcl/mta.h b/include/amcl/mta.h
index e4053a4..0db289b 100644
--- a/include/amcl/mta.h
+++ b/include/amcl/mta.h
@@ -254,6 +254,181 @@ extern void MTA_RP_proof_fromOctets(MTA_RP_proof *p, octet *S, octet *S1, octet
  */
 extern void MTA_RP_commitment_rv_kill(MTA_RP_commitment_rv *rv);
 
+/** \brief Secret random values for the receiver ZKP commitment */
+typedef struct
+{
+    BIG_1024_58 alpha[FFLEN_2048];              /**< Random value in \f$ [0, \ldots, q^3]          \f$ */
+    BIG_1024_58 beta[FFLEN_2048];               /**< Random value in \f$ [0, \ldots, N]            \f$ */
+    BIG_1024_58 gamma[FFLEN_2048];              /**< Random value in \f$ [0, \ldots, N]            \f$ */
+    BIG_1024_58 rho[FFLEN_2048 + HFLEN_2048];   /**< Random value in \f$ [0, \ldots, \tilde{N}q]   \f$ */
+    BIG_1024_58 rho1[FFLEN_2048 + HFLEN_2048];  /**< Random value in \f$ [0, \ldots, \tilde{N}q^3] \f$ */
+    BIG_1024_58 sigma[FFLEN_2048 + HFLEN_2048]; /**< Random value in \f$ [0, \ldots, \tilde{N}q]   \f$ */
+    BIG_1024_58 tau[FFLEN_2048 + HFLEN_2048];   /**< Random value in \f$ [0, \ldots, \tilde{N}q]   \f$ */
+} MTA_ZK_commitment_rv;
+
+/** \brief Public commitment for the Receiver ZKP */
+typedef struct
+{
+    BIG_1024_58 z[FFLEN_2048];      /**< Commitment to h1, h2, x using rho */
+    BIG_1024_58 z1[FFLEN_2048];     /**< Auxiliary Commitment to h1, h2, binding alpha and rho1 */
+    BIG_1024_58 t[FFLEN_2048];      /**< Commitment to h1, h2, y using sigma */
+    BIG_1024_58 v[2 * FFLEN_2048];  /**< Commitment to paillier PK and c1 using alpha and gamma */
+    BIG_1024_58 w[FFLEN_2048];      /**< Auxiliary Commitment to h1, h2, binding gamma and tau */
+} MTA_ZK_commitment;
+
+/** \brief Range Proof for the Receiver ZKP */
+typedef struct
+{
+    BIG_1024_58 s[FFLEN_2048];                /**< Proof of knowledge of the Paillier r value */
+    BIG_1024_58 s1[FFLEN_2048];               /**< Proof of knowledge of x. It must be less than q^3 */
+    BIG_1024_58 s2[FFLEN_2048 + HFLEN_2048];  /**< Auxiliary proof of knowledge for x */
+    BIG_1024_58 t1[FFLEN_2048];               /**< Proof of knowledge of y */
+    BIG_1024_58 t2[FFLEN_2048 + HFLEN_2048];  /**< Auxiliary proof of knowledge for y */
+} MTA_ZK_proof;
+
+/** \brief Commitment Generation for Receiver ZKP
+ *
+ *  Generate a commitment for the values x, y and c1
+ *
+ *  <ol>
+ *  <li> \f$ \alpha \in_R [0, \ldots, q^3]\f$
+ *  <li> \f$ \beta  \in_R [0, \ldots, N]\f$
+ *  <li> \f$ \gamma \in_R [0, \ldots, N]\f$
+ *  <li> \f$ \rho   \in_R [0, \ldots, q\tilde{N}]\f$
+ *  <li> \f$ \rho_1 \in_R [0, \ldots, q^{3}\tilde{N}]\f$
+ *  <li> \f$ \sigma \in_R [0, \ldots, q\tilde{N}]\f$
+ *  <li> \f$ \tau   \in_R [0, \ldots, q\tilde{N}]\f$
+ *  <li> \f$ z  = h_1^{x}h_2^{\rho}              \text{ }\mathrm{mod}\text{ }\tilde{N} \f$
+ *  <li> \f$ z_1 = h_1^{\alpha}h_2^{\rho_1}       \text{ }\mathrm{mod}\text{ }\tilde{N} \f$
+ *  <li> \f$ t  = h_1^{y}h_2^{\sigma}            \text{ }\mathrm{mod}\text{ }\tilde{N} \f$
+ *  <li> \f$ w  = h_1^{\gamma}h_2^{\tau}         \text{ }\mathrm{mod}\text{ }\tilde{N} \f$
+ *  <li> \f$ v  = c1^{\alpha}g^{\gamma}\beta^{N} \text{ }\mathrm{mod}\text{ }N^2 \f$
+ *  </ol>
+ *
+ *  @param RNG         csprng for random generation
+ *  @param key         Paillier key used to encrypt C1
+ *  @param mod         Public BC modulus of the verifier
+ *  @param X           Message to prove knowledge and range
+ *  @param Y           Message to prove knowledge
+ *  @param C1          Base Paillier Ciphertext
+ *  @param c           Destinaton commitment
+ *  @param rv          Random values associated to the commitment. If RNG is NULL this is read
+ */
+extern void MTA_ZK_commit(csprng *RNG, PAILLIER_public_key *key, COMMITMENTS_BC_pub_modulus *mod,  octet *X, octet *Y, octet *C1, MTA_ZK_commitment *c, MTA_ZK_commitment_rv *rv);
+
+/** \brief Deterministic Challenge generations for Receiver ZKP
+ *
+ *  Generate a challenge binding together public parameters and commitment
+ *
+ *  <ol>
+ *  <li> \f$ e = H( g | \tilde{N} | h_1 | h_2 | q | c_1 | c_2 | z | z1 | t | v | w ) \f$
+ *  </ol>
+ *
+ *  @param key         Public Paillier key of the prover
+ *  @param mod         Public BC modulus of the verifier
+ *  @param C1          Base Paillier Ciphertext
+ *  @param C2          New Paillier Ciphertext to prove knowledge and range
+ *  @param c           Commitment of the prover
+ *  @param E           Destination challenge
+ */
+extern void MTA_ZK_challenge(PAILLIER_public_key *key, COMMITMENTS_BC_pub_modulus *mod, octet *C1, octet *C2, MTA_ZK_commitment *c, octet *E);
+
+/** \brief Proof generation for Receiver ZKP
+ *
+ *  Generate a proof of knowledge of x, y and a range proof for x
+ *
+ *  <ol>
+ *  <li> \f$ s  = \beta r^e \text{ }\mathrm{mod}\text{ }N \f$
+ *  <li> \f$ s_1 = ex + \alpha \f$
+ *  <li> \f$ s_2 = e\rho + \rho_1 \f$
+ *  <li> \f$ t_1 = ey + \gamma \f$
+ *  <li> \f$ t_2 = e\sigma + \tau \f$
+ *  </ol>
+ *
+ *  @param key         Private Paillier key of the prover
+ *  @param rv          Random values associated to the commitment
+ *  @param X           Message to prove knowledge and range
+ *  @param Y           Message to prove knowledge
+ *  @param R           Random value used in the Paillier addition
+ *  @param E           Generated challenge
+ *  @param p           Destination proof
+ */
+extern void MTA_ZK_prove(PAILLIER_public_key *key, MTA_ZK_commitment_rv *rv, octet *X, octet *Y, octet *R, octet *E, MTA_ZK_proof *p);
+
+// TODO go from here
+
+/** \brief Verify a Proof for Receiver ZKP
+ *
+ *  Verify the proof of knowledge of x, y associated to c1, c2 and of x range
+ *
+ *  <ol>
+ *  <li> \f$ s_1 \stackrel{?}{\leq} q^3 \f$
+ *  <li> \f$ z_1 \stackrel{?}{=} h_1^{s_1}h_2^{s_2}z^{-e}    \text{ }\mathrm{mod}\text{ }\tilde{N} \f$
+ *  <li> \f$ w  \stackrel{?}{=} h_1^{t_1}h_2^{t_2}t^{-e}    \text{ }\mathrm{mod}\text{ }\tilde{N} \f$
+ *  <li> \f$ v  \stackrel{?}{=} c1^{s_1}s^{N}g^{t_1}c2^{-e} \text{ }\mathrm{mod}\text{ }N^2 \f$
+ *  </ol>
+ *
+ *  @param key         Public Paillier key of the prover
+ *  @param mod         Private BC modulus of the verifier
+ *  @param C1          Base Paillier Ciphertext
+ *  @param C2          New Paillier Ciphertext to prove knowledge and range
+ *  @param E           Generated challenge
+ *  @param c           Received commitment
+ *  @param p           Received proof
+ *  @return            MTA_OK if the proof is valid, MTA_FAIL otherwise
+ */
+extern int MTA_ZK_verify(PAILLIER_private_key *key, COMMITMENTS_BC_priv_modulus *mod, octet *C1, octet *C2, octet *E, MTA_ZK_commitment *c, MTA_ZK_proof *p);
+
+/** \brief Dump the commitment to octets
+ *
+ *  @param Z           Destination Octet for the z component of the commitment. FS_2048 long
+ *  @param Z1          Destination Octet for the z1 component of the commitment. FS_2048 long
+ *  @param T           Destination Octet for the t component of the commitment. FS_2048 long
+ *  @param V           Destination Octet for the v component of the commitment. FS_4096 long
+ *  @param W           Destination Octet for the w component of the commitment. FS_2048 long
+ *  @param c           Commitment to export
+ */
+extern void MTA_ZK_commitment_toOctets(octet *Z, octet *Z1, octet *T, octet *V, octet *W, MTA_ZK_commitment *c);
+
+/** \brief Read the commitments from octets
+ *
+ *  @param c           Destination commitment
+ *  @param Z           Destination Octet for the z component of the commitment. FS_2048 long
+ *  @param Z1          Destination Octet for the z1 component of the commitment. FS_2048 long
+ *  @param T           Destination Octet for the t component of the commitment. FS_2048 long
+ *  @param V           Destination Octet for the v component of the commitment. FS_4096 long
+ *  @param W           Destination Octet for the w component of the commitment. FS_2048 long
+ */
+extern void MTA_ZK_commitment_fromOctets(MTA_ZK_commitment *c, octet *Z, octet *Z1, octet *T, octet *V, octet *W);
+
+/** \brief Dump the proof to octets
+ *
+ *  @param S           Destination Octet for the s component of the proof. FS_2048 long
+ *  @param S1          Destination Octet for the s1 component of the proof. HFS_2048 long
+ *  @param S2          Destination Octet for the s2 component of the proof. FS_2048 + HFS_2048 long
+ *  @param T1          Destination Octet for the t1 component of the proof. FS_2048 long
+ *  @param T2          Destination Octet for the t2 component of the proof. FS_2048 + HFS_2048 long
+ *  @param p           Proof to export
+ */
+extern void MTA_ZK_proof_toOctets(octet *S, octet *S1, octet *S2, octet *T1, octet *T2, MTA_ZK_proof *p);
+
+/** \brief Read the commitments from octets
+ *
+ *  @param p           Destination proof
+ *  @param S           Octet with the s component of the proof
+ *  @param S1          Octet with the s1 component of the proof
+ *  @param S2          Octet with the s2 component of the proof
+ *  @param T1          Octet with the t1 component of the proof
+ *  @param T2          Octet with the t2 component of the proof
+ */
+extern void MTA_ZK_proof_fromOctets(MTA_ZK_proof *p, octet *S, octet *S1, octet *S2, octet *T1, octet *T2);
+
+/** \brief Clean the memory containing the random values
+ *
+ *   @param rv         Random values to clean
+ */
+extern void MTA_ZK_commitment_rv_kill(MTA_ZK_commitment_rv *rv);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/mta.c b/src/mta.c
index e1d725b..5fa3a31 100644
--- a/src/mta.c
+++ b/src/mta.c
@@ -145,6 +145,28 @@ void hash_RP_params(hash256 *sha, PAILLIER_public_key *key, COMMITMENTS_BC_pub_m
     OCT_hash(sha, &OCT);
 }
 
+// Update the provided hash with the data for the MTA ZK commitment
+void hash_ZK_commitment(hash256 *sha, MTA_ZK_commitment *c)
+{
+    char oct[2 * FS_2048];
+    octet OCT = {0, sizeof(oct), oct};
+
+    FF_2048_toOctet(&OCT, c->z, FFLEN_2048);
+    OCT_hash(sha, &OCT);
+
+    FF_2048_toOctet(&OCT, c->z1, FFLEN_2048);
+    OCT_hash(sha, &OCT);
+
+    FF_2048_toOctet(&OCT, c->t, FFLEN_2048);
+    OCT_hash(sha, &OCT);
+
+    FF_2048_toOctet(&OCT, c->v, 2 * FFLEN_2048);
+    OCT_hash(sha, &OCT);
+
+    FF_2048_toOctet(&OCT, c->w, FFLEN_2048);
+    OCT_hash(sha, &OCT);
+}
+
 /* MTA descriptions */
 
 // Client MTA first pass
@@ -645,3 +667,350 @@ void MTA_RP_commitment_rv_kill(MTA_RP_commitment_rv *rv)
     FF_2048_zero(rv->gamma, FFLEN_2048 + HFLEN_2048);
     FF_2048_zero(rv->rho,   FFLEN_2048 + HFLEN_2048);
 }
+
+void MTA_ZK_commit(csprng *RNG, PAILLIER_public_key *key, COMMITMENTS_BC_pub_modulus *mod,  octet *X, octet *Y, octet *C1, MTA_ZK_commitment *c, MTA_ZK_commitment_rv *rv)
+{
+    BIG_1024_58 q[HFLEN_2048];
+    BIG_1024_58 q3[FFLEN_2048];
+    BIG_1024_58 tws[FFLEN_2048 + HFLEN_2048];
+
+    BIG_512_60 alpha[HFLEN_4096];
+    BIG_512_60 beta[FFLEN_4096];
+    BIG_512_60 gamma[HFLEN_4096];
+    BIG_512_60 ws[FFLEN_4096];
+
+    char oct[2 * FS_2048];
+    octet OCT = {0, sizeof(oct), oct};
+
+    // Curve order
+    OCT_fromHex(&OCT, curve_order_hex);
+    FF_2048_zero(q, HFLEN_2048);
+    BIG_512_60_fromBytesLen(q[0],OCT.val,OCT.len);
+
+    // Zero out beta since it's needed regardless of RNG
+    FF_4096_zero(beta, FFLEN_4096);
+
+    if (RNG != NULL)
+    {
+        // Generate alpha in [0, .., q^3]
+        FF_2048_sqr(q3, q, HFLEN_2048);
+        FF_2048_mul(q3, q, q3, HFLEN_2048);
+
+        FF_2048_zero(rv->alpha, FFLEN_2048);
+        FF_2048_random(rv->alpha, RNG, HFLEN_2048);
+        FF_2048_mod(rv->alpha, q3, HFLEN_2048);
+
+        // Generate beta in [0, .., N]
+        FF_4096_randomnum(beta, key->n, RNG, HFLEN_4096);
+        FF_4096_toOctet(&OCT, beta, HFLEN_4096);
+        FF_2048_fromOctet(rv->beta, &OCT, FFLEN_2048);
+
+        // Generate gamma in [0, .., N]
+        FF_4096_randomnum(gamma, key->n, RNG, HFLEN_4096);
+        FF_4096_toOctet(&OCT, gamma, HFLEN_4096);
+        FF_2048_fromOctet(rv->gamma, &OCT, FFLEN_2048);
+
+        // Generate rho, tau, sigma in [0, .., Nt * q]
+        FF_2048_amul(tws, q, HFLEN_2048, mod->N, FFLEN_2048);
+        FF_2048_random(rv->rho, RNG, FFLEN_2048 + HFLEN_2048);
+        FF_2048_mod(rv->rho, tws, FFLEN_2048 + HFLEN_2048);
+
+        FF_2048_random(rv->tau, RNG, FFLEN_2048 + HFLEN_2048);
+        FF_2048_mod(rv->tau, tws, FFLEN_2048 + HFLEN_2048);
+
+        FF_2048_random(rv->sigma, RNG, FFLEN_2048 + HFLEN_2048);
+        FF_2048_mod(rv->sigma, tws, FFLEN_2048 + HFLEN_2048);
+
+        // Generate rho1 in [0, .., Nt * q^3]
+        FF_2048_amul(tws, q3, HFLEN_2048, mod->N, FFLEN_2048);
+        FF_2048_random(rv->rho1, RNG, FFLEN_2048 + HFLEN_2048);
+        FF_2048_mod(rv->rho1, tws, FFLEN_2048 + HFLEN_2048);
+    }
+    else
+    {
+        FF_2048_toOctet(&OCT, rv->beta, FFLEN_2048);
+        FF_4096_fromOctet(beta, &OCT, HFLEN_4096);
+
+        FF_2048_toOctet(&OCT, rv->gamma, FFLEN_2048);
+        FF_4096_fromOctet(gamma, &OCT, HFLEN_4096);
+    }
+
+    // Compute z = h1^x * h2^rho mod Nt
+    OCT_copy(&OCT, X);
+    OCT_pad(&OCT, HFS_2048);
+    FF_2048_zero(tws, FFLEN_2048 + HFLEN_2048);
+    FF_2048_fromOctet(tws, &OCT, HFLEN_2048);
+    FF_2048_skpow2(c->z, mod->b0, tws, mod->b1, rv->rho, mod->N, FFLEN_2048, FFLEN_2048 + HFLEN_2048);
+
+    // Compute t = h1^y * h2^sigma mod Nt
+    OCT_copy(&OCT, Y);
+    OCT_pad(&OCT, HFS_2048);
+    FF_2048_fromOctet(tws, &OCT, HFLEN_2048);
+    FF_2048_skpow2(c->t, mod->b0, tws, mod->b1, rv->sigma, mod->N, FFLEN_2048, FFLEN_2048 + HFLEN_2048);
+
+    // Compute z1 = h1^alpha * h2^rho1 mod Nt and
+    FF_2048_copy(tws, rv->alpha, HFLEN_2048);
+    FF_2048_skpow2(c->z1, mod->b0, tws, mod->b1, rv->rho1, mod->N, FFLEN_2048, FFLEN_2048 + HFLEN_2048);
+
+    // Compute w = h1^gamma * h2^tau mod Nt
+    FF_2048_copy(tws, rv->gamma, FFLEN_2048);
+    FF_2048_skpow2(c->w,  mod->b0, tws, mod->b1, rv->tau,  mod->N, FFLEN_2048, FFLEN_2048 + HFLEN_2048);
+
+    // Compute v = c1^alpha * g^gamma * beta^N mod n2
+    FF_4096_fromOctet(ws, C1, FFLEN_4096);
+
+    FF_2048_toOctet(&OCT, rv->alpha, HFLEN_2048);
+    OCT_pad(&OCT, HFS_4096);
+    FF_4096_fromOctet(alpha, &OCT, HFLEN_4096);
+
+    FF_4096_skpow3(ws, ws, alpha, key->g, gamma, beta, key->n, key->n2, FFLEN_4096, HFLEN_4096);
+
+    FF_4096_toOctet(&OCT, ws, FFLEN_4096);
+    FF_2048_fromOctet(c->v, &OCT, 2 * FFLEN_2048);
+
+    // Clean memory
+    FF_4096_zero(alpha, HFLEN_4096);
+    FF_4096_zero(beta,  FFLEN_4096);
+    FF_4096_zero(gamma, HFLEN_4096);
+}
+
+void MTA_ZK_challenge(PAILLIER_public_key *key, COMMITMENTS_BC_pub_modulus *mod, octet *C1, octet *C2, MTA_ZK_commitment *c, octet *E)
+{
+    hash256 sha;
+
+    char oct[2*FS_2048];
+    octet OCT = {0, sizeof(oct), oct};
+
+    BIG_256_56 q;
+    BIG_256_56 t;
+
+    // Load curve order
+    BIG_256_56_rcopy(q, CURVE_Order_SECP256K1);
+
+    HASH256_init(&sha);
+
+    /* Bind to public parameters */
+    hash_RP_params(&sha, key, mod, q);
+
+    /* Bind to proof input */
+    OCT_hash(&sha, C1);
+    OCT_hash(&sha, C2);
+
+    /* Bind to proof commitment */
+    hash_ZK_commitment(&sha, c);
+
+    /* Output */
+    HASH256_hash(&sha, OCT.val);
+    BIG_256_56_fromBytesLen(t, OCT.val, SHA256);
+    BIG_256_56_mod(t, q);
+
+    BIG_256_56_toBytes(E->val, t);
+    E->len = MODBYTES_256_56;
+}
+
+void MTA_ZK_prove(PAILLIER_public_key *key, MTA_ZK_commitment_rv *rv, octet *X, octet *Y, octet *R, octet *E, MTA_ZK_proof *p)
+{
+    BIG_1024_58 hws[HFLEN_2048];
+    BIG_1024_58 ws[FFLEN_2048];
+    BIG_1024_58 dws[2*FFLEN_2048];
+
+    BIG_1024_58 n[FFLEN_2048];
+    BIG_1024_58 e[HFLEN_2048];
+
+    char oct[2*FS_2048];
+    octet OCT = {0, sizeof(oct), oct};
+
+    OCT_copy(&OCT, E);
+    OCT_pad(&OCT, HFS_2048);
+    FF_2048_fromOctet(e, &OCT, HFLEN_2048);
+
+    // Compute s = beta * r^e mod N
+    OCT_copy(&OCT, R);
+    FF_2048_fromOctet(dws, &OCT, 2*FFLEN_2048);
+
+    FF_4096_toOctet(&OCT, key->n, HFLEN_4096);
+    FF_2048_fromOctet(n, &OCT, FFLEN_2048);
+
+    FF_2048_dmod(ws, dws, n, FFLEN_2048);
+    FF_2048_skpow(ws, ws, e, n, FFLEN_2048, HFLEN_2048);
+    FF_2048_mul(dws, rv->beta, ws, FFLEN_2048);
+    FF_2048_dmod(p->s, dws, n, FFLEN_2048);
+
+    // Compute s1 = e*x + alpha
+    OCT_copy(&OCT, X);
+    OCT_pad(&OCT, HFS_2048);
+    FF_2048_fromOctet(hws, &OCT, HFLEN_2048);
+
+    FF_2048_zero(p->s1, FFLEN_2048);
+
+    FF_2048_mul(ws, e, hws, HFLEN_2048);
+    FF_2048_copy(p->s1, rv->alpha, HFLEN_2048);
+    FF_2048_add(p->s1, p->s1, ws, HFLEN_2048);
+    FF_2048_norm(p->s1, HFLEN_2048);
+
+    // Compute s2 = e*rho + rho1
+    FF_2048_amul(dws, e, HFLEN_2048, rv->rho, FFLEN_2048 + HFLEN_2048);
+    FF_2048_copy(p->s2, rv->rho1, FFLEN_2048 + HFLEN_2048);
+    FF_2048_add(p->s2, p->s2, dws, FFLEN_2048 + HFLEN_2048);
+    FF_2048_norm(p->s2, FFLEN_2048 + HFLEN_2048);
+
+    // Compute t1 = e*y + gamma
+    OCT_copy(&OCT, Y);
+    OCT_pad(&OCT, HFS_2048);
+    FF_2048_fromOctet(hws, &OCT, HFLEN_2048);
+
+    FF_2048_mul(ws, e, hws, HFLEN_2048);
+    FF_2048_copy(p->t1, rv->gamma, FFLEN_2048);
+    FF_2048_add(p->t1, p->t1, ws, FFLEN_2048);
+    FF_2048_norm(p->t1, FFLEN_2048);
+
+    // Compute s2 = e*sigma + tau
+    FF_2048_amul(dws, e, HFLEN_2048, rv->sigma, FFLEN_2048 + HFLEN_2048);
+    FF_2048_copy(p->t2, rv->tau, FFLEN_2048 + HFLEN_2048);
+    FF_2048_add(p->t2, p->t2, dws, FFLEN_2048 + HFLEN_2048);
+    FF_2048_norm(p->t2, FFLEN_2048 + HFLEN_2048);
+
+    // Clean memory
+    FF_2048_zero(hws, HFLEN_2048);
+    FF_2048_zero(ws , FFLEN_2048);
+    FF_2048_zero(dws, 2 * FFLEN_2048);
+}
+
+int MTA_ZK_verify(PAILLIER_private_key *key, COMMITMENTS_BC_priv_modulus *mod, octet *C1, octet *C2, octet *E, MTA_ZK_commitment *c, MTA_ZK_proof *p)
+{
+    BIG_1024_58 e[FFLEN_2048];
+    BIG_1024_58 q[HFLEN_2048];
+    BIG_1024_58 n[FFLEN_2048];
+    BIG_1024_58 g[FFLEN_2048];
+
+    BIG_1024_58 p_proof[FFLEN_2048];
+    BIG_1024_58 q_proof[FFLEN_2048];
+    BIG_1024_58 p_gt[FFLEN_2048];
+    BIG_1024_58 q_gt[FFLEN_2048];
+
+    BIG_1024_58 c1[2 * FFLEN_2048];
+    BIG_1024_58 c2[2 * FFLEN_2048];
+
+    BIG_1024_58 ws1[FFLEN_2048];
+    BIG_1024_58 ws2[FFLEN_2048];
+
+    char oct[2*FS_2048];
+    octet OCT = {0, sizeof(oct), oct};
+
+    // Check if s1 < q^3
+    OCT_fromHex(&OCT, curve_order_hex);
+    OCT_pad(&OCT, HFS_2048);
+    FF_2048_fromOctet(q, &OCT, HFLEN_2048);
+    FF_2048_sqr(ws1, q, HFLEN_2048);
+    FF_2048_mul(ws1, ws1, q, HFLEN_2048);
+
+    if (FF_2048_comp(p->s1, ws1, HFLEN_2048) > 0)
+    {
+        return MTA_FAIL;
+    }
+
+    OCT_copy(&OCT, E);
+    OCT_pad(&OCT, FS_2048);
+    FF_2048_fromOctet(e, &OCT, FFLEN_2048);
+
+    // Split check b0^s1 * b1^s2 * z^(-e) == z1 mod PQ using CRT
+    MTA_triple_power(p_proof, mod->b0, mod->b1, p->s1, p->s2, c->z, e, mod->P, 0);
+    MTA_triple_power(q_proof, mod->b0, mod->b1, p->s1, p->s2, c->z, e, mod->Q, 0);
+
+    FF_2048_dmod(p_gt, c->z1, mod->P, HFLEN_2048);
+    FF_2048_dmod(q_gt, c->z1, mod->Q, HFLEN_2048);
+
+    if ((FF_2048_comp(p_gt, p_proof, HFLEN_2048) != 0) || (FF_2048_comp(q_gt, q_proof, HFLEN_2048) != 0))
+    {
+        return MTA_FAIL;
+    }
+
+    // Split check if b0^t1 * b1^t2 * t^(-e) == w mod PQ using CRT
+    MTA_triple_power(p_proof, mod->b0, mod->b1, p->t1, p->t2, c->t, e, mod->P, 1);
+    MTA_triple_power(q_proof, mod->b0, mod->b1, p->t1, p->t2, c->t, e, mod->Q, 1);
+
+    FF_2048_dmod(p_gt, c->w, mod->P, HFLEN_2048);
+    FF_2048_dmod(q_gt, c->w, mod->Q, HFLEN_2048);
+
+    if ((FF_2048_comp(p_gt, p_proof, HFLEN_2048) != 0) || (FF_2048_comp(q_gt, q_proof, HFLEN_2048) != 0))
+    {
+        return MTA_FAIL;
+    }
+
+    // Split check c1^s1 * s^N * g^t1 * c2^(-e) == v mod N^2 using CRT
+    FF_2048_mul(n, key->p, key->q, HFLEN_2048);
+    FF_2048_copy(g, n, FFLEN_2048);
+    FF_2048_inc(g, 1, FFLEN_2048);
+
+    FF_2048_fromOctet(c1, C1, 2 * FFLEN_2048);
+    FF_2048_fromOctet(c2, C2, 2 * FFLEN_2048);
+
+    FF_2048_dmod(ws1, c1, key->p2, FFLEN_2048);
+    FF_2048_dmod(ws2, c2, key->p2, FFLEN_2048);
+    FF_2048_invmodp(ws2, ws2, key->p2, FFLEN_2048);
+    FF_2048_pow4(p_proof, ws1, p->s1, p->s, n, g, p->t1, ws2, e, key->p2, FFLEN_2048, FFLEN_2048);
+
+    FF_2048_dmod(ws1, c1, key->q2, FFLEN_2048);
+    FF_2048_dmod(ws2, c2, key->q2, FFLEN_2048);
+    FF_2048_invmodp(ws2, ws2, key->q2, FFLEN_2048);
+    FF_2048_pow4(q_proof, ws1, p->s1, p->s, n, g, p->t1, ws2, e, key->q2, FFLEN_2048, FFLEN_2048);
+
+    FF_2048_dmod(p_gt, c->v, key->p2, FFLEN_2048);
+    FF_2048_dmod(q_gt, c->v, key->q2, FFLEN_2048);
+
+    if ((FF_2048_comp(p_gt, p_proof, FFLEN_2048) != 0) || (FF_2048_comp(q_gt, q_proof, FFLEN_2048) != 0))
+    {
+        return MTA_FAIL;
+    }
+
+    return MTA_OK;
+}
+
+void MTA_ZK_commitment_toOctets(octet *Z, octet *Z1, octet *T, octet *V, octet *W, MTA_ZK_commitment *c)
+{
+    FF_2048_toOctet(Z,  c->z, FFLEN_2048);
+    FF_2048_toOctet(Z1, c->z1,FFLEN_2048);
+    FF_2048_toOctet(T,  c->t, FFLEN_2048);
+    FF_2048_toOctet(V,  c->v, 2 * FFLEN_2048);
+    FF_2048_toOctet(W,  c->w, FFLEN_2048);
+}
+
+void MTA_ZK_commitment_fromOctets(MTA_ZK_commitment *c, octet *Z, octet *Z1, octet *T, octet *V, octet *W)
+{
+    FF_2048_fromOctet(c->z,  Z,  FFLEN_2048);
+    FF_2048_fromOctet(c->z1, Z1, FFLEN_2048);
+    FF_2048_fromOctet(c->t,  T,  FFLEN_2048);
+    FF_2048_fromOctet(c->v,  V,  2 * FFLEN_2048);
+    FF_2048_fromOctet(c->w,  W,  FFLEN_2048);
+}
+
+void MTA_ZK_proof_toOctets(octet *S, octet *S1, octet *S2, octet *T1, octet *T2, MTA_ZK_proof *p)
+{
+    FF_2048_toOctet(S,  p->s,  FFLEN_2048);
+    FF_2048_toOctet(S1, p->s1, HFLEN_2048);
+    FF_2048_toOctet(S2, p->s2, FFLEN_2048 + HFLEN_2048);
+    FF_2048_toOctet(T1, p->t1, FFLEN_2048);
+    FF_2048_toOctet(T2, p->t2, FFLEN_2048 + HFLEN_2048);
+}
+
+void MTA_ZK_proof_fromOctets(MTA_ZK_proof *p, octet *S, octet *S1, octet *S2, octet *T1, octet *T2)
+{
+    FF_2048_zero(p->s1, FFLEN_2048);
+
+    FF_2048_fromOctet(p->s,  S,  FFLEN_2048);
+    FF_2048_fromOctet(p->s1,  S1, HFLEN_2048);
+    FF_2048_fromOctet(p->s2, S2, FFLEN_2048 + HFLEN_2048);
+    FF_2048_fromOctet(p->t1, T1, FFLEN_2048);
+    FF_2048_fromOctet(p->t2, T2, FFLEN_2048 + HFLEN_2048);
+}
+
+void MTA_ZK_commitment_rv_kill(MTA_ZK_commitment_rv *rv)
+{
+    FF_2048_zero(rv->alpha, HFLEN_2048);
+    FF_2048_zero(rv->beta,  FFLEN_2048);
+    FF_2048_zero(rv->gamma, FFLEN_2048);
+    FF_2048_zero(rv->rho,   FFLEN_2048 + HFLEN_2048);
+    FF_2048_zero(rv->rho1,  FFLEN_2048 + HFLEN_2048);
+    FF_2048_zero(rv->sigma, FFLEN_2048 + HFLEN_2048);
+    FF_2048_zero(rv->tau,   FFLEN_2048 + HFLEN_2048);
+}