From 4b328c66161d11ddb240ca9c10298b0581aaa6b5 Mon Sep 17 00:00:00 2001
From: Sarang Noether <32460187+SarangNoether@users.noreply.github.com>
Date: Thu, 29 Aug 2019 11:35:12 +0000
Subject: [PATCH 1/9] CLSAG signatures

---
 src/crypto/crypto-ops.c              | 100 +++++++++++
 src/crypto/crypto-ops.h              |   2 +
 src/cryptonote_config.h              |   3 +
 src/ringct/rctOps.cpp                |  17 ++
 src/ringct/rctOps.h                  |   4 +
 src/ringct/rctSigs.cpp               | 238 +++++++++++++++++++++++++++
 src/ringct/rctSigs.h                 |   3 +
 src/ringct/rctTypes.h                |  15 ++
 tests/performance_tests/crypto_ops.h |  21 ++-
 tests/performance_tests/main.cpp     |   9 +
 tests/performance_tests/sig_clsag.h  |  83 ++++++++++
 tests/performance_tests/sig_mlsag.h  |  87 ++++++++++
 12 files changed, 578 insertions(+), 4 deletions(-)
 create mode 100644 tests/performance_tests/sig_clsag.h
 create mode 100644 tests/performance_tests/sig_mlsag.h

diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c
index 3110d3ce7..508709280 100644
--- a/src/crypto/crypto-ops.c
+++ b/src/crypto/crypto-ops.c
@@ -1234,6 +1234,56 @@ void ge_double_scalarmult_base_vartime(ge_p2 *r, const unsigned char *a, const g
   }
 }
 
+// Computes aG + bB + cC (G is the fixed basepoint)
+void ge_triple_scalarmult_base_vartime(ge_p2 *r, const unsigned char *a, const unsigned char *b, const ge_dsmp Bi, const unsigned char *c, const ge_dsmp Ci) {
+  signed char aslide[256];
+  signed char bslide[256];
+  signed char cslide[256];
+  ge_p1p1 t;
+  ge_p3 u;
+  int i;
+
+  slide(aslide, a);
+  slide(bslide, b);
+  slide(cslide, c);
+
+  ge_p2_0(r);
+
+  for (i = 255; i >= 0; --i) {
+    if (aslide[i] || bslide[i] || cslide[i]) break;
+  }
+
+  for (; i >= 0; --i) {
+    ge_p2_dbl(&t, r);
+
+    if (aslide[i] > 0) {
+      ge_p1p1_to_p3(&u, &t);
+      ge_madd(&t, &u, &ge_Bi[aslide[i]/2]);
+    } else if (aslide[i] < 0) {
+      ge_p1p1_to_p3(&u, &t);
+      ge_msub(&t, &u, &ge_Bi[(-aslide[i])/2]);
+    }
+
+    if (bslide[i] > 0) {
+      ge_p1p1_to_p3(&u, &t);
+      ge_add(&t, &u, &Bi[bslide[i]/2]);
+    } else if (bslide[i] < 0) {
+      ge_p1p1_to_p3(&u, &t);
+      ge_sub(&t, &u, &Bi[(-bslide[i])/2]);
+    }
+
+    if (cslide[i] > 0) {
+      ge_p1p1_to_p3(&u, &t);
+      ge_add(&t, &u, &Ci[cslide[i]/2]);
+    } else if (cslide[i] < 0) {
+      ge_p1p1_to_p3(&u, &t);
+      ge_sub(&t, &u, &Ci[(-cslide[i])/2]);
+    }
+
+    ge_p1p1_to_p2(r, &t);
+  }
+}
+
 void ge_double_scalarmult_base_vartime_p3(ge_p3 *r3, const unsigned char *a, const ge_p3 *A, const unsigned char *b) {
   signed char aslide[256];
   signed char bslide[256];
@@ -2148,6 +2198,56 @@ void ge_double_scalarmult_precomp_vartime2(ge_p2 *r, const unsigned char *a, con
   }
 }
 
+// Computes aA + bB + cC (all points require precomputation)
+void ge_triple_scalarmult_precomp_vartime(ge_p2 *r, const unsigned char *a, const ge_dsmp Ai, const unsigned char *b, const ge_dsmp Bi, const unsigned char *c, const ge_dsmp Ci) {
+  signed char aslide[256];
+  signed char bslide[256];
+  signed char cslide[256];
+  ge_p1p1 t;
+  ge_p3 u;
+  int i;
+
+  slide(aslide, a);
+  slide(bslide, b);
+  slide(cslide, c);
+
+  ge_p2_0(r);
+
+  for (i = 255; i >= 0; --i) {
+    if (aslide[i] || bslide[i] || cslide[i]) break;
+  }
+
+  for (; i >= 0; --i) {
+    ge_p2_dbl(&t, r);
+
+    if (aslide[i] > 0) {
+      ge_p1p1_to_p3(&u, &t);
+      ge_add(&t, &u, &Ai[aslide[i]/2]);
+    } else if (aslide[i] < 0) {
+      ge_p1p1_to_p3(&u, &t);
+      ge_sub(&t, &u, &Ai[(-aslide[i])/2]);
+    }
+
+    if (bslide[i] > 0) {
+      ge_p1p1_to_p3(&u, &t);
+      ge_add(&t, &u, &Bi[bslide[i]/2]);
+    } else if (bslide[i] < 0) {
+      ge_p1p1_to_p3(&u, &t);
+      ge_sub(&t, &u, &Bi[(-bslide[i])/2]);
+    }
+
+    if (cslide[i] > 0) {
+      ge_p1p1_to_p3(&u, &t);
+      ge_add(&t, &u, &Ci[cslide[i]/2]);
+    } else if (cslide[i] < 0) {
+      ge_p1p1_to_p3(&u, &t);
+      ge_sub(&t, &u, &Ci[(-cslide[i])/2]);
+    }
+
+    ge_p1p1_to_p2(r, &t);
+  }
+}
+
 void ge_double_scalarmult_precomp_vartime2_p3(ge_p3 *r3, const unsigned char *a, const ge_dsmp Ai, const unsigned char *b, const ge_dsmp Bi) {
   signed char aslide[256];
   signed char bslide[256];
diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h
index eeb94669b..22f76974b 100644
--- a/src/crypto/crypto-ops.h
+++ b/src/crypto/crypto-ops.h
@@ -79,6 +79,7 @@ typedef ge_cached ge_dsmp[8];
 extern const ge_precomp ge_Bi[8];
 void ge_dsm_precomp(ge_dsmp r, const ge_p3 *s);
 void ge_double_scalarmult_base_vartime(ge_p2 *, const unsigned char *, const ge_p3 *, const unsigned char *);
+void ge_triple_scalarmult_base_vartime(ge_p2 *, const unsigned char *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp);
 void ge_double_scalarmult_base_vartime_p3(ge_p3 *, const unsigned char *, const ge_p3 *, const unsigned char *);
 
 /* From ge_frombytes.c, modified */
@@ -130,6 +131,7 @@ void sc_reduce(unsigned char *);
 void ge_scalarmult(ge_p2 *, const unsigned char *, const ge_p3 *);
 void ge_scalarmult_p3(ge_p3 *, const unsigned char *, const ge_p3 *);
 void ge_double_scalarmult_precomp_vartime(ge_p2 *, const unsigned char *, const ge_p3 *, const unsigned char *, const ge_dsmp);
+void ge_triple_scalarmult_precomp_vartime(ge_p2 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp);
 void ge_double_scalarmult_precomp_vartime2(ge_p2 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp);
 void ge_double_scalarmult_precomp_vartime2_p3(ge_p3 *, const unsigned char *, const ge_dsmp, const unsigned char *, const ge_dsmp);
 void ge_mul8(ge_p1p1 *, const ge_p2 *);
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
index 8051ee9fa..7c240423a 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
@@ -226,6 +226,9 @@ namespace config
   const unsigned char HASH_KEY_MEMORY = 'k';
   const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
   const unsigned char HASH_KEY_TXPROOF_V2[] = "TXPROOF_V2";
+  const unsigned char HASH_KEY_CLSAG_ROUND[] = "CLSAG_round";
+  const unsigned char HASH_KEY_CLSAG_AGG_0[] = "CLSAG_agg_0";
+  const unsigned char HASH_KEY_CLSAG_AGG_1[] = "CLSAG_agg_1";
 
   namespace testnet
   {
diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp
index b2dd32ada..245a3f477 100644
--- a/src/ringct/rctOps.cpp
+++ b/src/ringct/rctOps.cpp
@@ -511,6 +511,23 @@ namespace rct {
         ge_tobytes(aAbB.bytes, &rv);
     }
 
+    // addKeys_aGbBcC
+    // computes aG + bB + cC
+    // G is the fixed basepoint and B,C require precomputation
+    void addKeys_aGbBcC(key &aGbBcC, const key &a, const key &b, const ge_dsmp B, const key &c, const ge_dsmp C) {
+        ge_p2 rv;
+        ge_triple_scalarmult_base_vartime(&rv, a.bytes, b.bytes, B, c.bytes, C);
+        ge_tobytes(aGbBcC.bytes, &rv);
+    }
+
+    // addKeys_aAbBcC
+    // computes aA + bB + cC
+    // A,B,C require precomputation
+    void addKeys_aAbBcC(key &aAbBcC, const key &a, const ge_dsmp A, const key &b, const ge_dsmp B, const key &c, const ge_dsmp C) {
+        ge_p2 rv;
+        ge_triple_scalarmult_precomp_vartime(&rv, a.bytes, A, b.bytes, B, c.bytes, C);
+        ge_tobytes(aAbBcC.bytes, &rv);
+    }
 
     //subtract Keys (subtracts curve points)
     //AB = A - B where A, B are curve points
diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h
index 74e0ad833..679ed1441 100644
--- a/src/ringct/rctOps.h
+++ b/src/ringct/rctOps.h
@@ -145,6 +145,10 @@ namespace rct {
     //B must be input after applying "precomp"
     void addKeys3(key &aAbB, const key &a, const key &A, const key &b, const ge_dsmp B);
     void addKeys3(key &aAbB, const key &a, const ge_dsmp A, const key &b, const ge_dsmp B);
+
+    void addKeys_aGbBcC(key &aGbBcC, const key &a, const key &b, const ge_dsmp B, const key &c, const ge_dsmp C);
+    void addKeys_aAbBcC(key &aAbBcC, const key &a, const ge_dsmp A, const key &b, const ge_dsmp B, const key &c, const ge_dsmp C);
+
     //AB = A - B where A, B are curve points
     void subKeys(key &AB, const key &A, const  key &B);
     //checks if A, B are equal as curve points
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index 2e3e7007e..cb702ed15 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -36,6 +36,7 @@
 #include "rctSigs.h"
 #include "bulletproofs.h"
 #include "cryptonote_basic/cryptonote_format_utils.h"
+#include "cryptonote_config.h"
 
 using namespace crypto;
 using namespace std;
@@ -165,6 +166,243 @@ namespace rct {
       return verifyBorromean(bb, P1_p3, P2_p3);
     }
 
+    // Generate a CLSAG signature
+    // See paper by Goodell et al. (https://eprint.iacr.org/2019/654)
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l, const multisig_kLRki *kLRki) {
+        clsag sig;
+        size_t n = P.size(); // ring size
+        CHECK_AND_ASSERT_THROW_MES(n == C.size(), "Signing and commitment key vector sizes must match!");
+        CHECK_AND_ASSERT_THROW_MES(l < n, "Signing index out of range!");
+
+        // Key images
+        ge_p3 H_p3;
+        hash_to_p3(H_p3,P[l]);
+        key H;
+        ge_p3_tobytes(H.bytes,&H_p3);
+
+        key D;
+        scalarmultKey(D,H,z);
+
+        // Multisig
+        if (kLRki)
+        {
+            sig.I = kLRki->ki;
+        }
+        else
+        {
+            scalarmultKey(sig.I,H,p);
+        }
+
+        geDsmp I_precomp;
+        geDsmp D_precomp;
+        precomp(I_precomp.k,sig.I);
+        precomp(D_precomp.k,D);
+
+        // Offset key image
+        scalarmultKey(sig.D,D,INV_EIGHT);
+
+        // Initial values
+        key a;
+        key aG;
+        key aH;
+        skpkGen(a,aG);
+        scalarmultKey(aH,H,a);
+
+        // Aggregation hashes
+        keyV mu_P_to_hash(2*n+3); // domain, I, D, P, C
+        keyV mu_C_to_hash(2*n+3); // domain, I, D, P, C
+        sc_0(mu_P_to_hash[0].bytes);
+        memcpy(mu_P_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_0,sizeof(config::HASH_KEY_CLSAG_AGG_0)-1);
+        sc_0(mu_C_to_hash[0].bytes);
+        memcpy(mu_C_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_1,sizeof(config::HASH_KEY_CLSAG_AGG_1)-1);
+        for (size_t i = 1; i < n+1; ++i) {
+            mu_P_to_hash[i] = P[i-1];
+            mu_C_to_hash[i] = P[i-1];
+        }
+        for (size_t i = n+1; i < 2*n+1; ++i) {
+            mu_P_to_hash[i] = C[i-n-1];
+            mu_C_to_hash[i] = C[i-n-1];
+        }
+        mu_P_to_hash[2*n+1] = sig.I;
+        mu_P_to_hash[2*n+2] = sig.D;
+        mu_C_to_hash[2*n+1] = sig.I;
+        mu_C_to_hash[2*n+2] = sig.D;
+        key mu_P, mu_C;
+        mu_P = hash_to_scalar(mu_P_to_hash);
+        mu_C = hash_to_scalar(mu_C_to_hash);
+
+        // Initial commitment
+        keyV c_to_hash(2*n+4); // domain, P, C, message, aG, aH
+        key c;
+        sc_0(c_to_hash[0].bytes);
+        memcpy(c_to_hash[0].bytes,config::HASH_KEY_CLSAG_ROUND,sizeof(config::HASH_KEY_CLSAG_ROUND)-1);
+        for (size_t i = 1; i < n+1; ++i)
+        {
+            c_to_hash[i] = P[i-1];
+            c_to_hash[i+n] = C[i-1];
+        }
+        c_to_hash[2*n+1] = message;
+
+        // Multisig data is present
+        if (kLRki)
+        {
+            a = kLRki->k;
+            c_to_hash[2*n+2] = kLRki->L;
+            c_to_hash[2*n+3] = kLRki->R;
+        }
+        else
+        {
+            c_to_hash[2*n+2] = aG;
+            c_to_hash[2*n+3] = aH;
+        }
+        c = hash_to_scalar(c_to_hash);
+        
+        size_t i;
+        i = (l + 1) % n;
+        if (i == 0)
+            copy(sig.c1, c);
+
+        // Decoy indices
+        sig.s = keyV(n);
+        key c_new;
+        key L;
+        key R;
+        key c_p; // = c[i]*mu_P
+        key c_c; // = c[i]*mu_C
+        geDsmp P_precomp;
+        geDsmp C_precomp;
+        geDsmp H_precomp;
+        ge_p3 Hi_p3;
+
+        while (i != l) {
+            sig.s[i] = skGen();
+            sc_0(c_new.bytes);
+            sc_mul(c_p.bytes,mu_P.bytes,c.bytes);
+            sc_mul(c_c.bytes,mu_C.bytes,c.bytes);
+
+            // Precompute points
+            precomp(P_precomp.k,P[i]);
+            precomp(C_precomp.k,C[i]);
+
+            // Compute L
+            addKeys_aGbBcC(L,sig.s[i],c_p,P_precomp.k,c_c,C_precomp.k);
+
+            // Compute R
+            hash_to_p3(Hi_p3,P[i]);
+            ge_dsm_precomp(H_precomp.k, &Hi_p3);
+            addKeys_aAbBcC(R,sig.s[i],H_precomp.k,c_p,I_precomp.k,c_c,D_precomp.k);
+
+            c_to_hash[2*n+2] = L;
+            c_to_hash[2*n+3] = R;
+            c_new = hash_to_scalar(c_to_hash);
+            copy(c,c_new);
+            
+            i = (i + 1) % n;
+            if (i == 0)
+                copy(sig.c1,c);
+        }
+
+        // Compute final scalar
+        key s0_p_mu_P;
+        sc_mul(s0_p_mu_P.bytes,mu_P.bytes,p.bytes);
+        key s0_add_z_mu_C;
+        sc_muladd(s0_add_z_mu_C.bytes,mu_C.bytes,z.bytes,s0_p_mu_P.bytes);
+        sc_mulsub(sig.s[l].bytes,c.bytes,s0_add_z_mu_C.bytes,a.bytes);
+
+        return sig;
+    }
+
+    // Verify a CLSAG signature
+    // See paper by Goodell et al. (https://eprint.iacr.org/2019/654)
+    bool CLSAG_Ver(const key &message, const keyV & P, const keyV & C, const clsag & sig)
+    {
+        size_t n = P.size(); // ring size
+        CHECK_AND_ASSERT_MES(n == C.size(), false, "Signing and commitment key vector sizes must match!");
+        CHECK_AND_ASSERT_MES(n == sig.s.size(), false, "Signature scalar vector is the wrong size!");
+        for (size_t i = 0; i < n; ++i)
+            CHECK_AND_ASSERT_MES(sc_check(sig.s[i].bytes) == 0, false, "Bad signature scalar!");
+        CHECK_AND_ASSERT_MES(sc_check(sig.c1.bytes) == 0, false, "Bad signature commitment!");
+
+        key c = copy(sig.c1);
+        key D_8 = scalarmult8(sig.D);
+        geDsmp I_precomp;
+        geDsmp D_precomp;
+        precomp(I_precomp.k,sig.I);
+        precomp(D_precomp.k,D_8);
+
+        // Aggregation hashes
+        keyV mu_P_to_hash(2*n+3); // domain, I, D, P, C
+        keyV mu_C_to_hash(2*n+3); // domain, I, D, P, C
+        sc_0(mu_P_to_hash[0].bytes);
+        memcpy(mu_P_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_0,sizeof(config::HASH_KEY_CLSAG_AGG_0)-1);
+        sc_0(mu_C_to_hash[0].bytes);
+        memcpy(mu_C_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_1,sizeof(config::HASH_KEY_CLSAG_AGG_1)-1);
+        for (size_t i = 1; i < n+1; ++i) {
+            mu_P_to_hash[i] = P[i-1];
+            mu_C_to_hash[i] = P[i-1];
+        }
+        for (size_t i = n+1; i < 2*n+1; ++i) {
+            mu_P_to_hash[i] = C[i-n-1];
+            mu_C_to_hash[i] = C[i-n-1];
+        }
+        mu_P_to_hash[2*n+1] = sig.I;
+        mu_P_to_hash[2*n+2] = sig.D;
+        mu_C_to_hash[2*n+1] = sig.I;
+        mu_C_to_hash[2*n+2] = sig.D;
+        key mu_P, mu_C;
+        mu_P = hash_to_scalar(mu_P_to_hash);
+        mu_C = hash_to_scalar(mu_C_to_hash);
+
+        keyV c_to_hash(2*n+4); // domain, P, C, message, L, R
+        sc_0(c_to_hash[0].bytes);
+        memcpy(c_to_hash[0].bytes,config::HASH_KEY_CLSAG_ROUND,sizeof(config::HASH_KEY_CLSAG_ROUND)-1);
+        for (size_t i = 1; i < n+1; ++i)
+        {
+            c_to_hash[i] = P[i-1];
+            c_to_hash[i+n] = C[i-1];
+        }
+        c_to_hash[2*n+1] = message;
+        key c_p; // = c[i]*mu_P
+        key c_c; // = c[i]*mu_C
+        key c_new;
+        key L;
+        key R;
+        geDsmp P_precomp;
+        geDsmp C_precomp;
+        geDsmp H_precomp;
+        size_t i = 0;
+        ge_p3 hash8_p3;
+        geDsmp hash_precomp;
+
+        while (i < n) {
+            sc_0(c_new.bytes);
+            sc_mul(c_p.bytes,mu_P.bytes,c.bytes);
+            sc_mul(c_c.bytes,mu_C.bytes,c.bytes);
+
+            // Precompute points
+            precomp(P_precomp.k,P[i]);
+            precomp(C_precomp.k,C[i]);
+
+            // Compute L
+            addKeys_aGbBcC(L,sig.s[i],c_p,P_precomp.k,c_c,C_precomp.k);
+
+            // Compute R
+            hash_to_p3(hash8_p3,P[i]);
+            ge_dsm_precomp(hash_precomp.k, &hash8_p3);
+            addKeys_aAbBcC(R,sig.s[i],hash_precomp.k,c_p,I_precomp.k,c_c,D_precomp.k);
+
+            c_to_hash[2*n+2] = L;
+            c_to_hash[2*n+3] = R;
+            c_new = hash_to_scalar(c_to_hash);
+            CHECK_AND_ASSERT_MES(!(c_new == rct::zero()), false, "Bad signature hash");
+            copy(c,c_new);
+
+            i = i + 1;
+        }
+        sc_sub(c_new.bytes,c.bytes,sig.c1.bytes);
+        return sc_isnonzero(c_new.bytes) == 0;
+    }
+
     // MLSAG signatures
     // See paper by Noether (https://eprint.iacr.org/2015/1098)
     // This generalization allows for some dimensions not to require linkability;
diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h
index 9227eab1e..87d2b994b 100644
--- a/src/ringct/rctSigs.h
+++ b/src/ringct/rctSigs.h
@@ -76,6 +76,9 @@ namespace rct {
     // Ver verifies that the MG sig was created correctly
     mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev);
     bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows);
+
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l, const multisig_kLRki *kLRki);
+    bool CLSAG_Ver(const key &message, const keyV & P, const keyV & C, const clsag & sig);
     //mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index);
 
     //proveRange and verRange
diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h
index ce11981ad..b83c267d0 100644
--- a/src/ringct/rctTypes.h
+++ b/src/ringct/rctTypes.h
@@ -163,6 +163,21 @@ namespace rct {
             // FIELD(II) - not serialized, it can be reconstructed
         END_SERIALIZE()
     };
+
+    // CLSAG signature
+    struct clsag {
+        keyV s; // scalars
+        key c1;
+
+        key I; // signing key image
+        key D; // commitment key image
+
+        BEGIN_SERIALIZE_OBJECT()
+            FIELD(s)
+            FIELD(c1)
+        END_SERIALIZE()
+    };
+
     //contains the data for an Borromean sig
     // also contains the "Ci" values such that
     // \sum Ci = C
diff --git a/tests/performance_tests/crypto_ops.h b/tests/performance_tests/crypto_ops.h
index ae00bb517..9db2e413a 100644
--- a/tests/performance_tests/crypto_ops.h
+++ b/tests/performance_tests/crypto_ops.h
@@ -51,11 +51,15 @@ enum test_op
   op_scalarmult8_p3,
   op_ge_dsm_precomp,
   op_ge_double_scalarmult_base_vartime,
+  op_ge_triple_scalarmult_base_vartime,
   op_ge_double_scalarmult_precomp_vartime,
+  op_ge_triple_scalarmult_precomp_vartime,
   op_ge_double_scalarmult_precomp_vartime2,
   op_addKeys2,
   op_addKeys3,
   op_addKeys3_2,
+  op_addKeys_aGbBcC,
+  op_addKeys_aAbBcC,
   op_isInMainSubgroup,
   op_zeroCommitUncached,
 };
@@ -70,15 +74,20 @@ public:
   {
     scalar0 = rct::skGen();
     scalar1 = rct::skGen();
+    scalar2 = rct::skGen();
     point0 = rct::scalarmultBase(rct::skGen());
     point1 = rct::scalarmultBase(rct::skGen());
+    point2 = rct::scalarmultBase(rct::skGen());
     if (ge_frombytes_vartime(&p3_0, point0.bytes) != 0)
       return false;
     if (ge_frombytes_vartime(&p3_1, point1.bytes) != 0)
       return false;
+    if (ge_frombytes_vartime(&p3_2, point2.bytes) != 0)
+      return false;
     ge_p3_to_cached(&cached, &p3_0);
     rct::precomp(precomp0, point0);
     rct::precomp(precomp1, point1);
+    rct::precomp(precomp2, point2);
     return true;
   }
 
@@ -109,11 +118,15 @@ public:
       case op_scalarmult8_p3: rct::scalarmult8(p3_0,point0); break;
       case op_ge_dsm_precomp: ge_dsm_precomp(dsmp, &p3_0); break;
       case op_ge_double_scalarmult_base_vartime: ge_double_scalarmult_base_vartime(&tmp_p2, scalar0.bytes, &p3_0, scalar1.bytes); break;
+      case op_ge_triple_scalarmult_base_vartime: ge_triple_scalarmult_base_vartime(&tmp_p2, scalar0.bytes, scalar1.bytes, precomp1, scalar2.bytes, precomp2); break;
       case op_ge_double_scalarmult_precomp_vartime: ge_double_scalarmult_precomp_vartime(&tmp_p2, scalar0.bytes, &p3_0, scalar1.bytes, precomp0); break;
+      case op_ge_triple_scalarmult_precomp_vartime: ge_triple_scalarmult_precomp_vartime(&tmp_p2, scalar0.bytes, precomp0, scalar1.bytes, precomp1, scalar2.bytes, precomp2); break;
       case op_ge_double_scalarmult_precomp_vartime2: ge_double_scalarmult_precomp_vartime2(&tmp_p2, scalar0.bytes, precomp0, scalar1.bytes, precomp1); break;
       case op_addKeys2: rct::addKeys2(key, scalar0, scalar1, point0); break;
       case op_addKeys3: rct::addKeys3(key, scalar0, point0, scalar1, precomp1); break;
       case op_addKeys3_2: rct::addKeys3(key, scalar0, precomp0, scalar1, precomp1); break;
+      case op_addKeys_aGbBcC: rct::addKeys_aGbBcC(key, scalar0, scalar1, precomp1, scalar2, precomp2); break;
+      case op_addKeys_aAbBcC: rct::addKeys_aAbBcC(key, scalar0, precomp0, scalar1, precomp1, scalar2, precomp2); break;
       case op_isInMainSubgroup: rct::isInMainSubgroup(point0); break;
       case op_zeroCommitUncached: rct::zeroCommit(9001); break;
       case op_zeroCommitCached: rct::zeroCommit(9000); break;
@@ -123,9 +136,9 @@ public:
   }
 
 private:
-  rct::key scalar0, scalar1;
-  rct::key point0, point1;
-  ge_p3 p3_0, p3_1;
+  rct::key scalar0, scalar1, scalar2;
+  rct::key point0, point1, point2;
+  ge_p3 p3_0, p3_1, p3_2;
   ge_cached cached;
-  ge_dsmp precomp0, precomp1;
+  ge_dsmp precomp0, precomp1, precomp2;
 };
diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp
index ca0528e16..b0cec464c 100644
--- a/tests/performance_tests/main.cpp
+++ b/tests/performance_tests/main.cpp
@@ -60,6 +60,8 @@
 #include "bulletproof.h"
 #include "crypto_ops.h"
 #include "multiexp.h"
+#include "sig_mlsag.h"
+#include "sig_clsag.h"
 
 namespace po = boost::program_options;
 
@@ -213,6 +215,9 @@ int main(int argc, char** argv)
   TEST_PERFORMANCE1(filter, p, test_cn_fast_hash, 32);
   TEST_PERFORMANCE1(filter, p, test_cn_fast_hash, 16384);
 
+  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 11, true); // MLSAG verification
+  TEST_PERFORMANCE3(filter, p, test_sig_clsag, 11, true, 0); // CLSAG verification
+
   TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, false);
   TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, true);
 
@@ -257,11 +262,15 @@ int main(int argc, char** argv)
   TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_scalarmult8_p3);
   TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_dsm_precomp);
   TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_double_scalarmult_base_vartime);
+  TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_triple_scalarmult_base_vartime);
   TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_double_scalarmult_precomp_vartime);
+  TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_triple_scalarmult_precomp_vartime);
   TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_ge_double_scalarmult_precomp_vartime2);
   TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys2);
   TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys3);
   TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys3_2);
+  TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys_aGbBcC);
+  TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_addKeys_aAbBcC);
   TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_isInMainSubgroup);
   TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_zeroCommitUncached);
   TEST_PERFORMANCE1(filter, p, test_crypto_ops, op_zeroCommitCached);
diff --git a/tests/performance_tests/sig_clsag.h b/tests/performance_tests/sig_clsag.h
new file mode 100644
index 000000000..0873fa520
--- /dev/null
+++ b/tests/performance_tests/sig_clsag.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2014-2019, The Monero Project
+// 
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+// 
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+//    conditions and the following disclaimer.
+// 
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+//    of conditions and the following disclaimer in the documentation and/or other
+//    materials provided with the distribution.
+// 
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+//    used to endorse or promote products derived from this software without specific
+//    prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// 
+// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+
+#pragma once
+
+#include "ringct/rctSigs.h"
+#include "cryptonote_basic/cryptonote_basic.h"
+
+#include "single_tx_test_base.h"
+
+template<size_t ring_size, bool ver, size_t index>
+class test_sig_clsag : public single_tx_test_base
+{
+public:
+  static const size_t n = ring_size;
+  static const size_t loop_count = 1000;
+  static const size_t l = index;
+
+  bool init()
+  {
+    if (!single_tx_test_base::init())
+      return false;
+
+    p = rct::skGen();
+    z = rct::skGen();
+    P = rct::skvGen(n);
+    C = rct::skvGen(n);
+    for (size_t i = 0 ; i < n; i++)
+    {
+        P[i] = rct::scalarmultBase(P[i]);
+        C[i] = rct::scalarmultBase(C[i]);
+    }
+    P[l] = rct::scalarmultBase(p);
+    C[l] = rct::scalarmultBase(z);
+    
+    sig = CLSAG_Gen(rct::identity(),P,p,C,z,l,NULL);
+
+    return true;
+  }
+
+  bool test()
+  {
+    if (ver)
+      return CLSAG_Ver(rct::identity(),P,C,sig);
+    else
+      CLSAG_Gen(rct::identity(),P,p,C,z,l,NULL);
+    return true;
+  }
+
+private:
+  rct::key p;
+  rct::key z;
+  rct::keyV P;
+  rct::keyV C;
+  rct::clsag sig;
+};
diff --git a/tests/performance_tests/sig_mlsag.h b/tests/performance_tests/sig_mlsag.h
new file mode 100644
index 000000000..fc987fcf5
--- /dev/null
+++ b/tests/performance_tests/sig_mlsag.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2014-2019, The Monero Project
+// 
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+// 
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+//    conditions and the following disclaimer.
+// 
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+//    of conditions and the following disclaimer in the documentation and/or other
+//    materials provided with the distribution.
+// 
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+//    used to endorse or promote products derived from this software without specific
+//    prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// 
+// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+
+#pragma once
+
+#include "ringct/rctSigs.h"
+#include "cryptonote_basic/cryptonote_basic.h"
+
+#include "single_tx_test_base.h"
+
+template<size_t ring_size, bool ver>
+class test_sig_mlsag : public single_tx_test_base
+{
+public:
+  static const size_t cols = ring_size;
+  static const size_t rows = 2; // 1 spend + 1 commitment
+  static const size_t loop_count = 1000;
+
+  bool init()
+  {
+    if (!single_tx_test_base::init())
+      return false;
+
+    rct::keyV xtmp = rct::skvGen(rows);
+    rct::keyM xm = rct::keyMInit(rows, cols);// = [[None]*N] #just used to generate test public keys
+    sk = rct::skvGen(rows);
+    P  = rct::keyMInit(rows, cols);// = keyM[[None]*N] #stores the public keys;
+    ind = 0; // fixed spend index
+    for (size_t j = 0 ; j < rows ; j++)
+    {
+        for (size_t i = 0 ; i < cols ; i++)
+        {
+            xm[i][j] = rct::skGen();
+            P[i][j] = rct::scalarmultBase(xm[i][j]);
+        }
+    }
+    for (size_t j = 0 ; j < rows ; j++)
+    {
+        sk[j] = xm[ind][j];
+    }
+    IIccss = MLSAG_Gen(rct::identity(), P, sk, NULL, NULL, ind, rows-1, hw::get_device("default"));
+
+    return true;
+  }
+
+  bool test()
+  {
+    if (ver)
+      return MLSAG_Ver(rct::identity(), P, IIccss, rows-1);
+    else
+      MLSAG_Gen(rct::identity(), P, sk, NULL, NULL, ind, rows-1, hw::get_device("default"));
+    return true;
+  }
+
+private:
+  rct::keyV sk;
+  rct::keyM P;
+  size_t ind;
+  rct::mgSig IIccss;
+};

From 8cd1d6df8f332f82fc782ad667a35e32d0d1c029 Mon Sep 17 00:00:00 2001
From: moneromooo-monero <moneromooo-monero@users.noreply.github.com>
Date: Thu, 13 Jun 2019 20:01:10 +0000
Subject: [PATCH 2/9] unit_tests: add ge_triple_scalarmult_base_vartime test

---
 tests/unit_tests/multiexp.cpp | 62 +++++++++++++++++++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/tests/unit_tests/multiexp.cpp b/tests/unit_tests/multiexp.cpp
index f12dd6b49..722c568da 100644
--- a/tests/unit_tests/multiexp.cpp
+++ b/tests/unit_tests/multiexp.cpp
@@ -252,3 +252,65 @@ TEST(multiexp, pippenger_cached)
     ASSERT_TRUE(basic(data) == pippenger(data, cache));
   }
 }
+
+TEST(multiexp, scalarmult_triple)
+{
+  std::vector<rct::MultiexpData> data;
+  ge_p2 p2;
+  rct::key res;
+  ge_p3 Gp3;
+
+  ge_frombytes_vartime(&Gp3, rct::G.bytes);
+
+  static const rct::key scalars[] = {
+    rct::Z,
+    rct::I,
+    rct::L,
+    rct::EIGHT,
+    rct::INV_EIGHT,
+  };
+  static const ge_p3 points[] = {
+    ge_p3_identity,
+    ge_p3_H,
+    Gp3,
+  };
+  ge_dsmp ppre[sizeof(points) / sizeof(points[0])];
+
+  for (size_t i = 0; i < sizeof(points) / sizeof(points[0]); ++i)
+    ge_dsm_precomp(ppre[i], &points[i]);
+
+  data.resize(3);
+  for (const rct::key &x: scalars)
+  {
+    data[0].scalar = x;
+    for (const rct::key &y: scalars)
+    {
+      data[1].scalar = y;
+      for (const rct::key &z: scalars)
+      {
+        data[2].scalar = z;
+        for (size_t i = 0; i < sizeof(points) / sizeof(points[0]); ++i)
+        {
+          data[1].point = points[i];
+          for (size_t j = 0; j < sizeof(points) / sizeof(points[0]); ++j)
+          {
+            data[0].point = Gp3;
+            data[2].point = points[j];
+
+            ge_triple_scalarmult_base_vartime(&p2, data[0].scalar.bytes, data[1].scalar.bytes, ppre[i], data[2].scalar.bytes, ppre[j]);
+            ge_tobytes(res.bytes, &p2);
+            ASSERT_TRUE(basic(data) == res);
+
+            for (size_t k = 0; k < sizeof(points) / sizeof(points[0]); ++k)
+            {
+              data[0].point = points[k];
+              ge_triple_scalarmult_precomp_vartime(&p2, data[0].scalar.bytes, ppre[k], data[1].scalar.bytes, ppre[i], data[2].scalar.bytes, ppre[j]);
+              ge_tobytes(res.bytes, &p2);
+              ASSERT_TRUE(basic(data) == res);
+            }
+          }
+        }
+      }
+    }
+  }
+}

From 82ee01699c2b910e44fd7362bd47d3a1cc9c26af Mon Sep 17 00:00:00 2001
From: moneromooo-monero <moneromooo-monero@users.noreply.github.com>
Date: Sun, 9 Jun 2019 13:02:16 +0000
Subject: [PATCH 3/9] Integrate CLSAGs into monero

They are allowed from v12, and MLSAGs are rejected from v13.
---
 .../cryptonote_boost_serialization.h          |  27 ++-
 .../cryptonote_format_utils.cpp               |   9 +-
 src/cryptonote_config.h                       |   1 +
 src/cryptonote_core/blockchain.cpp            |  56 ++++-
 src/cryptonote_core/cryptonote_core.cpp       |   3 +-
 src/device/device_ledger.cpp                  |   6 +-
 src/device_trezor/trezor/protocol.hpp         |   2 +-
 src/hardforks/hardforks.cpp                   |   5 +
 src/ringct/rctSigs.cpp                        | 184 ++++++++++++--
 src/ringct/rctSigs.h                          |   4 +-
 src/ringct/rctTypes.cpp                       |   2 +
 src/ringct/rctTypes.h                         | 139 +++++++----
 src/rpc/core_rpc_server.cpp                   |   2 +-
 src/wallet/api/wallet.cpp                     |   1 +
 src/wallet/wallet2.cpp                        |  94 +++++---
 src/wallet/wallet2.h                          |   2 +-
 tests/core_tests/CMakeLists.txt               |   2 +
 tests/core_tests/bulletproofs.cpp             |  68 ++++--
 tests/core_tests/bulletproofs.h               |  56 +++--
 tests/core_tests/chaingen_main.cpp            |   6 +-
 tests/core_tests/chaingen_tests_list.h        |   1 +
 tests/core_tests/multisig.cpp                 |  68 +++---
 tests/core_tests/multisig.h                   |   2 +-
 tests/core_tests/rct.cpp                      |   2 +-
 tests/core_tests/rct2.cpp                     | 224 ++++++++++++++++++
 tests/core_tests/rct2.h                       | 116 +++++++++
 tests/functional_tests/transfer.py            |   4 +-
 tests/performance_tests/sig_clsag.h           |   4 +-
 tests/trezor/trezor_tests.cpp                 |   2 +-
 tests/unit_tests/ringct.cpp                   | 164 +++++++++++++
 tests/unit_tests/serialization.cpp            |  22 +-
 31 files changed, 1083 insertions(+), 195 deletions(-)
 create mode 100644 tests/core_tests/rct2.cpp
 create mode 100644 tests/core_tests/rct2.h

diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h
index b3d39a616..c6b81b094 100644
--- a/src/cryptonote_basic/cryptonote_boost_serialization.h
+++ b/src/cryptonote_basic/cryptonote_boost_serialization.h
@@ -45,7 +45,6 @@
 #include "ringct/rctTypes.h"
 #include "ringct/rctOps.h"
 
-//namespace cryptonote {
 namespace boost
 {
   namespace serialization
@@ -244,6 +243,15 @@ namespace boost
     // a & x.II; // not serialized, we can recover it from the tx vin
   }
 
+  template <class Archive>
+  inline void serialize(Archive &a, rct::clsag &x, const boost::serialization::version_type ver)
+  {
+    a & x.s;
+    a & x.c1;
+    // a & x.I; // not serialized, we can recover it from the tx vin
+    a & x.D;
+  }
+
   template <class Archive>
   inline void serialize(Archive &a, rct::ecdhTuple &x, const boost::serialization::version_type ver)
   {
@@ -264,6 +272,9 @@ namespace boost
   inline void serialize(Archive &a, rct::multisig_out &x, const boost::serialization::version_type ver)
   {
     a & x.c;
+    if (ver < 1)
+      return;
+    a & x.mu_p;
   }
 
   template <class Archive>
@@ -294,7 +305,7 @@ namespace boost
     a & x.type;
     if (x.type == rct::RCTTypeNull)
       return;
-    if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2)
+    if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG)
       throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type");
     // a & x.message; message is not serialized, as it can be reconstructed from the tx data
     // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets
@@ -312,6 +323,8 @@ namespace boost
     if (x.rangeSigs.empty())
       a & x.bulletproofs;
     a & x.MGs;
+    if (ver >= 1u)
+      a & x.CLSAGs;
     if (x.rangeSigs.empty())
       a & x.pseudoOuts;
   }
@@ -322,7 +335,7 @@ namespace boost
     a & x.type;
     if (x.type == rct::RCTTypeNull)
       return;
-    if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2)
+    if (x.type != rct::RCTTypeFull && x.type != rct::RCTTypeSimple && x.type != rct::RCTTypeBulletproof && x.type != rct::RCTTypeBulletproof2 && x.type != rct::RCTTypeCLSAG)
       throw boost::archive::archive_exception(boost::archive::archive_exception::other_exception, "Unsupported rct type");
     // a & x.message; message is not serialized, as it can be reconstructed from the tx data
     // a & x.mixRing; mixRing is not serialized, as it can be reconstructed from the offsets
@@ -336,7 +349,9 @@ namespace boost
     if (x.p.rangeSigs.empty())
       a & x.p.bulletproofs;
     a & x.p.MGs;
-    if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2)
+    if (ver >= 1u)
+      a & x.p.CLSAGs;
+    if (x.type == rct::RCTTypeBulletproof || x.type == rct::RCTTypeBulletproof2 || x.type == rct::RCTTypeCLSAG)
       a & x.p.pseudoOuts;
   }
 
@@ -377,4 +392,6 @@ namespace boost
 }
 }
 
-//}
+BOOST_CLASS_VERSION(rct::rctSigPrunable, 1)
+BOOST_CLASS_VERSION(rct::rctSig, 1)
+BOOST_CLASS_VERSION(rct::multisig_out, 1)
diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp
index d808a9c1d..fcc96883b 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.cpp
+++ b/src/cryptonote_basic/cryptonote_format_utils.cpp
@@ -436,7 +436,7 @@ namespace cryptonote
   {
     CHECK_AND_ASSERT_MES(tx.pruned, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support non pruned txes");
     CHECK_AND_ASSERT_MES(tx.version >= 2, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support v1 txes");
-    CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTTypeBulletproof2,
+    CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG,
         std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support older range proof types");
     CHECK_AND_ASSERT_MES(!tx.vin.empty(), std::numeric_limits<uint64_t>::max(), "empty vin");
     CHECK_AND_ASSERT_MES(tx.vin[0].type() == typeid(cryptonote::txin_to_key), std::numeric_limits<uint64_t>::max(), "empty vin");
@@ -458,9 +458,12 @@ namespace cryptonote
     extra = 32 * (9 + 2 * nrl) + 2;
     weight += extra;
 
-    // calculate deterministic MLSAG data size
+    // calculate deterministic CLSAG/MLSAG data size
     const size_t ring_size = boost::get<cryptonote::txin_to_key>(tx.vin[0]).key_offsets.size();
-    extra = tx.vin.size() * (ring_size * (1 + 1) * 32 + 32 /* cc */);
+    if (tx.rct_signatures.type == rct::RCTTypeCLSAG)
+      extra = tx.vin.size() * (ring_size + 2) * 32;
+    else
+      extra = tx.vin.size() * (ring_size * (1 + 1) * 32 + 32 /* cc */);
     weight += extra;
 
     // calculate deterministic pseudoOuts size
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
index 7c240423a..f4709dc01 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
@@ -179,6 +179,7 @@
 #define HF_VERSION_ENFORCE_MIN_AGE              12
 #define HF_VERSION_EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY 12
 #define HF_VERSION_EXACT_COINBASE               13
+#define HF_VERSION_CLSAG                        13
 
 #define PER_KB_FEE_QUANTIZATION_DECIMALS        8
 
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 20dc7f9fb..9d4c5a66c 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -3015,6 +3015,30 @@ bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context
     }
   }
 
+  // from v13, allow CLSAGs
+  if (hf_version < HF_VERSION_CLSAG) {
+    if (tx.version >= 2) {
+      if (tx.rct_signatures.type == rct::RCTTypeCLSAG)
+      {
+        MERROR_VER("Ringct type " << (unsigned)rct::RCTTypeCLSAG << " is not allowed before v" << HF_VERSION_CLSAG);
+        tvc.m_invalid_output = true;
+        return false;
+      }
+    }
+  }
+
+  // from v14, allow only CLSAGs
+  if (hf_version > HF_VERSION_CLSAG) {
+    if (tx.version >= 2) {
+      if (tx.rct_signatures.type <= rct::RCTTypeBulletproof2)
+      {
+        MERROR_VER("Ringct type " << (unsigned)tx.rct_signatures.type << " is not allowed from v" << (HF_VERSION_CLSAG + 1));
+        tvc.m_invalid_output = true;
+        return false;
+      }
+    }
+  }
+
   return true;
 }
 //------------------------------------------------------------------
@@ -3055,7 +3079,7 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr
       }
     }
   }
-  else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2)
+  else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG)
   {
     CHECK_AND_ASSERT_MES(!pubkeys.empty() && !pubkeys[0].empty(), false, "empty pubkeys");
     rv.mixRing.resize(pubkeys.size());
@@ -3068,6 +3092,14 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr
       }
     }
   }
+  else if (rv.type == rct::RCTTypeCLSAG)
+  {
+    CHECK_AND_ASSERT_MES(rv.p.CLSAGs.size() == tx.vin.size(), false, "Bad CLSAGs size");
+    for (size_t n = 0; n < tx.vin.size(); ++n)
+    {
+      rv.p.CLSAGs[n].I = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image);
+    }
+  }
   else
   {
     CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type));
@@ -3096,6 +3128,17 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr
       }
     }
   }
+  else if (rv.type == rct::RCTTypeCLSAG)
+  {
+    if (!tx.pruned)
+    {
+      CHECK_AND_ASSERT_MES(rv.p.CLSAGs.size() == tx.vin.size(), false, "Bad CLSAGs size");
+      for (size_t n = 0; n < tx.vin.size(); ++n)
+      {
+        rv.p.CLSAGs[n].I = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image);
+      }
+    }
+  }
   else
   {
     CHECK_AND_ASSERT_MES(false, false, "Unsupported rct tx type: " + boost::lexical_cast<std::string>(rv.type));
@@ -3377,6 +3420,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
     case rct::RCTTypeSimple:
     case rct::RCTTypeBulletproof:
     case rct::RCTTypeBulletproof2:
+    case rct::RCTTypeCLSAG:
     {
       // check all this, either reconstructed (so should really pass), or not
       {
@@ -3412,14 +3456,20 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc,
         }
       }
 
-      if (rv.p.MGs.size() != tx.vin.size())
+      const size_t n_sigs = rv.type == rct::RCTTypeCLSAG ? rv.p.CLSAGs.size() : rv.p.MGs.size();
+      if (n_sigs != tx.vin.size())
       {
         MERROR_VER("Failed to check ringct signatures: mismatched MGs/vin sizes");
         return false;
       }
       for (size_t n = 0; n < tx.vin.size(); ++n)
       {
-        if (rv.p.MGs[n].II.empty() || memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32))
+        bool error;
+        if (rv.type == rct::RCTTypeCLSAG)
+          error = memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.CLSAGs[n].I, 32);
+        else
+          error = rv.p.MGs[n].II.empty() || memcmp(&boost::get<txin_to_key>(tx.vin[n]).k_image, &rv.p.MGs[n].II[0], 32);
+        if (error)
         {
           MERROR_VER("Failed to check ringct signatures: mismatched key image");
           return false;
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 9a1439c4a..474362ed0 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -928,6 +928,7 @@ namespace cryptonote
           break;
         case rct::RCTTypeBulletproof:
         case rct::RCTTypeBulletproof2:
+        case rct::RCTTypeCLSAG:
           if (!is_canonical_bulletproof_layout(rv.p.bulletproofs))
           {
             MERROR_VER("Bulletproof does not have canonical form");
@@ -955,7 +956,7 @@ namespace cryptonote
       {
         if (!tx_info[n].result)
           continue;
-        if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2)
+        if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAG)
           continue;
         if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx->rct_signatures))
         {
diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp
index 30964848d..c59b648bd 100644
--- a/src/device/device_ledger.cpp
+++ b/src/device/device_ledger.cpp
@@ -1857,7 +1857,7 @@ namespace hw {
 
         // ======  Aout, Bout, AKout, C, v, k ======
         kv_offset = data_offset;
-        if (type==rct::RCTTypeBulletproof2) {
+        if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG) {
           C_offset = kv_offset+ (8)*outputs_size;
         } else {
           C_offset = kv_offset+ (32+32)*outputs_size;
@@ -1874,7 +1874,7 @@ namespace hw {
           offset = set_command_header(INS_VALIDATE, 0x02, i+1);
           //options
           this->buffer_send[offset] = (i==outputs_size-1)? 0x00:0x80 ;
-          this->buffer_send[offset] |= (type==rct::RCTTypeBulletproof2)?0x02:0x00;
+          this->buffer_send[offset] |= (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG)?0x02:0x00;
           offset += 1;
           //is_subaddress
           this->buffer_send[offset] = outKeys.is_subaddress;
@@ -1895,7 +1895,7 @@ namespace hw {
           memmove(this->buffer_send+offset, data+C_offset,32);
           offset += 32;
           C_offset += 32;
-          if (type==rct::RCTTypeBulletproof2) {
+          if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG) {
             //k
             memset(this->buffer_send+offset, 0, 32);
             offset += 32;
diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp
index 35e7d9605..fa824ec3b 100644
--- a/src/device_trezor/trezor/protocol.hpp
+++ b/src/device_trezor/trezor/protocol.hpp
@@ -309,7 +309,7 @@ namespace tx {
         throw std::invalid_argument("RV not initialized");
       }
       auto tp = m_ct.rv->type;
-      return tp == rct::RCTTypeBulletproof || tp == rct::RCTTypeBulletproof2;
+      return tp == rct::RCTTypeBulletproof || tp == rct::RCTTypeBulletproof2 || tp == rct::RCTTypeCLSAG;
     }
 
     bool is_offloading() const {
diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp
index f4de0ddcf..c94884fd8 100644
--- a/src/hardforks/hardforks.cpp
+++ b/src/hardforks/hardforks.cpp
@@ -67,6 +67,9 @@ const hardfork_t mainnet_hard_forks[] = {
 
   // version 12 starts from block 1978433, which is on or around the 30th of November, 2019. Fork time finalised on 2019-10-18.
   { 12, 1978433, 0, 1571419280 },
+
+  { 13, 2210000, 0, 1598180817 },
+  { 14, 2210720, 0, 1598180818 },
 };
 const size_t num_mainnet_hard_forks = sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]);
 const uint64_t mainnet_hard_fork_version_1_till = 1009826;
@@ -110,5 +113,7 @@ const hardfork_t stagenet_hard_forks[] = {
   { 10, 269000, 0, 1550153694 },
   { 11, 269720, 0, 1550225678 },
   { 12, 454721, 0, 1571419280 },
+  { 13, 699045, 0, 1598180817 },
+  { 14, 699765, 0, 1598180818 },
 };
 const size_t num_stagenet_hard_forks = sizeof(stagenet_hard_forks) / sizeof(stagenet_hard_forks[0]);
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index cb702ed15..074812156 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -168,11 +168,14 @@ namespace rct {
 
     // Generate a CLSAG signature
     // See paper by Goodell et al. (https://eprint.iacr.org/2019/654)
-    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l, const multisig_kLRki *kLRki) {
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout) {
         clsag sig;
         size_t n = P.size(); // ring size
         CHECK_AND_ASSERT_THROW_MES(n == C.size(), "Signing and commitment key vector sizes must match!");
         CHECK_AND_ASSERT_THROW_MES(l < n, "Signing index out of range!");
+        CHECK_AND_ASSERT_THROW_MES(scalarmultBase(z) == C[l], "C does not match z!");
+        CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
+        CHECK_AND_ASSERT_THROW_MES((mscout && mspout) || !kLRki, "Multisig pointers are not all present");
 
         // Key images
         ge_p3 H_p3;
@@ -309,9 +312,18 @@ namespace rct {
         sc_muladd(s0_add_z_mu_C.bytes,mu_C.bytes,z.bytes,s0_p_mu_P.bytes);
         sc_mulsub(sig.s[l].bytes,c.bytes,s0_add_z_mu_C.bytes,a.bytes);
 
+        if (mscout)
+          *mscout = c;
+        if (mspout)
+          *mspout = mu_P;
+
         return sig;
     }
 
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l) {
+        return CLSAG_Gen(message, P, p, C, z, l, NULL, NULL, NULL);
+    }
+
     // Verify a CLSAG signature
     // See paper by Goodell et al. (https://eprint.iacr.org/2019/654)
     bool CLSAG_Ver(const key &message, const keyV & P, const keyV & C, const clsag & sig)
@@ -665,7 +677,7 @@ namespace rct {
       hashes.push_back(hash2rct(h));
 
       keyV kv;
-      if (rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2)
+      if (rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG)
       {
         kv.reserve((6*2+9) * rv.p.bulletproofs.size());
         for (const auto &p: rv.p.bulletproofs)
@@ -793,6 +805,35 @@ namespace rct {
         return result;
     }
 
+    clsag proveRctCLSAGSimple(const key &message, const ctkeyV &pubs, const ctkey &inSk, const key &a, const key &Cout, const multisig_kLRki *kLRki, key *mscout, key *mspout, unsigned int index, hw::device &hwdev) {
+        //setup vars
+        size_t rows = 1;
+        size_t cols = pubs.size();
+        CHECK_AND_ASSERT_THROW_MES(cols >= 1, "Empty pubs");
+        CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
+        keyV tmp(rows + 1);
+        keyV sk(rows + 1);
+        size_t i;
+        keyM M(cols, tmp);
+
+        keyV P, C;
+        P.reserve(pubs.size());
+        C.reserve(pubs.size());
+        for (const ctkey &k: pubs)
+        {
+            P.push_back(k.dest);
+            rct::key tmp;
+            subKeys(tmp, k.mask, Cout);
+            C.push_back(tmp);
+        }
+
+        sk[0] = copy(inSk.dest);
+        sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes);
+        clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], index, kLRki, mscout, mspout);
+        memwipe(sk.data(), sk.size() * sizeof(key));
+        return result;
+    }
+
 
     //Ring-ct MG sigs
     //Prove: 
@@ -872,6 +913,33 @@ namespace rct {
         catch (...) { return false; }
     }
 
+    bool verRctCLSAGSimple(const key &message, const clsag &clsag, const ctkeyV & pubs, const key & C) {
+        try
+        {
+            PERF_TIMER(verRctCLSAGSimple);
+            //setup vars
+            const size_t cols = pubs.size();
+            CHECK_AND_ASSERT_MES(cols >= 1, false, "Empty pubs");
+            keyV Pi(cols), Ci(cols);
+            ge_p3 Cp3;
+            CHECK_AND_ASSERT_MES_L1(ge_frombytes_vartime(&Cp3, C.bytes) == 0, false, "point conv failed");
+            ge_cached Ccached;
+            ge_p3_to_cached(&Ccached, &Cp3);
+            ge_p1p1 p1;
+            //create the matrix to mg sig
+            for (size_t i = 0; i < cols; i++) {
+                    Pi[i] = pubs[i].dest;
+                    ge_p3 p3;
+                    CHECK_AND_ASSERT_MES_L1(ge_frombytes_vartime(&p3, pubs[i].mask.bytes) == 0, false, "point conv failed");
+                    ge_sub(&p1, &p3, &Ccached);
+                    ge_p1p1_to_p3(&p3, &p1);
+                    ge_p3_tobytes(Ci[i].bytes, &p3);
+            }
+            return CLSAG_Ver(message, Pi, Ci, clsag);
+        }
+        catch (...) { return false; }
+    }
+
 
     //These functions get keys from blockchain
     //replace these when connecting blockchain
@@ -964,7 +1032,7 @@ namespace rct {
             //mask amount and mask
             rv.ecdhInfo[i].mask = copy(outSk[i].mask);
             rv.ecdhInfo[i].amount = d2h(amounts[i]);
-            hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2);
+            hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG);
         }
 
         //set txn fee
@@ -1012,7 +1080,27 @@ namespace rct {
         }
 
         rctSig rv;
-        rv.type = bulletproof ? (rct_config.bp_version == 0 || rct_config.bp_version >= 2 ? RCTTypeBulletproof2 : RCTTypeBulletproof) : RCTTypeSimple;
+        if (bulletproof)
+        {
+          switch (rct_config.bp_version)
+          {
+            case 0:
+            case 3:
+              rv.type = RCTTypeCLSAG;
+              break;
+            case 2:
+              rv.type = RCTTypeBulletproof2;
+              break;
+            case 1:
+              rv.type = RCTTypeBulletproof;
+              break;
+            default:
+              ASSERT_MES_AND_THROW("Unsupported BP version: " << rct_config.bp_version);
+          }
+        }
+        else
+          rv.type = RCTTypeSimple;
+
         rv.message = message;
         rv.outPk.resize(destinations.size());
         if (!bulletproof)
@@ -1102,7 +1190,7 @@ namespace rct {
             //mask amount and mask
             rv.ecdhInfo[i].mask = copy(outSk[i].mask);
             rv.ecdhInfo[i].amount = d2h(outamounts[i]);
-            hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2);
+            hwdev.ecdhEncode(rv.ecdhInfo[i], amount_keys[i], rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG);
         }
             
         //set txn fee
@@ -1112,7 +1200,10 @@ namespace rct {
         rv.mixRing = mixRing;
         keyV &pseudoOuts = bulletproof ? rv.p.pseudoOuts : rv.pseudoOuts;
         pseudoOuts.resize(inamounts.size());
-        rv.p.MGs.resize(inamounts.size());
+        if (rv.type == RCTTypeCLSAG)
+            rv.p.CLSAGs.resize(inamounts.size());
+        else
+            rv.p.MGs.resize(inamounts.size());
         key sumpouts = zero(); //sum pseudoOut masks
         keyV a(inamounts.size());
         for (i = 0 ; i < inamounts.size() - 1; i++) {
@@ -1126,9 +1217,20 @@ namespace rct {
 
         key full_message = get_pre_mlsag_hash(rv,hwdev);
         if (msout)
-          msout->c.resize(inamounts.size());
-        for (i = 0 ; i < inamounts.size(); i++) {
-            rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, index[i], hwdev);
+        {
+            msout->c.resize(inamounts.size());
+            msout->mu_p.resize(rv.type == RCTTypeCLSAG ? inamounts.size() : 0);
+        }
+        for (i = 0 ; i < inamounts.size(); i++)
+        {
+            if (rv.type == RCTTypeCLSAG)
+            {
+                rv.p.CLSAGs[i] = proveRctCLSAGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, msout ? &msout->mu_p[i] : NULL, index[i], hwdev);
+            }
+            else
+            {
+                rv.p.MGs[i] = proveRctMGSimple(full_message, rv.mixRing[i], inSk[i], a[i], pseudoOuts[i], kLRki ? &(*kLRki)[i]: NULL, msout ? &msout->c[i] : NULL, index[i], hwdev);
+            }
         }
         return rv;
     }
@@ -1233,13 +1335,22 @@ namespace rct {
         {
           CHECK_AND_ASSERT_MES(rvp, false, "rctSig pointer is NULL");
           const rctSig &rv = *rvp;
-          CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2,
+          CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG,
               false, "verRctSemanticsSimple called on non simple rctSig");
           const bool bulletproof = is_rct_bulletproof(rv.type);
           if (bulletproof)
           {
             CHECK_AND_ASSERT_MES(rv.outPk.size() == n_bulletproof_amounts(rv.p.bulletproofs), false, "Mismatched sizes of outPk and bulletproofs");
-            CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.MGs");
+            if (rv.type == RCTTypeCLSAG)
+            {
+              CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs are not empty for CLSAG");
+              CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.CLSAGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.CLSAGs");
+            }
+            else
+            {
+              CHECK_AND_ASSERT_MES(rv.p.CLSAGs.empty(), false, "CLSAGs are not empty for MLSAG");
+              CHECK_AND_ASSERT_MES(rv.p.pseudoOuts.size() == rv.p.MGs.size(), false, "Mismatched sizes of rv.p.pseudoOuts and rv.p.MGs");
+            }
             CHECK_AND_ASSERT_MES(rv.pseudoOuts.empty(), false, "rv.pseudoOuts is not empty");
           }
           else
@@ -1333,7 +1444,7 @@ namespace rct {
       {
         PERF_TIMER(verRctNonSemanticsSimple);
 
-        CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2,
+        CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG,
             false, "verRctNonSemanticsSimple called on non simple rctSig");
         const bool bulletproof = is_rct_bulletproof(rv.type);
         // semantics check is early, and mixRing/MGs aren't resolved yet
@@ -1356,14 +1467,19 @@ namespace rct {
         results.resize(rv.mixRing.size());
         for (size_t i = 0 ; i < rv.mixRing.size() ; i++) {
           tpool.submit(&waiter, [&, i] {
-              results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], pseudoOuts[i]);
+              if (rv.type == RCTTypeCLSAG)
+              {
+                  results[i] = verRctCLSAGSimple(message, rv.p.CLSAGs[i], rv.mixRing[i], pseudoOuts[i]);
+              }
+              else
+                  results[i] = verRctMGSimple(message, rv.p.MGs[i], rv.mixRing[i], pseudoOuts[i]);
           });
         }
         waiter.wait(&tpool);
 
         for (size_t i = 0; i < results.size(); ++i) {
           if (!results[i]) {
-            LOG_PRINT_L1("verRctMGSimple failed for input " << i);
+            LOG_PRINT_L1("verRctMGSimple/verRctCLSAGSimple failed for input " << i);
             return false;
           }
         }
@@ -1400,7 +1516,7 @@ namespace rct {
 
         //mask amount and mask
         ecdhTuple ecdh_info = rv.ecdhInfo[i];
-        hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2);
+        hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG);
         mask = ecdh_info.mask;
         key amount = ecdh_info.amount;
         key C = rv.outPk[i].mask;
@@ -1424,13 +1540,13 @@ namespace rct {
     }
 
     xmr_amount decodeRctSimple(const rctSig & rv, const key & sk, unsigned int i, key &mask, hw::device &hwdev) {
-        CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2, false, "decodeRct called on non simple rctSig");
+        CHECK_AND_ASSERT_MES(rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG, false, "decodeRct called on non simple rctSig");
         CHECK_AND_ASSERT_THROW_MES(i < rv.ecdhInfo.size(), "Bad index");
         CHECK_AND_ASSERT_THROW_MES(rv.outPk.size() == rv.ecdhInfo.size(), "Mismatched sizes of rv.outPk and rv.ecdhInfo");
 
         //mask amount and mask
         ecdhTuple ecdh_info = rv.ecdhInfo[i];
-        hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2);
+        hwdev.ecdhDecode(ecdh_info, sk, rv.type == RCTTypeBulletproof2 || rv.type == RCTTypeCLSAG);
         mask = ecdh_info.mask;
         key amount = ecdh_info.amount;
         key C = rv.outPk[i].mask;
@@ -1453,12 +1569,13 @@ namespace rct {
       return decodeRctSimple(rv, sk, i, mask, hwdev);
     }
 
-    bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) {
+    bool signMultisigMLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) {
         CHECK_AND_ASSERT_MES(rv.type == RCTTypeFull || rv.type == RCTTypeSimple || rv.type == RCTTypeBulletproof || rv.type == RCTTypeBulletproof2,
             false, "unsupported rct type");
         CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes");
         CHECK_AND_ASSERT_MES(k.size() == rv.p.MGs.size(), false, "Mismatched k/MGs size");
         CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size");
+        CHECK_AND_ASSERT_MES(rv.p.CLSAGs.empty(), false, "CLSAGs not empty for MLSAGs");
         if (rv.type == RCTTypeFull)
         {
           CHECK_AND_ASSERT_MES(rv.p.MGs.size() == 1, false, "MGs not a single element");
@@ -1468,6 +1585,8 @@ namespace rct {
             CHECK_AND_ASSERT_MES(!rv.p.MGs[n].ss[indices[n]].empty(), false, "empty ss line");
         }
 
+        // MLSAG: each player contributes a share to the secret-index ss: k - cc*secret_key_share
+        //     cc: msout.c[n], secret_key_share: secret_key
         for (size_t n = 0; n < indices.size(); ++n) {
             rct::key diff;
             sc_mulsub(diff.bytes, msout.c[n].bytes, secret_key.bytes, k[n].bytes);
@@ -1475,4 +1594,33 @@ namespace rct {
         }
         return true;
     }
+
+    bool signMultisigCLSAG(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) {
+        CHECK_AND_ASSERT_MES(rv.type == RCTTypeCLSAG, false, "unsupported rct type");
+        CHECK_AND_ASSERT_MES(indices.size() == k.size(), false, "Mismatched k/indices sizes");
+        CHECK_AND_ASSERT_MES(k.size() == rv.p.CLSAGs.size(), false, "Mismatched k/MGs size");
+        CHECK_AND_ASSERT_MES(k.size() == msout.c.size(), false, "Mismatched k/msout.c size");
+        CHECK_AND_ASSERT_MES(rv.p.MGs.empty(), false, "MGs not empty for CLSAGs");
+        CHECK_AND_ASSERT_MES(msout.c.size() == msout.mu_p.size(), false, "Bad mu_p size");
+        for (size_t n = 0; n < indices.size(); ++n) {
+            CHECK_AND_ASSERT_MES(indices[n] < rv.p.CLSAGs[n].s.size(), false, "Index out of range");
+        }
+
+        // CLSAG: each player contributes a share to the secret-index ss: k - cc*mu_p*secret_key_share
+        // cc: msout.c[n], mu_p, msout.mu_p[n], secret_key_share: secret_key
+        for (size_t n = 0; n < indices.size(); ++n) {
+            rct::key diff, sk;
+            sc_mul(sk.bytes, msout.mu_p[n].bytes, secret_key.bytes);
+            sc_mulsub(diff.bytes, msout.c[n].bytes, sk.bytes, k[n].bytes);
+            sc_add(rv.p.CLSAGs[n].s[indices[n]].bytes, rv.p.CLSAGs[n].s[indices[n]].bytes, diff.bytes);
+        }
+        return true;
+    }
+
+    bool signMultisig(rctSig &rv, const std::vector<unsigned int> &indices, const keyV &k, const multisig_out &msout, const key &secret_key) {
+        if (rv.type == RCTTypeCLSAG)
+            return signMultisigCLSAG(rv, indices, k, msout, secret_key);
+        else
+            return signMultisigMLSAG(rv, indices, k, msout, secret_key);
+    }
 }
diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h
index 87d2b994b..ed82f6bc5 100644
--- a/src/ringct/rctSigs.h
+++ b/src/ringct/rctSigs.h
@@ -77,9 +77,9 @@ namespace rct {
     mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev);
     bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows);
 
-    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l, const multisig_kLRki *kLRki);
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout);
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l);
     bool CLSAG_Ver(const key &message, const keyV & P, const keyV & C, const clsag & sig);
-    //mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index);
 
     //proveRange and verRange
     //proveRange gives C, and mask such that \sumCi = C
diff --git a/src/ringct/rctTypes.cpp b/src/ringct/rctTypes.cpp
index 1763542db..1f674056d 100644
--- a/src/ringct/rctTypes.cpp
+++ b/src/ringct/rctTypes.cpp
@@ -195,6 +195,7 @@ namespace rct {
             case RCTTypeSimple:
             case RCTTypeBulletproof:
             case RCTTypeBulletproof2:
+            case RCTTypeCLSAG:
                 return true;
             default:
                 return false;
@@ -207,6 +208,7 @@ namespace rct {
         {
             case RCTTypeBulletproof:
             case RCTTypeBulletproof2:
+            case RCTTypeCLSAG:
                 return true;
             default:
                 return false;
diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h
index b83c267d0..cb9e72d2b 100644
--- a/src/ringct/rctTypes.h
+++ b/src/ringct/rctTypes.h
@@ -113,9 +113,14 @@ namespace rct {
 
     struct multisig_out {
         std::vector<key> c; // for all inputs
+        std::vector<key> mu_p; // for all inputs
+        std::vector<key> c0; // for all inputs
 
         BEGIN_SERIALIZE_OBJECT()
           FIELD(c)
+          FIELD(mu_p)
+          if (!mu_p.empty() && mu_p.size() != c.size())
+            return false;
         END_SERIALIZE()
     };
 
@@ -175,6 +180,8 @@ namespace rct {
         BEGIN_SERIALIZE_OBJECT()
             FIELD(s)
             FIELD(c1)
+            // FIELD(I) - not serialized, it can be reconstructed
+            FIELD(D)
         END_SERIALIZE()
     };
 
@@ -249,6 +256,7 @@ namespace rct {
       RCTTypeSimple = 2,
       RCTTypeBulletproof = 3,
       RCTTypeBulletproof2 = 4,
+      RCTTypeCLSAG = 5,
     };
     enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof };
     struct RCTConfig {
@@ -277,7 +285,7 @@ namespace rct {
           FIELD(type)
           if (type == RCTTypeNull)
             return ar.stream().good();
-          if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2)
+          if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG)
             return false;
           VARINT_FIELD(txnFee)
           // inputs/outputs not saved, only here for serialization help
@@ -306,7 +314,7 @@ namespace rct {
             return false;
           for (size_t i = 0; i < outputs; ++i)
           {
-            if (type == RCTTypeBulletproof2)
+            if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
             {
               ar.begin_object();
               if (!typename Archive<W>::is_saving())
@@ -353,6 +361,7 @@ namespace rct {
         std::vector<rangeSig> rangeSigs;
         std::vector<Bulletproof> bulletproofs;
         std::vector<mgSig> MGs; // simple rct has N, full has 1
+        std::vector<clsag> CLSAGs;
         keyV pseudoOuts; //C - for simple rct
 
         // when changing this function, update cryptonote::get_pruned_transaction_weight
@@ -361,12 +370,12 @@ namespace rct {
         {
           if (type == RCTTypeNull)
             return ar.stream().good();
-          if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2)
+          if (type != RCTTypeFull && type != RCTTypeSimple && type != RCTTypeBulletproof && type != RCTTypeBulletproof2 && type != RCTTypeCLSAG)
             return false;
-          if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2)
+          if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
           {
             uint32_t nbp = bulletproofs.size();
-            if (type == RCTTypeBulletproof2)
+            if (type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
               VARINT_FIELD(nbp)
             else
               FIELD(nbp)
@@ -401,55 +410,98 @@ namespace rct {
             ar.end_array();
           }
 
-          ar.tag("MGs");
-          ar.begin_array();
-          // we keep a byte for size of MGs, because we don't know whether this is
-          // a simple or full rct signature, and it's starting to annoy the hell out of me
-          size_t mg_elements = (type == RCTTypeSimple || type == RCTTypeBulletproof || type == RCTTypeBulletproof2) ? inputs : 1;
-          PREPARE_CUSTOM_VECTOR_SERIALIZATION(mg_elements, MGs);
-          if (MGs.size() != mg_elements)
-            return false;
-          for (size_t i = 0; i < mg_elements; ++i)
+          if (type == RCTTypeCLSAG)
           {
-            // we save the MGs contents directly, because we want it to save its
-            // arrays and matrices without the size prefixes, and the load can't
-            // know what size to expect if it's not in the data
-            ar.begin_object();
-            ar.tag("ss");
+            ar.tag("CLSAGs");
             ar.begin_array();
-            PREPARE_CUSTOM_VECTOR_SERIALIZATION(mixin + 1, MGs[i].ss);
-            if (MGs[i].ss.size() != mixin + 1)
+            PREPARE_CUSTOM_VECTOR_SERIALIZATION(inputs, CLSAGs);
+            if (CLSAGs.size() != inputs)
               return false;
-            for (size_t j = 0; j < mixin + 1; ++j)
+            for (size_t i = 0; i < inputs; ++i)
             {
+              // we save the CLSAGs contents directly, because we want it to save its
+              // arrays without the size prefixes, and the load can't know what size
+              // to expect if it's not in the data
+              ar.begin_object();
+              ar.tag("s");
               ar.begin_array();
-              size_t mg_ss2_elements = ((type == RCTTypeSimple || type == RCTTypeBulletproof || type == RCTTypeBulletproof2) ? 1 : inputs) + 1;
-              PREPARE_CUSTOM_VECTOR_SERIALIZATION(mg_ss2_elements, MGs[i].ss[j]);
-              if (MGs[i].ss[j].size() != mg_ss2_elements)
+              PREPARE_CUSTOM_VECTOR_SERIALIZATION(mixin + 1, CLSAGs[i].s);
+              if (CLSAGs[i].s.size() != mixin + 1)
                 return false;
-              for (size_t k = 0; k < mg_ss2_elements; ++k)
+              for (size_t j = 0; j <= mixin; ++j)
               {
-                FIELDS(MGs[i].ss[j][k])
-                if (mg_ss2_elements - k > 1)
+                FIELDS(CLSAGs[i].s[j])
+                if (mixin + 1 - j > 1)
                   ar.delimit_array();
               }
               ar.end_array();
 
-              if (mixin + 1 - j > 1)
-                ar.delimit_array();
+              ar.tag("c1");
+              FIELDS(CLSAGs[i].c1)
+
+              // CLSAGs[i].I not saved, it can be reconstructed
+              ar.tag("D");
+              FIELDS(CLSAGs[i].D)
+              ar.end_object();
+
+              if (inputs - i > 1)
+                 ar.delimit_array();
+            }
+
+            ar.end_array();
+          }
+          else
+          {
+            ar.tag("MGs");
+            ar.begin_array();
+            // we keep a byte for size of MGs, because we don't know whether this is
+            // a simple or full rct signature, and it's starting to annoy the hell out of me
+            size_t mg_elements = (type == RCTTypeSimple || type == RCTTypeBulletproof || type == RCTTypeBulletproof2) ? inputs : 1;
+            PREPARE_CUSTOM_VECTOR_SERIALIZATION(mg_elements, MGs);
+            if (MGs.size() != mg_elements)
+              return false;
+            for (size_t i = 0; i < mg_elements; ++i)
+            {
+              // we save the MGs contents directly, because we want it to save its
+              // arrays and matrices without the size prefixes, and the load can't
+              // know what size to expect if it's not in the data
+              ar.begin_object();
+              ar.tag("ss");
+              ar.begin_array();
+              PREPARE_CUSTOM_VECTOR_SERIALIZATION(mixin + 1, MGs[i].ss);
+              if (MGs[i].ss.size() != mixin + 1)
+                return false;
+              for (size_t j = 0; j < mixin + 1; ++j)
+              {
+                ar.begin_array();
+                size_t mg_ss2_elements = ((type == RCTTypeSimple || type == RCTTypeBulletproof || type == RCTTypeBulletproof2) ? 1 : inputs) + 1;
+                PREPARE_CUSTOM_VECTOR_SERIALIZATION(mg_ss2_elements, MGs[i].ss[j]);
+                if (MGs[i].ss[j].size() != mg_ss2_elements)
+                  return false;
+                for (size_t k = 0; k < mg_ss2_elements; ++k)
+                {
+                  FIELDS(MGs[i].ss[j][k])
+                  if (mg_ss2_elements - k > 1)
+                    ar.delimit_array();
+                }
+                ar.end_array();
+  
+                if (mixin + 1 - j > 1)
+                  ar.delimit_array();
+              }
+              ar.end_array();
+
+              ar.tag("cc");
+              FIELDS(MGs[i].cc)
+              // MGs[i].II not saved, it can be reconstructed
+              ar.end_object();
+
+              if (mg_elements - i > 1)
+                 ar.delimit_array();
             }
             ar.end_array();
-
-            ar.tag("cc");
-            FIELDS(MGs[i].cc)
-            // MGs[i].II not saved, it can be reconstructed
-            ar.end_object();
-
-            if (mg_elements - i > 1)
-               ar.delimit_array();
           }
-          ar.end_array();
-          if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2)
+          if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
           {
             ar.tag("pseudoOuts");
             ar.begin_array();
@@ -479,12 +531,12 @@ namespace rct {
 
         keyV& get_pseudo_outs()
         {
-          return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 ? p.pseudoOuts : pseudoOuts;
+          return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts;
         }
 
         keyV const& get_pseudo_outs() const
         {
-          return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 ? p.pseudoOuts : pseudoOuts;
+          return type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG ? p.pseudoOuts : pseudoOuts;
         }
 
         BEGIN_SERIALIZE_OBJECT()
@@ -651,6 +703,7 @@ VARIANT_TAG(debug_archive, rct::rctSig, "rct::rctSig");
 VARIANT_TAG(debug_archive, rct::Bulletproof, "rct::bulletproof");
 VARIANT_TAG(debug_archive, rct::multisig_kLRki, "rct::multisig_kLRki");
 VARIANT_TAG(debug_archive, rct::multisig_out, "rct::multisig_out");
+VARIANT_TAG(debug_archive, rct::clsag, "rct::clsag");
 
 VARIANT_TAG(binary_archive, rct::key, 0x90);
 VARIANT_TAG(binary_archive, rct::key64, 0x91);
@@ -667,6 +720,7 @@ VARIANT_TAG(binary_archive, rct::rctSig, 0x9b);
 VARIANT_TAG(binary_archive, rct::Bulletproof, 0x9c);
 VARIANT_TAG(binary_archive, rct::multisig_kLRki, 0x9d);
 VARIANT_TAG(binary_archive, rct::multisig_out, 0x9e);
+VARIANT_TAG(binary_archive, rct::clsag, 0x9f);
 
 VARIANT_TAG(json_archive, rct::key, "rct_key");
 VARIANT_TAG(json_archive, rct::key64, "rct_key64");
@@ -683,5 +737,6 @@ VARIANT_TAG(json_archive, rct::rctSig, "rct_rctSig");
 VARIANT_TAG(json_archive, rct::Bulletproof, "rct_bulletproof");
 VARIANT_TAG(json_archive, rct::multisig_kLRki, "rct_multisig_kLR");
 VARIANT_TAG(json_archive, rct::multisig_out, "rct_multisig_out");
+VARIANT_TAG(json_archive, rct::clsag, "rct_clsag");
 
 #endif  /* RCTTYPES_H */
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 9e17bed63..36e86ae5d 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -1306,7 +1306,7 @@ namespace cryptonote
       case 1: res.pow_algorithm = "CNv1 (Cryptonight variant 1)"; break;
       case 2: case 3: res.pow_algorithm = "CNv2 (Cryptonight variant 2)"; break;
       case 4: case 5: res.pow_algorithm = "CNv4 (Cryptonight variant 4)"; break;
-      case 6: res.pow_algorithm = "RandomX"; break;
+      case 6: case 7: res.pow_algorithm = "RandomX"; break;
       default: res.pow_algorithm = "I'm not sure actually"; break;
     }
     if (res.is_background_mining_enabled)
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 73ab88a9f..e00f9d2e9 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -1692,6 +1692,7 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::str
         destinations.size() + 1,
         extra_size,
         m_wallet->use_fork_rules(8, 0),
+        m_wallet->use_fork_rules(HF_VERSION_CLSAG, 0),
         m_wallet->get_base_fee(),
         m_wallet->get_fee_multiplier(m_wallet->adjust_priority(static_cast<uint32_t>(priority))),
         m_wallet->get_fee_quantization_mask());
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 5d78bb7b0..31aaaddea 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -243,6 +243,22 @@ namespace
         add_reason(reason, "tx was not relayed");
       return reason;
   }
+
+  size_t get_num_outputs(const std::vector<cryptonote::tx_destination_entry> &dsts, const std::vector<tools::wallet2::transfer_details> &transfers, const std::vector<size_t> &selected_transfers)
+  {
+    size_t outputs = dsts.size();
+    uint64_t needed_money = 0;
+    for (const auto& dt: dsts)
+      needed_money += dt.amount;
+    uint64_t found_money = 0;
+    for(size_t idx: selected_transfers)
+      found_money += transfers[idx].amount();
+    if (found_money != needed_money)
+      ++outputs; // change
+    if (outputs < 2)
+      ++outputs; // extra 0 dummy output
+    return outputs;
+  }
 }
 
 namespace
@@ -795,7 +811,7 @@ void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_
   }
 }
 
-size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof)
+size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag)
 {
   size_t size = 0;
 
@@ -829,8 +845,11 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra
   else
     size += (2*64*32+32+64*32) * n_outputs;
 
-  // MGs
-  size += n_inputs * (64 * (mixin+1) + 32);
+  // MGs/CLSAGs
+  if (clsag)
+    size += n_inputs * (32 * (mixin+1) + 64);
+  else
+    size += n_inputs * (64 * (mixin+1) + 32);
 
   // mixRing - not serialized, can be reconstructed
   /* size += 2 * 32 * (mixin+1) * n_inputs; */
@@ -848,17 +867,17 @@ size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra
   return size;
 }
 
-size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof)
+size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag)
 {
   if (use_rct)
-    return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof);
+    return estimate_rct_tx_size(n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag);
   else
     return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size;
 }
 
-uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof)
+uint64_t estimate_tx_weight(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag)
 {
-  size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof);
+  size_t size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag);
   if (use_rct && bulletproof && n_outputs > 2)
   {
     const uint64_t bp_base = 368;
@@ -879,6 +898,11 @@ uint8_t get_bulletproof_fork()
   return 8;
 }
 
+uint8_t get_clsag_fork()
+{
+  return HF_VERSION_CLSAG;
+}
+
 uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask)
 {
   if (use_per_byte_fee)
@@ -1752,6 +1776,7 @@ static uint64_t decodeRct(const rct::rctSig & rv, const crypto::key_derivation &
     case rct::RCTTypeSimple:
     case rct::RCTTypeBulletproof:
     case rct::RCTTypeBulletproof2:
+    case rct::RCTTypeCLSAG:
       return rct::decodeRctSimple(rv, rct::sk2rct(scalar1), i, mask, hwdev);
     case rct::RCTTypeFull:
       return rct::decodeRct(rv, rct::sk2rct(scalar1), i, mask, hwdev);
@@ -7354,16 +7379,16 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto
   return sign_multisig_tx_to_file(exported_txs, filename, txids);
 }
 //----------------------------------------------------------------------------------------------------
-uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const
+uint64_t wallet2::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const
 {
   if (use_per_byte_fee)
   {
-    const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof);
+    const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag);
     return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask);
   }
   else
   {
-    const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof);
+    const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof, clsag);
     return calculate_fee(base_fee, estimated_tx_size, fee_multiplier);
   }
 }
@@ -9066,7 +9091,10 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
   ptx.construction_data.extra = tx.extra;
   ptx.construction_data.unlock_time = unlock_time;
   ptx.construction_data.use_rct = true;
-  ptx.construction_data.rct_config = { tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof, use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1};
+  ptx.construction_data.rct_config = {
+    tx.rct_signatures.p.bulletproofs.empty() ? rct::RangeProofBorromean : rct::RangeProofPaddedBulletproof,
+    use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1
+  };
   ptx.construction_data.dests = dsts;
   // record which subaddress indices are being used as inputs
   ptx.construction_data.subaddr_account = subaddr_account;
@@ -9752,9 +9780,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
   const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0);
   const bool use_rct = use_fork_rules(4, 0);
   const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
+  const bool clsag = use_fork_rules(get_clsag_fork(), 0);
   const rct::RCTConfig rct_config {
     bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean,
-    bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0
+    bulletproof ? (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0
   };
 
   const uint64_t base_fee  = get_base_fee();
@@ -9790,7 +9819,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
   // early out if we know we can't make it anyway
   // we could also check for being within FEE_PER_KB, but if the fee calculation
   // ever changes, this might be missed, so let this go through
-  const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof));
+  const uint64_t min_fee = (fee_multiplier * base_fee * estimate_tx_size(use_rct, 1, fake_outs_count, 2, extra.size(), bulletproof, clsag));
   uint64_t balance_subtotal = 0;
   uint64_t unlocked_balance_subtotal = 0;
   for (uint32_t index_minor : subaddr_indices)
@@ -9808,8 +9837,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
     LOG_PRINT_L2("Candidate subaddress index for spending: " << i);
 
   // determine threshold for fractional amount
-  const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof);
-  const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof);
+  const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag);
+  const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag);
   THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!");
   const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring;
   const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024);
@@ -9906,7 +9935,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
   {
     // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which
     // will get us a known fee.
-    uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask);
+    uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask);
     preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices);
     if (!preferred_inputs.empty())
     {
@@ -10018,7 +10047,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
     }
     else
     {
-      while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof) < TX_WEIGHT_TARGET(upper_transaction_weight_limit))
+      while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit))
       {
         // we can fully pay that destination
         LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<
@@ -10030,7 +10059,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
         ++original_output_index;
       }
 
-      if (available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) {
+      if (available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) {
         // we can partially fill that destination
         LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) <<
           " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount));
@@ -10054,7 +10083,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
       }
       else
       {
-        const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof);
+        const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag);
         try_tx = dsts.empty() || (estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit));
         THROW_WALLET_EXCEPTION_IF(try_tx && tx.dsts.empty(), error::tx_too_big, estimated_rct_tx_weight, upper_transaction_weight_limit);
       }
@@ -10064,7 +10093,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
       cryptonote::transaction test_tx;
       pending_tx test_ptx;
 
-      needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask);
+      const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers);
+      needed_fee = estimate_fee(use_per_byte_fee, use_rct ,tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask);
 
       uint64_t inputs = 0, outputs = needed_fee;
       for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount();
@@ -10313,10 +10343,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
   // determine threshold for fractional amount
   const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0);
   const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
+  const bool clsag = use_fork_rules(get_clsag_fork(), 0);
   const uint64_t base_fee  = get_base_fee();
   const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
-  const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof);
-  const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof);
+  const size_t tx_weight_one_ring = estimate_tx_weight(use_rct, 1, fake_outs_count, 2, 0, bulletproof, clsag);
+  const size_t tx_weight_two_rings = estimate_tx_weight(use_rct, 2, fake_outs_count, 2, 0, bulletproof, clsag);
   THROW_WALLET_EXCEPTION_IF(tx_weight_one_ring > tx_weight_two_rings, error::wallet_internal_error, "Estimated tx weight with 1 input is larger than with 2 inputs!");
   const size_t tx_weight_per_ring = tx_weight_two_rings - tx_weight_one_ring;
   const uint64_t fractional_threshold = (fee_multiplier * base_fee * tx_weight_per_ring) / (use_per_byte_fee ? 1 : 1024);
@@ -10422,9 +10453,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
   const bool use_per_byte_fee = use_fork_rules(HF_VERSION_PER_BYTE_FEE);
   const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0);
   const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
+  const bool clsag = use_fork_rules(get_clsag_fork(), 0);
   const rct::RCTConfig rct_config {
     bulletproof ? rct::RangeProofPaddedBulletproof : rct::RangeProofBorromean,
-    bulletproof ? (use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0,
+    bulletproof ? (use_fork_rules(HF_VERSION_CLSAG, -10) ? 3 : use_fork_rules(HF_VERSION_SMALLER_BP, -10) ? 2 : 1) : 0,
   };
   const uint64_t base_fee  = get_base_fee();
   const uint64_t fee_multiplier = get_fee_multiplier(priority, get_fee_algorithm());
@@ -10453,7 +10485,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
     uint64_t fee_dust_threshold;
     if (use_fork_rules(HF_VERSION_PER_BYTE_FEE))
     {
-      const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof);
+      const uint64_t estimated_tx_weight_with_one_extra_output = estimate_tx_weight(use_rct, tx.selected_transfers.size() + 1, fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag);
       fee_dust_threshold = calculate_fee_from_weight(base_fee, estimated_tx_weight_with_one_extra_output, fee_multiplier, fee_quantization_mask);
     }
     else
@@ -10484,14 +10516,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
     // here, check if we need to sent tx and start a new one
     LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit "
       << upper_transaction_weight_limit);
-    const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof);
+    const size_t estimated_rct_tx_weight = estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 2, extra.size(), bulletproof, clsag);
     bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_weight >= TX_WEIGHT_TARGET(upper_transaction_weight_limit));
 
     if (try_tx) {
       cryptonote::transaction test_tx;
       pending_tx test_ptx;
 
-      needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, base_fee, fee_multiplier, fee_quantization_mask);
+      const size_t num_outputs = get_num_outputs(tx.dsts, m_transfers, tx.selected_transfers);
+      needed_fee = estimate_fee(use_per_byte_fee, use_rct, tx.selected_transfers.size(), fake_outs_count, num_outputs, extra.size(), bulletproof, clsag, base_fee, fee_multiplier, fee_quantization_mask);
 
       // add N - 1 outputs for correct initial fee estimation
       for (size_t i = 0; i < ((outputs > 1) ? outputs - 1 : outputs); ++i)
@@ -11353,7 +11386,7 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt
         crypto::secret_key scalar1;
         crypto::derivation_to_scalar(found_derivation, n, scalar1);
         rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
-        rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2);
+        rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
         const rct::key C = tx.rct_signatures.outPk[n].mask;
         rct::key Ctmp;
         THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask");
@@ -11997,7 +12030,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
       crypto::secret_key shared_secret;
       crypto::derivation_to_scalar(derivation, proof.index_in_tx, shared_secret);
       rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[proof.index_in_tx];
-      rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2);
+      rct::ecdhDecode(ecdh_info, rct::sk2rct(shared_secret), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
       amount = rct::h2d(ecdh_info.amount);
     }
     total += amount;
@@ -14036,8 +14069,9 @@ std::pair<size_t, uint64_t> wallet2::estimate_tx_size_and_weight(bool use_rct, i
     n_outputs = 2; // extra dummy output
 
   const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0);
-  size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof);
-  uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof);
+  const bool clsag = use_fork_rules(get_clsag_fork(), 0);
+  size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag);
+  uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof, clsag);
   return std::make_pair(size, weight);
 }
 //----------------------------------------------------------------------------------------------------
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index f283a873e..117e4e2f2 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -1400,7 +1400,7 @@ private:
     std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels);
     std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees);
 
-    uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const;
+    uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, bool clsag, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const;
     uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1);
     uint64_t get_base_fee();
     uint64_t get_fee_quantization_mask();
diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt
index ca9a09d82..654233d03 100644
--- a/tests/core_tests/CMakeLists.txt
+++ b/tests/core_tests/CMakeLists.txt
@@ -44,6 +44,7 @@ set(core_tests_sources
   v2_tests.cpp
   rct.cpp
   bulletproofs.cpp
+  rct2.cpp
   wallet_tools.cpp)
 
 set(core_tests_headers
@@ -64,6 +65,7 @@ set(core_tests_headers
   v2_tests.h
   rct.h
   bulletproofs.h
+  rct2.h
   wallet_tools.h)
 
 add_executable(core_tests
diff --git a/tests/core_tests/bulletproofs.cpp b/tests/core_tests/bulletproofs.cpp
index 04eeb9e01..44adc42e7 100644
--- a/tests/core_tests/bulletproofs.cpp
+++ b/tests/core_tests/bulletproofs.cpp
@@ -42,7 +42,7 @@ using namespace cryptonote;
 // Tests
 
 bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& events,
-    size_t mixin, size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config,
+    size_t mixin, size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version,
     const std::function<bool(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations, size_t tx_idx)> &pre_tx,
     const std::function<bool(transaction &tx, size_t tx_idx)> &post_tx) const
 {
@@ -157,7 +157,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& eve
       crypto::derivation_to_scalar(derivation, o, amount_key);
       rct::key rct_tx_mask;
       const uint8_t type = rct_txes.back().rct_signatures.type;
-      if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2)
+      if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2 || type == rct::RCTTypeCLSAG)
         rct::decodeRctSimple(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
       else
         rct::decodeRct(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
@@ -173,7 +173,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& eve
 
   CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk_txes, blk_last, miner_account,
       test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_tx_hashes | test_generator::bf_hf_version | test_generator::bf_max_outs,
-      10, 10, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
+      hf_version, hf_version, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
       crypto::hash(), 0, transaction(), starting_rct_tx_hashes, 0, 6, 10),
       false, "Failed to generate block");
   if (!valid)
@@ -205,13 +205,22 @@ bool gen_bp_tx_validation_base::check_bp(const cryptonote::transaction &tx, size
   return true;
 }
 
-bool gen_bp_tx_valid_1::generate(std::vector<test_event_entry>& events) const
+bool gen_bp_tx_valid_1_before_12::generate(std::vector<test_event_entry>& events) const
 {
   const size_t mixin = 10;
   const uint64_t amounts_paid[] = {10000, (uint64_t)-1};
   const size_t bp_sizes[] = {1, (size_t)-1};
-  const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 0 } };
-  return generate_with(events, mixin, 1, amounts_paid, true, rct_config, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_1"); });
+  const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 2 } };
+  return generate_with(events, mixin, 1, amounts_paid, true, rct_config, 11, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_1_before_12"); });
+}
+
+bool gen_bp_tx_invalid_1_from_12::generate(std::vector<test_event_entry>& events) const
+{
+  const size_t mixin = 10;
+  const uint64_t amounts_paid[] = {10000, (uint64_t)-1};
+  const size_t bp_sizes[] = {1, (size_t)-1};
+  const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 2 } };
+  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, 12, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_invalid_1_from_12"); });
 }
 
 bool gen_bp_tx_invalid_1_1::generate(std::vector<test_event_entry>& events) const
@@ -219,7 +228,7 @@ bool gen_bp_tx_invalid_1_1::generate(std::vector<test_event_entry>& events) cons
   const size_t mixin = 10;
   const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
   const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof , 0 } };
-  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, NULL);
+  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL);
 }
 
 bool gen_bp_tx_valid_2::generate(std::vector<test_event_entry>& events) const
@@ -228,7 +237,7 @@ bool gen_bp_tx_valid_2::generate(std::vector<test_event_entry>& events) const
   const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
   const size_t bp_sizes[] = {2, (size_t)-1};
   const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 0 } };
-  return generate_with(events, mixin, 1, amounts_paid, true, rct_config, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_2"); });
+  return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_2"); });
 }
 
 bool gen_bp_tx_valid_3::generate(std::vector<test_event_entry>& events) const
@@ -237,7 +246,7 @@ bool gen_bp_tx_valid_3::generate(std::vector<test_event_entry>& events) const
   const uint64_t amounts_paid[] = {5000, 5000, 5000, (uint64_t)-1};
   const size_t bp_sizes[] = {4, (size_t)-1};
   const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 0 } };
-  return generate_with(events, mixin, 1, amounts_paid, true, rct_config, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_3"); });
+  return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_3"); });
 }
 
 bool gen_bp_tx_valid_16::generate(std::vector<test_event_entry>& events) const
@@ -246,7 +255,7 @@ bool gen_bp_tx_valid_16::generate(std::vector<test_event_entry>& events) const
   const uint64_t amounts_paid[] = {500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, (uint64_t)-1};
   const size_t bp_sizes[] = {16, (size_t)-1};
   const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof , 0 } };
-  return generate_with(events, mixin, 1, amounts_paid, true, rct_config, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_16"); });
+  return generate_with(events, mixin, 1, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_tx_valid_16"); });
 }
 
 bool gen_bp_tx_invalid_4_2_1::generate(std::vector<test_event_entry>& events) const
@@ -254,7 +263,7 @@ bool gen_bp_tx_invalid_4_2_1::generate(std::vector<test_event_entry>& events) co
   const size_t mixin = 10;
   const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1};
   const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 0 } };
-  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, NULL);
+  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL);
 }
 
 bool gen_bp_tx_invalid_16_16::generate(std::vector<test_event_entry>& events) const
@@ -262,7 +271,7 @@ bool gen_bp_tx_invalid_16_16::generate(std::vector<test_event_entry>& events) co
   const size_t mixin = 10;
   const uint64_t amounts_paid[] = {1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1};
   const rct::RCTConfig rct_config[] = { { rct::RangeProofMultiOutputBulletproof , 0 } };
-  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, NULL);
+  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL);
 }
 
 bool gen_bp_txs_valid_2_and_2::generate(std::vector<test_event_entry>& events) const
@@ -271,7 +280,7 @@ bool gen_bp_txs_valid_2_and_2::generate(std::vector<test_event_entry>& events) c
   const uint64_t amounts_paid[] = {1000, 1000, (size_t)-1, 1000, 1000, (uint64_t)-1};
   const size_t bp_sizes[] = {2, (size_t)-1, 2, (size_t)-1};
   const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 0 }, {rct::RangeProofPaddedBulletproof, 0 } };
-  return generate_with(events, mixin, 2, amounts_paid, true, rct_config, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_2"); });
+  return generate_with(events, mixin, 2, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx){ return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_2"); });
 }
 
 bool gen_bp_txs_invalid_2_and_8_2_and_16_16_1::generate(std::vector<test_event_entry>& events) const
@@ -279,7 +288,7 @@ bool gen_bp_txs_invalid_2_and_8_2_and_16_16_1::generate(std::vector<test_event_e
   const size_t mixin = 10;
   const uint64_t amounts_paid[] = {1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, 1000, (uint64_t)-1};
   const rct::RCTConfig rct_config[] = {{rct::RangeProofMultiOutputBulletproof, 0}, {rct::RangeProofMultiOutputBulletproof, 0}, {rct::RangeProofMultiOutputBulletproof, 0}};
-  return generate_with(events, mixin, 3, amounts_paid, false, rct_config, NULL, NULL);
+  return generate_with(events, mixin, 3, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, NULL);
 }
 
 bool gen_bp_txs_valid_2_and_3_and_2_and_4::generate(std::vector<test_event_entry>& events) const
@@ -288,7 +297,7 @@ bool gen_bp_txs_valid_2_and_3_and_2_and_4::generate(std::vector<test_event_entry
   const uint64_t amounts_paid[] = {11111115000, 11111115000, (uint64_t)-1, 11111115000, 11111115000, 11111115001, (uint64_t)-1, 11111115000, 11111115002, (uint64_t)-1, 11111115000, 11111115000, 11111115000, 11111115003, (uint64_t)-1};
   const rct::RCTConfig rct_config[] = {{rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}, {rct::RangeProofPaddedBulletproof, 0}};
   const size_t bp_sizes[] = {2, (size_t)-1, 4, (size_t)-1, 2, (size_t)-1, 4, (size_t)-1};
-  return generate_with(events, mixin, 4, amounts_paid, true, rct_config, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx) { return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_3_and_2_and_4"); });
+  return generate_with(events, mixin, 4, amounts_paid, true, rct_config, HF_VERSION_CLSAG, NULL, [&](const cryptonote::transaction &tx, size_t tx_idx) { return check_bp(tx, tx_idx, bp_sizes, "gen_bp_txs_valid_2_and_3_and_2_and_4"); });
 }
 
 bool gen_bp_tx_invalid_not_enough_proofs::generate(std::vector<test_event_entry>& events) const
@@ -297,8 +306,8 @@ bool gen_bp_tx_invalid_not_enough_proofs::generate(std::vector<test_event_entry>
   const size_t mixin = 10;
   const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
   const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } };
-  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, [&](cryptonote::transaction &tx, size_t idx){
-    CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2);
+  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){
+    CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
     CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty());
     tx.rct_signatures.p.bulletproofs.pop_back();
     CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty());
@@ -312,8 +321,8 @@ bool gen_bp_tx_invalid_empty_proofs::generate(std::vector<test_event_entry>& eve
   const size_t mixin = 10;
   const uint64_t amounts_paid[] = {50000, 50000, (uint64_t)-1};
   const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } };
-  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, [&](cryptonote::transaction &tx, size_t idx){
-    CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2);
+  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){
+    CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
     tx.rct_signatures.p.bulletproofs.clear();
     return true;
   });
@@ -325,8 +334,8 @@ bool gen_bp_tx_invalid_too_many_proofs::generate(std::vector<test_event_entry>&
   const size_t mixin = 10;
   const uint64_t amounts_paid[] = {10000, (uint64_t)-1};
   const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } };
-  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, [&](cryptonote::transaction &tx, size_t idx){
-    CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2);
+  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){
+    CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
     CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty());
     tx.rct_signatures.p.bulletproofs.push_back(tx.rct_signatures.p.bulletproofs.back());
     return true;
@@ -339,8 +348,8 @@ bool gen_bp_tx_invalid_wrong_amount::generate(std::vector<test_event_entry>& eve
   const size_t mixin = 10;
   const uint64_t amounts_paid[] = {10000, (uint64_t)-1};
   const rct::RCTConfig rct_config[] = { { rct::RangeProofBulletproof, 0 } };
-  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, [&](cryptonote::transaction &tx, size_t idx){
-    CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2);
+  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG, NULL, [&](cryptonote::transaction &tx, size_t idx){
+    CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeBulletproof || tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
     CHECK_TEST_CONDITION(!tx.rct_signatures.p.bulletproofs.empty());
     tx.rct_signatures.p.bulletproofs.back() = rct::bulletproof_PROVE(1000, rct::skGen());
     return true;
@@ -353,7 +362,18 @@ bool gen_bp_tx_invalid_borromean_type::generate(std::vector<test_event_entry>& e
   const size_t mixin = 10;
   const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
   const rct::RCTConfig rct_config[] = { { rct::RangeProofBorromean, 0 } };
-  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, NULL, [&](cryptonote::transaction &tx, size_t tx_idx){
+  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, 11, NULL, [&](cryptonote::transaction &tx, size_t tx_idx){
+    return true;
+  });
+}
+
+bool gen_bp_tx_invalid_bulletproof2_type::generate(std::vector<test_event_entry>& events) const
+{
+  DEFINE_TESTS_ERROR_CONTEXT("gen_bp_tx_invalid_bulletproof2_type");
+  const size_t mixin = 10;
+  const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
+  const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 2 } };
+  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG + 1, NULL, [&](cryptonote::transaction &tx, size_t tx_idx){
     return true;
   });
 }
diff --git a/tests/core_tests/bulletproofs.h b/tests/core_tests/bulletproofs.h
index 93fe2947f..b30d82e68 100644
--- a/tests/core_tests/bulletproofs.h
+++ b/tests/core_tests/bulletproofs.h
@@ -82,7 +82,7 @@ struct gen_bp_tx_validation_base : public test_chain_unit_base
   }
 
   bool generate_with(std::vector<test_event_entry>& events, size_t mixin,
-      size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config,
+      size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version,
       const std::function<bool(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations, size_t)> &pre_tx,
       const std::function<bool(cryptonote::transaction &tx, size_t)> &post_tx) const;
 
@@ -95,99 +95,119 @@ private:
 
 template<>
 struct get_test_options<gen_bp_tx_validation_base> {
-  const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(10, 73), std::make_pair(0, 0)};
+  const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(12, 73), std::make_pair(0, 0)};
+  const cryptonote::test_options test_options = {
+    hard_forks, 0
+  };
+};
+
+template<uint8_t test_version = HF_VERSION_CLSAG>
+struct get_bp_versioned_test_options: public get_test_options<gen_bp_tx_validation_base> {
+  const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(test_version, 73), std::make_pair(0, 0)};
   const cryptonote::test_options test_options = {
     hard_forks, 0
   };
 };
 
 // valid
-struct gen_bp_tx_valid_1 : public gen_bp_tx_validation_base
+struct gen_bp_tx_valid_1_before_12 : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_tx_valid_1>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_tx_valid_1_before_12>: public get_bp_versioned_test_options<11> {};
+
+struct gen_bp_tx_invalid_1_from_12 : public gen_bp_tx_validation_base
+{
+  bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bp_tx_invalid_1_from_12>: public get_bp_versioned_test_options<12> {};
 
 struct gen_bp_tx_invalid_1_1 : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_tx_invalid_1_1>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_tx_invalid_1_1>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_tx_valid_2 : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_tx_valid_2>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_tx_valid_2>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_tx_valid_3 : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_tx_valid_3>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_tx_valid_3>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_tx_valid_16 : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_tx_valid_16>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_tx_valid_16>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_tx_invalid_4_2_1 : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_tx_invalid_4_2_1>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_tx_invalid_4_2_1>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_tx_invalid_16_16 : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_tx_invalid_16_16>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_tx_invalid_16_16>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_txs_valid_2_and_2 : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_txs_valid_2_and_2>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_txs_valid_2_and_2>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_txs_invalid_2_and_8_2_and_16_16_1 : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_txs_invalid_2_and_8_2_and_16_16_1>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_txs_invalid_2_and_8_2_and_16_16_1>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_txs_valid_2_and_3_and_2_and_4 : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_txs_valid_2_and_3_and_2_and_4>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_txs_valid_2_and_3_and_2_and_4>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_tx_invalid_not_enough_proofs : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_tx_invalid_not_enough_proofs>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_tx_invalid_not_enough_proofs>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_tx_invalid_empty_proofs : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_tx_invalid_empty_proofs>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_tx_invalid_empty_proofs>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_tx_invalid_too_many_proofs : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_tx_invalid_too_many_proofs>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_tx_invalid_too_many_proofs>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_tx_invalid_wrong_amount : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_tx_invalid_wrong_amount>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_tx_invalid_wrong_amount>: public get_bp_versioned_test_options<HF_VERSION_CLSAG> {};
 
 struct gen_bp_tx_invalid_borromean_type : public gen_bp_tx_validation_base
 {
   bool generate(std::vector<test_event_entry>& events) const;
 };
-template<> struct get_test_options<gen_bp_tx_invalid_borromean_type>: public get_test_options<gen_bp_tx_validation_base> {};
+template<> struct get_test_options<gen_bp_tx_invalid_borromean_type>: public get_bp_versioned_test_options<9> {};
+
+struct gen_bp_tx_invalid_bulletproof2_type : public gen_bp_tx_validation_base
+{
+  bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_bp_tx_invalid_bulletproof2_type>: public get_bp_versioned_test_options<HF_VERSION_CLSAG + 1> {};
diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp
index 9895d4814..c55154917 100644
--- a/tests/core_tests/chaingen_main.cpp
+++ b/tests/core_tests/chaingen_main.cpp
@@ -248,7 +248,8 @@ int main(int argc, char* argv[])
     GENERATE_AND_PLAY(gen_multisig_tx_invalid_48_1_no_signers);
     GENERATE_AND_PLAY(gen_multisig_tx_invalid_48_1_23_no_threshold);
 
-    GENERATE_AND_PLAY(gen_bp_tx_valid_1);
+    GENERATE_AND_PLAY(gen_bp_tx_valid_1_before_12);
+    GENERATE_AND_PLAY(gen_bp_tx_invalid_1_from_12);
     GENERATE_AND_PLAY(gen_bp_tx_invalid_1_1);
     GENERATE_AND_PLAY(gen_bp_tx_valid_2);
     GENERATE_AND_PLAY(gen_bp_tx_valid_3);
@@ -263,6 +264,9 @@ int main(int argc, char* argv[])
     GENERATE_AND_PLAY(gen_bp_tx_invalid_too_many_proofs);
     GENERATE_AND_PLAY(gen_bp_tx_invalid_wrong_amount);
     GENERATE_AND_PLAY(gen_bp_tx_invalid_borromean_type);
+    GENERATE_AND_PLAY(gen_bp_tx_invalid_bulletproof2_type);
+
+    GENERATE_AND_PLAY(gen_rct2_tx_clsag_malleability);
 
     GENERATE_AND_PLAY(gen_block_low_coinbase);
 
diff --git a/tests/core_tests/chaingen_tests_list.h b/tests/core_tests/chaingen_tests_list.h
index 94eb23ce9..db78c3e41 100644
--- a/tests/core_tests/chaingen_tests_list.h
+++ b/tests/core_tests/chaingen_tests_list.h
@@ -43,6 +43,7 @@
 #include "rct.h"
 #include "multisig.h"
 #include "bulletproofs.h"
+#include "rct2.h"
 /************************************************************************/
 /*                                                                      */
 /************************************************************************/
diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp
index 28d43e815..f098e1bce 100644
--- a/tests/core_tests/multisig.cpp
+++ b/tests/core_tests/multisig.cpp
@@ -163,9 +163,9 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
 
   MAKE_GENESIS_BLOCK(events, blk_0, miner_account[creator], ts_start);
 
-  // create 8 miner accounts, and have them mine the next 8 blocks
+  // create 16 miner accounts, and have them mine the next 16 blocks
   // they will have a coinbase with a single out that's pseudo rct
-  constexpr size_t n_coinbases = 8;
+  constexpr size_t n_coinbases = 16;
   cryptonote::account_base miner_accounts[n_coinbases];
   const cryptonote::block *prev_block = &blk_0;
   cryptonote::block blocks[n_coinbases];
@@ -175,7 +175,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
     account_base &account = n < inputs ? miner_account[creator] : miner_accounts[n];
     CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, account,
         test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs,
-        4, 4, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
+        10, 10, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
           crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4),
         false, "Failed to generate block");
     events.push_back(blocks[n]);
@@ -191,7 +191,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
       cryptonote::block blk;
       CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk, blk_last, miner_accounts[0],
           test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version | test_generator::bf_max_outs,
-          4, 4, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
+          10, 10, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
           crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 1, 4),
           false, "Failed to generate block");
       events.push_back(blk);
@@ -363,7 +363,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
 #endif
   std::vector<crypto::secret_key> additional_tx_secret_keys;
   auto sources_copy = sources;
-  r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, { rct::RangeProofBorromean, 0 }, msoutp);
+  r = construct_tx_and_get_tx_key(miner_account[creator].get_keys(), subaddresses, sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_secret_keys, true, { rct::RangeProofPaddedBulletproof, 2 }, msoutp);
   CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction");
 
 #ifndef NO_MULTISIG
@@ -453,7 +453,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
       crypto::secret_key scalar1;
       crypto::derivation_to_scalar(derivation, n, scalar1);
       rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
-      rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2);
+      rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2 || tx.rct_signatures.type == rct::RCTTypeCLSAG);
       rct::key C = tx.rct_signatures.outPk[n].mask;
       rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H);
       CHECK_AND_ASSERT_MES(rct::equalKeys(C, Ctmp), false, "Failed to decode amount");
@@ -476,196 +476,196 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry
 
 bool gen_multisig_tx_valid_22_1_2::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_22_1_2_many_inputs::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 4, mixin, amount_paid, true, 2, 2, 1, {2}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_22_2_1::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, true, 2, 2, 2, {1}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_33_1_23::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, true, 3, 3, 1, {2, 3}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_33_3_21::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, true, 3, 3, 3, {2, 1}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_23_1_2::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 1, {2}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_23_1_3::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 1, {3}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_23_2_1::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 2, {1}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_23_2_3::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, true, 2, 3, 2, {3}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_45_1_234::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, true, 4, 5, 1, {2, 3, 4}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_45_4_135_many_inputs::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 4, mixin, amount_paid, true, 4, 5, 4, {1, 3, 5}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_89_3_1245789::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, true, 8, 9, 3, {1, 2, 4, 5, 7, 8, 9}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_24_1_2::generate(std::vector<test_event_entry>& events) const
 {
-    const size_t mixin = 4;
+    const size_t mixin = 10;
     const uint64_t amount_paid = 10000;
     return generate_with(events, 2, mixin, amount_paid, true, 2, 4, 1, {2}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_24_1_2_many_inputs::generate(std::vector<test_event_entry>& events) const
 {
-    const size_t mixin = 4;
+    const size_t mixin = 10;
     const uint64_t amount_paid = 10000;
     return generate_with(events, 4, mixin, amount_paid, true, 2, 4, 1, {2}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_25_1_2::generate(std::vector<test_event_entry>& events) const
 {
-    const size_t mixin = 4;
+    const size_t mixin = 10;
     const uint64_t amount_paid = 10000;
     return generate_with(events, 2, mixin, amount_paid, true, 2, 5, 1, {2}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_25_1_2_many_inputs::generate(std::vector<test_event_entry>& events) const
 {
-    const size_t mixin = 4;
+    const size_t mixin = 10;
     const uint64_t amount_paid = 10000;
     return generate_with(events, 4, mixin, amount_paid, true, 2, 5, 1, {2}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_48_1_234::generate(std::vector<test_event_entry>& events) const
 {
-    const size_t mixin = 4;
+    const size_t mixin = 10;
     const uint64_t amount_paid = 10000;
     return generate_with(events, 2, mixin, amount_paid, true, 4, 8, 1, {2, 3, 4}, NULL, NULL);
 }
 
 bool gen_multisig_tx_valid_48_1_234_many_inputs::generate(std::vector<test_event_entry>& events) const
 {
-    const size_t mixin = 4;
+    const size_t mixin = 10;
     const uint64_t amount_paid = 10000;
     return generate_with(events, 4, mixin, amount_paid, true, 4, 8, 1, {2, 3, 4}, NULL, NULL);
 }
 
 bool gen_multisig_tx_invalid_22_1__no_threshold::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, false, 2, 2, 1, {}, NULL, NULL);
 }
 
 bool gen_multisig_tx_invalid_33_1__no_threshold::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {}, NULL, NULL);
 }
 
 bool gen_multisig_tx_invalid_33_1_2_no_threshold::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {2}, NULL, NULL);
 }
 
 bool gen_multisig_tx_invalid_33_1_3_no_threshold::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, false, 3, 3, 1, {3}, NULL, NULL);
 }
 
 bool gen_multisig_tx_invalid_23_1__no_threshold::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, false, 2, 3, 1, {}, NULL, NULL);
 }
 
 bool gen_multisig_tx_invalid_45_5_23_no_threshold::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, false, 4, 5, 5, {2, 3}, NULL, NULL);
 }
 
 bool gen_multisig_tx_invalid_24_1_no_signers::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, false, 2, 4, 1, {}, NULL, NULL);
 }
 
 bool gen_multisig_tx_invalid_25_1_no_signers::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, false, 2, 5, 1, {}, NULL, NULL);
 }
 
 bool gen_multisig_tx_invalid_48_1_no_signers::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, false, 4, 8, 1, {}, NULL, NULL);
 }
 
 bool gen_multisig_tx_invalid_48_1_23_no_threshold::generate(std::vector<test_event_entry>& events) const
 {
-  const size_t mixin = 4;
+  const size_t mixin = 10;
   const uint64_t amount_paid = 10000;
   return generate_with(events, 2, mixin, amount_paid, false, 4, 8, 1, {2, 3}, NULL, NULL);
 }
diff --git a/tests/core_tests/multisig.h b/tests/core_tests/multisig.h
index 462c74f46..333c4fe38 100644
--- a/tests/core_tests/multisig.h
+++ b/tests/core_tests/multisig.h
@@ -82,7 +82,7 @@ private:
 
 template<>
 struct get_test_options<gen_multisig_tx_validation_base> {
-  const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(4, 1), std::make_pair(0, 0)};
+  const std::pair<uint8_t, uint64_t> hard_forks[3] = {std::make_pair(1, 0), std::make_pair(10, 1), std::make_pair(0, 0)};
   const cryptonote::test_options test_options = {
     hard_forks, 0
   };
diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp
index 6bf708855..6ce99e76e 100644
--- a/tests/core_tests/rct.cpp
+++ b/tests/core_tests/rct.cpp
@@ -133,7 +133,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry
       crypto::secret_key amount_key;
       crypto::derivation_to_scalar(derivation, o, amount_key);
       const uint8_t type = rct_txes[n].rct_signatures.type;
-      if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2)
+      if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2 || type == rct::RCTTypeCLSAG)
         rct::decodeRctSimple(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4], hw::get_device("default"));
       else
         rct::decodeRct(rct_txes[n].rct_signatures, rct::sk2rct(amount_key), o, rct_tx_masks[o+n*4], hw::get_device("default"));
diff --git a/tests/core_tests/rct2.cpp b/tests/core_tests/rct2.cpp
new file mode 100644
index 000000000..55d700429
--- /dev/null
+++ b/tests/core_tests/rct2.cpp
@@ -0,0 +1,224 @@
+// Copyright (c) 2014-2019, The Monero Project
+// 
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+// 
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+//    conditions and the following disclaimer.
+// 
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+//    of conditions and the following disclaimer in the documentation and/or other
+//    materials provided with the distribution.
+// 
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+//    used to endorse or promote products derived from this software without specific
+//    prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// 
+// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+
+#include "ringct/rctSigs.h"
+#include "ringct/bulletproofs.h"
+#include "chaingen.h"
+#include "rct2.h"
+#include "device/device.hpp"
+
+using namespace epee;
+using namespace crypto;
+using namespace cryptonote;
+
+//----------------------------------------------------------------------------------------------------------------------
+// Tests
+
+bool gen_rct2_tx_validation_base::generate_with(std::vector<test_event_entry>& events,
+    size_t mixin, size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version,
+    const std::function<bool(std::vector<tx_source_entry> &sources, std::vector<tx_destination_entry> &destinations, size_t tx_idx)> &pre_tx,
+    const std::function<bool(transaction &tx, size_t tx_idx)> &post_tx) const
+{
+  uint64_t ts_start = 1338224400;
+
+  GENERATE_ACCOUNT(miner_account);
+  MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start);
+
+  // create 12 miner accounts, and have them mine the next 12 blocks
+  cryptonote::account_base miner_accounts[12];
+  const cryptonote::block *prev_block = &blk_0;
+  cryptonote::block blocks[12 + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW];
+  for (size_t n = 0; n < 12; ++n) {
+    miner_accounts[n].generate();
+    CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[n], *prev_block, miner_accounts[n],
+        test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version,
+        2, 2, prev_block->timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
+          crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 0, 2),
+        false, "Failed to generate block");
+    events.push_back(blocks[n]);
+    prev_block = blocks + n;
+    LOG_PRINT_L0("Initial miner tx " << n << ": " << obj_to_json_str(blocks[n].miner_tx));
+  }
+
+  // rewind
+  cryptonote::block blk_r, blk_last;
+  {
+    blk_last = blocks[11];
+    for (size_t i = 0; i < CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; ++i)
+    {
+      CHECK_AND_ASSERT_MES(generator.construct_block_manually(blocks[12+i], blk_last, miner_account,
+          test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_hf_version,
+          2, 2, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
+          crypto::hash(), 0, transaction(), std::vector<crypto::hash>(), 0, 0, 2),
+          false, "Failed to generate block");
+      events.push_back(blocks[12+i]);
+      blk_last = blocks[12+i];
+    }
+    blk_r = blk_last;
+  }
+
+  // create 4 txes from these miners in another block, to generate some rct outputs
+  std::vector<transaction> rct_txes;
+  cryptonote::block blk_txes;
+  std::vector<crypto::hash> starting_rct_tx_hashes;
+  static const uint64_t input_amounts_available[] = {5000000000000, 30000000000000, 100000000000, 80000000000};
+  for (size_t n = 0; n < n_txes; ++n)
+  {
+    std::vector<tx_source_entry> sources;
+
+    sources.resize(1);
+    tx_source_entry& src = sources.back();
+
+    const uint64_t needed_amount = input_amounts_available[n];
+    src.amount = input_amounts_available[n];
+    size_t real_index_in_tx = 0;
+    for (size_t m = 0; m <= mixin; ++m) {
+      size_t index_in_tx = 0;
+      for (size_t i = 0; i < blocks[m].miner_tx.vout.size(); ++i)
+        if (blocks[m].miner_tx.vout[i].amount == needed_amount)
+          index_in_tx = i;
+      CHECK_AND_ASSERT_MES(blocks[m].miner_tx.vout[index_in_tx].amount == needed_amount, false, "Expected amount not found");
+      src.push_output(m, boost::get<txout_to_key>(blocks[m].miner_tx.vout[index_in_tx].target).key, src.amount);
+      if (m == n)
+        real_index_in_tx = index_in_tx;
+    }
+    src.real_out_tx_key = cryptonote::get_tx_pub_key_from_extra(blocks[n].miner_tx);
+    src.real_output = n;
+    src.real_output_in_tx_index = real_index_in_tx;
+    src.mask = rct::identity();
+    src.rct = false;
+
+    //fill outputs entry
+    tx_destination_entry td;
+    td.addr = miner_accounts[n].get_keys().m_account_address;
+    std::vector<tx_destination_entry> destinations;
+    for (int o = 0; amounts_paid[o] != (uint64_t)-1; ++o)
+    {
+      td.amount = amounts_paid[o];
+      destinations.push_back(td);
+    }
+
+    if (pre_tx && !pre_tx(sources, destinations, n))
+    {
+      MDEBUG("pre_tx returned failure");
+      return false;
+    }
+
+    crypto::secret_key tx_key;
+    std::vector<crypto::secret_key> additional_tx_keys;
+    std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
+    subaddresses[miner_accounts[n].get_keys().m_account_address.m_spend_public_key] = {0,0};
+    rct_txes.resize(rct_txes.size() + 1);
+    bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes.back(), 0, tx_key, additional_tx_keys, true, rct_config[n]);
+    CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction");
+
+    if (post_tx && !post_tx(rct_txes.back(), n))
+    {
+      MDEBUG("post_tx returned failure");
+      return false;
+    }
+
+    //events.push_back(rct_txes.back());
+    starting_rct_tx_hashes.push_back(get_transaction_hash(rct_txes.back()));
+    LOG_PRINT_L0("Test tx: " << obj_to_json_str(rct_txes.back()));
+
+    for (int o = 0; amounts_paid[o] != (uint64_t)-1; ++o)
+    {
+      crypto::key_derivation derivation;
+      bool r = crypto::generate_key_derivation(destinations[o].addr.m_view_public_key, tx_key, derivation);
+      CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation");
+      crypto::secret_key amount_key;
+      crypto::derivation_to_scalar(derivation, o, amount_key);
+      rct::key rct_tx_mask;
+      const uint8_t type = rct_txes.back().rct_signatures.type;
+      if (type == rct::RCTTypeSimple || type == rct::RCTTypeBulletproof || type == rct::RCTTypeBulletproof2 || type == rct::RCTTypeCLSAG)
+        rct::decodeRctSimple(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
+      else
+        rct::decodeRct(rct_txes.back().rct_signatures, rct::sk2rct(amount_key), o, rct_tx_mask, hw::get_device("default"));
+    }
+
+    while (amounts_paid[0] != (size_t)-1)
+      ++amounts_paid;
+    ++amounts_paid;
+  }
+  if (!valid)
+    DO_CALLBACK(events, "mark_invalid_tx");
+  events.push_back(rct_txes);
+
+  CHECK_AND_ASSERT_MES(generator.construct_block_manually(blk_txes, blk_last, miner_account,
+      test_generator::bf_major_ver | test_generator::bf_minor_ver | test_generator::bf_timestamp | test_generator::bf_tx_hashes | test_generator::bf_hf_version | test_generator::bf_max_outs,
+      hf_version, hf_version, blk_last.timestamp + DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN * 2, // v2 has blocks twice as long
+      crypto::hash(), 0, transaction(), starting_rct_tx_hashes, 0, 6, 10),
+      false, "Failed to generate block");
+  if (!valid)
+    DO_CALLBACK(events, "mark_invalid_block");
+  events.push_back(blk_txes);
+  blk_last = blk_txes;
+
+  return true;
+}
+
+bool gen_rct2_tx_validation_base::check_bp(const cryptonote::transaction &tx, size_t tx_idx, const size_t *sizes, const char *context) const
+{
+  DEFINE_TESTS_ERROR_CONTEXT(context);
+  CHECK_TEST_CONDITION(tx.version >= 2);
+  CHECK_TEST_CONDITION(rct::is_rct_bulletproof(tx.rct_signatures.type));
+  size_t n_sizes = 0, n_amounts = 0;
+  for (size_t n = 0; n < tx_idx; ++n)
+  {
+    while (sizes[0] != (size_t)-1)
+      ++sizes;
+    ++sizes;
+  }
+  while (sizes[n_sizes] != (size_t)-1)
+    n_amounts += sizes[n_sizes++];
+  CHECK_TEST_CONDITION(tx.rct_signatures.p.bulletproofs.size() == n_sizes);
+  CHECK_TEST_CONDITION(rct::n_bulletproof_max_amounts(tx.rct_signatures.p.bulletproofs) == n_amounts);
+  for (size_t n = 0; n < n_sizes; ++n)
+    CHECK_TEST_CONDITION(rct::n_bulletproof_max_amounts(tx.rct_signatures.p.bulletproofs[n]) == sizes[n]);
+  return true;
+}
+
+bool gen_rct2_tx_clsag_malleability::generate(std::vector<test_event_entry>& events) const
+{
+  DEFINE_TESTS_ERROR_CONTEXT("gen_rct_tx_clsag_malleability");
+  const int mixin = 10;
+  const uint64_t amounts_paid[] = {5000, 5000, (uint64_t)-1};
+  const rct::RCTConfig rct_config[] = { { rct::RangeProofPaddedBulletproof, 3 } };
+  return generate_with(events, mixin, 1, amounts_paid, false, rct_config, HF_VERSION_CLSAG + 1, NULL, [&](cryptonote::transaction &tx, size_t tx_idx) {
+    CHECK_TEST_CONDITION(tx.version == 2);
+    CHECK_TEST_CONDITION(tx.rct_signatures.type == rct::RCTTypeCLSAG);
+    CHECK_TEST_CONDITION(!tx.rct_signatures.p.CLSAGs.empty());
+    rct::key x;
+    CHECK_TEST_CONDITION(epee::string_tools::hex_to_pod("c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", x));
+    tx.rct_signatures.p.CLSAGs[0].D = rct::addKeys(tx.rct_signatures.p.CLSAGs[0].D, x);
+    return true;
+  });
+}
diff --git a/tests/core_tests/rct2.h b/tests/core_tests/rct2.h
new file mode 100644
index 000000000..2fe9d6113
--- /dev/null
+++ b/tests/core_tests/rct2.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2014-2019, The Monero Project
+// 
+// All rights reserved.
+// 
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+// 
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+//    conditions and the following disclaimer.
+// 
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+//    of conditions and the following disclaimer in the documentation and/or other
+//    materials provided with the distribution.
+// 
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+//    used to endorse or promote products derived from this software without specific
+//    prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// 
+// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+
+#pragma once 
+#include "chaingen.h"
+
+struct gen_rct2_tx_validation_base : public test_chain_unit_base
+{
+  gen_rct2_tx_validation_base()
+    : m_invalid_tx_index(0)
+    , m_invalid_block_index(0)
+  {
+    REGISTER_CALLBACK_METHOD(gen_rct2_tx_validation_base, mark_invalid_tx);
+    REGISTER_CALLBACK_METHOD(gen_rct2_tx_validation_base, mark_invalid_block);
+  }
+
+  bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/)
+  {
+    if (m_invalid_tx_index == event_idx)
+      return tvc.m_verifivation_failed;
+    else
+      return !tvc.m_verifivation_failed && tx_added;
+  }
+
+  bool check_tx_verification_context_array(const std::vector<cryptonote::tx_verification_context>& tvcs, size_t tx_added, size_t event_idx, const std::vector<cryptonote::transaction>& /*txs*/)
+  {
+    size_t failed = 0;
+    for (const cryptonote::tx_verification_context &tvc: tvcs)
+      if (tvc.m_verifivation_failed)
+        ++failed;
+    if (m_invalid_tx_index == event_idx)
+      return failed > 0;
+    else
+      return failed == 0 && tx_added == tvcs.size();
+  }
+
+  bool check_block_verification_context(const cryptonote::block_verification_context& bvc, size_t event_idx, const cryptonote::block& /*block*/)
+  {
+    if (m_invalid_block_index == event_idx)
+      return bvc.m_verifivation_failed;
+    else
+      return !bvc.m_verifivation_failed;
+  }
+
+  bool mark_invalid_block(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
+  {
+    m_invalid_block_index = ev_index + 1;
+    return true;
+  }
+
+  bool mark_invalid_tx(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/)
+  {
+    m_invalid_tx_index = ev_index + 1;
+    return true;
+  }
+
+  bool generate_with(std::vector<test_event_entry>& events, size_t mixin,
+      size_t n_txes, const uint64_t *amounts_paid, bool valid, const rct::RCTConfig *rct_config, uint8_t hf_version,
+      const std::function<bool(std::vector<cryptonote::tx_source_entry> &sources, std::vector<cryptonote::tx_destination_entry> &destinations, size_t)> &pre_tx,
+      const std::function<bool(cryptonote::transaction &tx, size_t)> &post_tx) const;
+
+  bool check_bp(const cryptonote::transaction &tx, size_t tx_idx, const size_t *sizes, const char *context) const;
+
+private:
+  size_t m_invalid_tx_index;
+  size_t m_invalid_block_index;
+};
+
+template<>
+struct get_test_options<gen_rct2_tx_validation_base> {
+  const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(12, 73), std::make_pair(0, 0)};
+  const cryptonote::test_options test_options = {
+    hard_forks, 0
+  };
+};
+
+template<uint8_t test_version = 12>
+struct get_rct2_versioned_test_options: public get_test_options<gen_rct2_tx_validation_base> {
+  const std::pair<uint8_t, uint64_t> hard_forks[4] = {std::make_pair(1, 0), std::make_pair(2, 1), std::make_pair(test_version, 73), std::make_pair(0, 0)};
+  const cryptonote::test_options test_options = {
+    hard_forks, 0
+  };
+};
+
+struct gen_rct2_tx_clsag_malleability : public gen_rct2_tx_validation_base
+{
+  bool generate(std::vector<test_event_entry>& events) const;
+};
+template<> struct get_test_options<gen_rct2_tx_clsag_malleability>: public get_rct2_versioned_test_options<HF_VERSION_CLSAG + 1> {};
diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py
index 132758f50..fca9ce91c 100755
--- a/tests/functional_tests/transfer.py
+++ b/tests/functional_tests/transfer.py
@@ -135,7 +135,7 @@ class TransferTest():
         assert res.fee > 0
         fee = res.fee
         assert len(res.tx_blob) > 0
-        blob_size = len(res.tx_blob) // 2
+        tx_weight = res.weight
         assert len(res.tx_metadata) == 0
         assert len(res.multisig_txset) == 0
         assert len(res.unsigned_txset) == 0
@@ -144,7 +144,7 @@ class TransferTest():
         res = daemon.get_fee_estimate(10)
         assert res.fee > 0
         assert res.quantization_mask > 0
-        expected_fee = (res.fee * 1 * blob_size + res.quantization_mask - 1) // res.quantization_mask * res.quantization_mask
+        expected_fee = (res.fee * 1 * tx_weight + res.quantization_mask - 1) // res.quantization_mask * res.quantization_mask
         assert abs(1 - fee / expected_fee) < 0.01
 
         self.wallet[0].refresh()
diff --git a/tests/performance_tests/sig_clsag.h b/tests/performance_tests/sig_clsag.h
index 0873fa520..2f738679c 100644
--- a/tests/performance_tests/sig_clsag.h
+++ b/tests/performance_tests/sig_clsag.h
@@ -60,7 +60,7 @@ public:
     P[l] = rct::scalarmultBase(p);
     C[l] = rct::scalarmultBase(z);
     
-    sig = CLSAG_Gen(rct::identity(),P,p,C,z,l,NULL);
+    sig = CLSAG_Gen(rct::identity(),P,p,C,z,l);
 
     return true;
   }
@@ -70,7 +70,7 @@ public:
     if (ver)
       return CLSAG_Ver(rct::identity(),P,C,sig);
     else
-      CLSAG_Gen(rct::identity(),P,p,C,z,l,NULL);
+      CLSAG_Gen(rct::identity(),P,p,C,z,l);
     return true;
   }
 
diff --git a/tests/trezor/trezor_tests.cpp b/tests/trezor/trezor_tests.cpp
index f5867f5e7..6a92868cf 100644
--- a/tests/trezor/trezor_tests.cpp
+++ b/tests/trezor/trezor_tests.cpp
@@ -546,7 +546,7 @@ static void expand_tsx(cryptonote::transaction &tx)
     for (size_t n = 0; n < tx.vin.size(); ++n)
       rv.p.MGs[0].II[n] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image);
   }
-  else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2)
+  else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2 || rv.type == rct::RCTTypeCLSAG)
   {
     CHECK_AND_ASSERT_THROW_MES(rv.p.MGs.size() == tx.vin.size(), "Bad MGs size");
     for (size_t n = 0; n < tx.vin.size(); ++n)
diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp
index 807bab64a..75fe9ecfb 100644
--- a/tests/unit_tests/ringct.cpp
+++ b/tests/unit_tests/ringct.cpp
@@ -38,6 +38,7 @@
 #include "ringct/rctSigs.h"
 #include "ringct/rctOps.h"
 #include "device/device.hpp"
+#include "string_tools.h"
 
 using namespace std;
 using namespace crypto;
@@ -137,6 +138,169 @@ TEST(ringct, MG_sigs)
         ASSERT_FALSE(MLSAG_Ver(message, P, IIccss, R));
 }
 
+TEST(ringct, CLSAG)
+{
+  const size_t ring_size = 11;
+  const size_t idx = 5;
+  keyV P, C;
+  key p, z;
+  const key message = identity();
+  key backup;
+  clsag clsag;
+
+  for (size_t i = 0; i < ring_size; ++i)
+  {
+    key Sk, Pk;
+    skpkGen(Sk, Pk);
+    P.push_back(Pk);
+    skpkGen(Sk, Pk);
+    C.push_back(Pk);
+  }
+  skpkGen(p, P[idx]);
+  skpkGen(z, C[idx]);
+
+  // bad p at creation
+  clsag = CLSAG_Gen(zero(), P, p, C, z, idx); //, hw::get_device("default"));
+  ASSERT_FALSE(CLSAG_Ver(message, P, C, clsag));
+
+  // bad index at creation
+  try
+  {
+    clsag = CLSAG_Gen(message, P, p, C, z, (idx + 1) % ring_size); //, hw::get_device("default"));
+    ASSERT_FALSE(CLSAG_Ver(message, P, C, clsag));
+  }
+  catch (...) { /* either exception, or failure to verify above */ }
+
+  // bad z at creation
+  try
+  {
+    clsag = CLSAG_Gen(message, P, p, C, skGen(), idx); //, hw::get_device("default"));
+    ASSERT_FALSE(CLSAG_Ver(message, P, C, clsag));
+  }
+  catch (...) { /* either exception, or failure to verify above */ }
+
+  // bad C at creation
+  backup = C[idx];
+  C[idx] = scalarmultBase(skGen());
+  try
+  {
+    clsag = CLSAG_Gen(message, P, p, C, z, idx); //, hw::get_device("default"));
+    ASSERT_FALSE(CLSAG_Ver(message, P, C, clsag));
+  }
+  catch (...) { /* either exception, or failure to verify above */ }
+  C[idx] = backup;
+
+  // bad p at creation
+  try
+  {
+    clsag = CLSAG_Gen(message, P, skGen(), C, z, idx); //, hw::get_device("default"));
+    ASSERT_FALSE(CLSAG_Ver(message, P, C, clsag));
+  }
+  catch (...) { /* either exception, or failure to verify above */ }
+
+  // bad P at creation
+  backup = P[idx];
+  P[idx] = scalarmultBase(skGen());
+  try
+  {
+    clsag = CLSAG_Gen(message, P, p, C, z, idx); //, hw::get_device("default"));
+    ASSERT_FALSE(CLSAG_Ver(message, P, C, clsag));
+  }
+  catch (...) { /* either exception, or failure to verify above */ }
+  P[idx] = backup;
+
+  // good
+  clsag = CLSAG_Gen(message, P, p, C, z, idx); //, hw::get_device("default"));
+  ASSERT_TRUE(CLSAG_Ver(message, P, C, clsag));
+
+  // bad message at verification
+  ASSERT_FALSE(CLSAG_Ver(zero(), P, C, clsag));
+
+  // bad real P at verification
+  backup = P[idx];
+  P[idx] = scalarmultBase(skGen());
+  ASSERT_FALSE(CLSAG_Ver(zero(), P, C, clsag));
+  P[idx] = backup;
+
+  // bad fake P at verification
+  backup = P[(idx + 1) % ring_size];
+  P[(idx + 1) % ring_size] = scalarmultBase(skGen());
+  ASSERT_FALSE(CLSAG_Ver(zero(), P, C, clsag));
+  P[(idx + 1) % ring_size] = backup;
+
+  // bad real C at verification
+  backup = C[idx];
+  C[idx] = scalarmultBase(skGen());
+  ASSERT_FALSE(CLSAG_Ver(zero(), P, C, clsag));
+  C[idx] = backup;
+
+  // bad fake C at verification
+  backup = C[(idx + 1) % ring_size];
+  C[(idx + 1) % ring_size] = scalarmultBase(skGen());
+  ASSERT_FALSE(CLSAG_Ver(zero(), P, C, clsag));
+  C[(idx + 1) % ring_size] = backup;
+
+  // empty s
+  auto sbackup = clsag.s;
+  clsag.s.clear();
+  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
+  clsag.s = sbackup;
+
+  // too few s elements
+  backup = clsag.s.back();
+  clsag.s.pop_back();
+  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
+  clsag.s.push_back(backup);
+
+  // too many s elements
+  clsag.s.push_back(skGen());
+  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
+  clsag.s.pop_back();
+
+  // bad s in clsag at verification
+  for (auto &s: clsag.s)
+  {
+    backup = s;
+    s = skGen();
+    ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
+    s = backup;
+  }
+
+  // bad c1 in clsag at verification
+  backup = clsag.c1;
+  clsag.c1 = skGen();
+  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
+  clsag.c1 = backup;
+
+  // bad I in clsag at verification
+  backup = clsag.I;
+  clsag.I = scalarmultBase(skGen());
+  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
+  clsag.I = backup;
+
+  // bad D in clsag at verification
+  backup = clsag.D;
+  clsag.D = scalarmultBase(skGen());
+  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
+  clsag.D = backup;
+
+  // D not in main subgroup in clsag at verification
+  backup = clsag.D;
+  rct::key x;
+  ASSERT_TRUE(epee::string_tools::hex_to_pod("c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", x));
+  clsag.D = rct::addKeys(clsag.D, x);
+  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
+  clsag.D = backup;
+
+  // swapped I and D in clsag at verification
+  std::swap(clsag.I, clsag.D);
+  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
+  std::swap(clsag.I, clsag.D);
+
+  // check it's still good, in case we failed to restore
+  ASSERT_TRUE(CLSAG_Ver(message, P, C, clsag));
+}
+
 TEST(ringct, range_proofs)
 {
         //Ring CT Stuff
diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp
index e730f6867..7b8a291d0 100644
--- a/tests/unit_tests/serialization.cpp
+++ b/tests/unit_tests/serialization.cpp
@@ -477,6 +477,7 @@ TEST(Serialization, serializes_ringct_types)
   rct::ecdhTuple ecdh0, ecdh1;
   rct::boroSig boro0, boro1;
   rct::mgSig mg0, mg1;
+  rct::clsag clsag0, clsag1;
   rct::Bulletproof bp0, bp1;
   rct::rctSig s0, s1;
   cryptonote::transaction tx0, tx1;
@@ -592,9 +593,11 @@ TEST(Serialization, serializes_ringct_types)
   rct::skpkGen(Sk, Pk);
   destinations.push_back(Pk);
   //compute rct data with mixin 3
-  const rct::RCTConfig rct_config{ rct::RangeProofPaddedBulletproof, 0 };
+  const rct::RCTConfig rct_config{ rct::RangeProofPaddedBulletproof, 2 };
   s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config, hw::get_device("default"));
 
+  ASSERT_FALSE(s0.p.MGs.empty());
+  ASSERT_TRUE(s0.p.CLSAGs.empty());
   mg0 = s0.p.MGs[0];
   ASSERT_TRUE(serialization::dump_binary(mg0, blob));
   ASSERT_TRUE(serialization::parse_binary(blob, mg1));
@@ -614,6 +617,23 @@ TEST(Serialization, serializes_ringct_types)
   ASSERT_TRUE(serialization::parse_binary(blob, bp1));
   bp1.V = bp0.V; // this is not saved, as it is reconstructed from other tx data
   ASSERT_EQ(bp0, bp1);
+
+  const rct::RCTConfig rct_config_clsag{ rct::RangeProofPaddedBulletproof, 3 };
+  s0 = rct::genRctSimple(rct::zero(), sc, pc, destinations, inamounts, amounts, amount_keys, NULL, NULL, 0, 3, rct_config_clsag, hw::get_device("default"));
+
+  ASSERT_FALSE(s0.p.CLSAGs.empty());
+  ASSERT_TRUE(s0.p.MGs.empty());
+  clsag0 = s0.p.CLSAGs[0];
+  ASSERT_TRUE(serialization::dump_binary(clsag0, blob));
+  ASSERT_TRUE(serialization::parse_binary(blob, clsag1));
+  ASSERT_TRUE(clsag0.s.size() == clsag1.s.size());
+  for (size_t n = 0; n < clsag0.s.size(); ++n)
+  {
+    ASSERT_TRUE(clsag0.s[n] == clsag1.s[n]);
+  }
+  ASSERT_TRUE(clsag0.c1 == clsag1.c1);
+  // I is not serialized, they are meant to be reconstructed
+  ASSERT_TRUE(clsag0.D == clsag1.D);
 }
 
 TEST(Serialization, portability_wallet)

From 641b08c920f9c68d957e13147cf9c3e329cf83f1 Mon Sep 17 00:00:00 2001
From: Sarang Noether <32460187+SarangNoether@users.noreply.github.com>
Date: Tue, 10 Mar 2020 18:46:37 -0400
Subject: [PATCH 4/9] CLSAG optimizations

---
 src/ringct/rctSigs.cpp           | 264 ++++++++++++++++---------------
 src/ringct/rctSigs.h             |   7 +-
 tests/performance_tests/main.cpp |   2 -
 tests/unit_tests/ringct.cpp      | 166 ++++++++++---------
 4 files changed, 221 insertions(+), 218 deletions(-)

diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index 074812156..2e56dad58 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -168,12 +168,17 @@ namespace rct {
 
     // Generate a CLSAG signature
     // See paper by Goodell et al. (https://eprint.iacr.org/2019/654)
-    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout) {
+    //
+    // The keys are set as follows:
+    //   P[l] == p*G
+    //   C[l] == z*G
+    //   C[i] == C_nonzero[i] - C_offset (for hashing purposes) for all i
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout) {
         clsag sig;
         size_t n = P.size(); // ring size
         CHECK_AND_ASSERT_THROW_MES(n == C.size(), "Signing and commitment key vector sizes must match!");
+        CHECK_AND_ASSERT_THROW_MES(n == C_nonzero.size(), "Signing and commitment key vector sizes must match!");
         CHECK_AND_ASSERT_THROW_MES(l < n, "Signing index out of range!");
-        CHECK_AND_ASSERT_THROW_MES(scalarmultBase(z) == C[l], "C does not match z!");
         CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present");
         CHECK_AND_ASSERT_THROW_MES((mscout && mspout) || !kLRki, "Multisig pointers are not all present");
 
@@ -212,8 +217,8 @@ namespace rct {
         scalarmultKey(aH,H,a);
 
         // Aggregation hashes
-        keyV mu_P_to_hash(2*n+3); // domain, I, D, P, C
-        keyV mu_C_to_hash(2*n+3); // domain, I, D, P, C
+        keyV mu_P_to_hash(2*n+4); // domain, I, D, P, C, C_offset
+        keyV mu_C_to_hash(2*n+4); // domain, I, D, P, C, C_offset
         sc_0(mu_P_to_hash[0].bytes);
         memcpy(mu_P_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_0,sizeof(config::HASH_KEY_CLSAG_AGG_0)-1);
         sc_0(mu_C_to_hash[0].bytes);
@@ -223,40 +228,43 @@ namespace rct {
             mu_C_to_hash[i] = P[i-1];
         }
         for (size_t i = n+1; i < 2*n+1; ++i) {
-            mu_P_to_hash[i] = C[i-n-1];
-            mu_C_to_hash[i] = C[i-n-1];
+            mu_P_to_hash[i] = C_nonzero[i-n-1];
+            mu_C_to_hash[i] = C_nonzero[i-n-1];
         }
         mu_P_to_hash[2*n+1] = sig.I;
         mu_P_to_hash[2*n+2] = sig.D;
+        mu_P_to_hash[2*n+3] = C_offset;
         mu_C_to_hash[2*n+1] = sig.I;
         mu_C_to_hash[2*n+2] = sig.D;
+        mu_C_to_hash[2*n+3] = C_offset;
         key mu_P, mu_C;
         mu_P = hash_to_scalar(mu_P_to_hash);
         mu_C = hash_to_scalar(mu_C_to_hash);
 
         // Initial commitment
-        keyV c_to_hash(2*n+4); // domain, P, C, message, aG, aH
+        keyV c_to_hash(2*n+5); // domain, P, C, C_offset, message, aG, aH
         key c;
         sc_0(c_to_hash[0].bytes);
         memcpy(c_to_hash[0].bytes,config::HASH_KEY_CLSAG_ROUND,sizeof(config::HASH_KEY_CLSAG_ROUND)-1);
         for (size_t i = 1; i < n+1; ++i)
         {
             c_to_hash[i] = P[i-1];
-            c_to_hash[i+n] = C[i-1];
+            c_to_hash[i+n] = C_nonzero[i-1];
         }
-        c_to_hash[2*n+1] = message;
+        c_to_hash[2*n+1] = C_offset;
+        c_to_hash[2*n+2] = message;
 
         // Multisig data is present
         if (kLRki)
         {
             a = kLRki->k;
-            c_to_hash[2*n+2] = kLRki->L;
-            c_to_hash[2*n+3] = kLRki->R;
+            c_to_hash[2*n+3] = kLRki->L;
+            c_to_hash[2*n+4] = kLRki->R;
         }
         else
         {
-            c_to_hash[2*n+2] = aG;
-            c_to_hash[2*n+3] = aH;
+            c_to_hash[2*n+3] = aG;
+            c_to_hash[2*n+4] = aH;
         }
         c = hash_to_scalar(c_to_hash);
         
@@ -295,8 +303,8 @@ namespace rct {
             ge_dsm_precomp(H_precomp.k, &Hi_p3);
             addKeys_aAbBcC(R,sig.s[i],H_precomp.k,c_p,I_precomp.k,c_c,D_precomp.k);
 
-            c_to_hash[2*n+2] = L;
-            c_to_hash[2*n+3] = R;
+            c_to_hash[2*n+3] = L;
+            c_to_hash[2*n+4] = R;
             c_new = hash_to_scalar(c_to_hash);
             copy(c,c_new);
             
@@ -320,99 +328,8 @@ namespace rct {
         return sig;
     }
 
-    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l) {
-        return CLSAG_Gen(message, P, p, C, z, l, NULL, NULL, NULL);
-    }
-
-    // Verify a CLSAG signature
-    // See paper by Goodell et al. (https://eprint.iacr.org/2019/654)
-    bool CLSAG_Ver(const key &message, const keyV & P, const keyV & C, const clsag & sig)
-    {
-        size_t n = P.size(); // ring size
-        CHECK_AND_ASSERT_MES(n == C.size(), false, "Signing and commitment key vector sizes must match!");
-        CHECK_AND_ASSERT_MES(n == sig.s.size(), false, "Signature scalar vector is the wrong size!");
-        for (size_t i = 0; i < n; ++i)
-            CHECK_AND_ASSERT_MES(sc_check(sig.s[i].bytes) == 0, false, "Bad signature scalar!");
-        CHECK_AND_ASSERT_MES(sc_check(sig.c1.bytes) == 0, false, "Bad signature commitment!");
-
-        key c = copy(sig.c1);
-        key D_8 = scalarmult8(sig.D);
-        geDsmp I_precomp;
-        geDsmp D_precomp;
-        precomp(I_precomp.k,sig.I);
-        precomp(D_precomp.k,D_8);
-
-        // Aggregation hashes
-        keyV mu_P_to_hash(2*n+3); // domain, I, D, P, C
-        keyV mu_C_to_hash(2*n+3); // domain, I, D, P, C
-        sc_0(mu_P_to_hash[0].bytes);
-        memcpy(mu_P_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_0,sizeof(config::HASH_KEY_CLSAG_AGG_0)-1);
-        sc_0(mu_C_to_hash[0].bytes);
-        memcpy(mu_C_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_1,sizeof(config::HASH_KEY_CLSAG_AGG_1)-1);
-        for (size_t i = 1; i < n+1; ++i) {
-            mu_P_to_hash[i] = P[i-1];
-            mu_C_to_hash[i] = P[i-1];
-        }
-        for (size_t i = n+1; i < 2*n+1; ++i) {
-            mu_P_to_hash[i] = C[i-n-1];
-            mu_C_to_hash[i] = C[i-n-1];
-        }
-        mu_P_to_hash[2*n+1] = sig.I;
-        mu_P_to_hash[2*n+2] = sig.D;
-        mu_C_to_hash[2*n+1] = sig.I;
-        mu_C_to_hash[2*n+2] = sig.D;
-        key mu_P, mu_C;
-        mu_P = hash_to_scalar(mu_P_to_hash);
-        mu_C = hash_to_scalar(mu_C_to_hash);
-
-        keyV c_to_hash(2*n+4); // domain, P, C, message, L, R
-        sc_0(c_to_hash[0].bytes);
-        memcpy(c_to_hash[0].bytes,config::HASH_KEY_CLSAG_ROUND,sizeof(config::HASH_KEY_CLSAG_ROUND)-1);
-        for (size_t i = 1; i < n+1; ++i)
-        {
-            c_to_hash[i] = P[i-1];
-            c_to_hash[i+n] = C[i-1];
-        }
-        c_to_hash[2*n+1] = message;
-        key c_p; // = c[i]*mu_P
-        key c_c; // = c[i]*mu_C
-        key c_new;
-        key L;
-        key R;
-        geDsmp P_precomp;
-        geDsmp C_precomp;
-        geDsmp H_precomp;
-        size_t i = 0;
-        ge_p3 hash8_p3;
-        geDsmp hash_precomp;
-
-        while (i < n) {
-            sc_0(c_new.bytes);
-            sc_mul(c_p.bytes,mu_P.bytes,c.bytes);
-            sc_mul(c_c.bytes,mu_C.bytes,c.bytes);
-
-            // Precompute points
-            precomp(P_precomp.k,P[i]);
-            precomp(C_precomp.k,C[i]);
-
-            // Compute L
-            addKeys_aGbBcC(L,sig.s[i],c_p,P_precomp.k,c_c,C_precomp.k);
-
-            // Compute R
-            hash_to_p3(hash8_p3,P[i]);
-            ge_dsm_precomp(hash_precomp.k, &hash8_p3);
-            addKeys_aAbBcC(R,sig.s[i],hash_precomp.k,c_p,I_precomp.k,c_c,D_precomp.k);
-
-            c_to_hash[2*n+2] = L;
-            c_to_hash[2*n+3] = R;
-            c_new = hash_to_scalar(c_to_hash);
-            CHECK_AND_ASSERT_MES(!(c_new == rct::zero()), false, "Bad signature hash");
-            copy(c,c_new);
-
-            i = i + 1;
-        }
-        sc_sub(c_new.bytes,c.bytes,sig.c1.bytes);
-        return sc_isnonzero(c_new.bytes) == 0;
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l) {
+        return CLSAG_Gen(message, P, p, C, z, C_nonzero, C_offset, l, NULL, NULL, NULL);
     }
 
     // MLSAG signatures
@@ -816,12 +733,14 @@ namespace rct {
         size_t i;
         keyM M(cols, tmp);
 
-        keyV P, C;
+        keyV P, C, C_nonzero;
         P.reserve(pubs.size());
         C.reserve(pubs.size());
+        C_nonzero.reserve(pubs.size());
         for (const ctkey &k: pubs)
         {
             P.push_back(k.dest);
+            C_nonzero.push_back(k.mask);
             rct::key tmp;
             subKeys(tmp, k.mask, Cout);
             C.push_back(tmp);
@@ -829,7 +748,7 @@ namespace rct {
 
         sk[0] = copy(inSk.dest);
         sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes);
-        clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], index, kLRki, mscout, mspout);
+        clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], C_nonzero, Cout, index, kLRki, mscout, mspout);
         memwipe(sk.data(), sk.size() * sizeof(key));
         return result;
     }
@@ -913,29 +832,116 @@ namespace rct {
         catch (...) { return false; }
     }
 
-    bool verRctCLSAGSimple(const key &message, const clsag &clsag, const ctkeyV & pubs, const key & C) {
+    bool verRctCLSAGSimple(const key &message, const clsag &sig, const ctkeyV & pubs, const key & C_offset) {
         try
         {
             PERF_TIMER(verRctCLSAGSimple);
-            //setup vars
-            const size_t cols = pubs.size();
-            CHECK_AND_ASSERT_MES(cols >= 1, false, "Empty pubs");
-            keyV Pi(cols), Ci(cols);
-            ge_p3 Cp3;
-            CHECK_AND_ASSERT_MES_L1(ge_frombytes_vartime(&Cp3, C.bytes) == 0, false, "point conv failed");
-            ge_cached Ccached;
-            ge_p3_to_cached(&Ccached, &Cp3);
-            ge_p1p1 p1;
-            //create the matrix to mg sig
-            for (size_t i = 0; i < cols; i++) {
-                    Pi[i] = pubs[i].dest;
-                    ge_p3 p3;
-                    CHECK_AND_ASSERT_MES_L1(ge_frombytes_vartime(&p3, pubs[i].mask.bytes) == 0, false, "point conv failed");
-                    ge_sub(&p1, &p3, &Ccached);
-                    ge_p1p1_to_p3(&p3, &p1);
-                    ge_p3_tobytes(Ci[i].bytes, &p3);
+            const size_t n = pubs.size();
+
+            // Check data
+            CHECK_AND_ASSERT_MES(n >= 1, false, "Empty pubs");
+            CHECK_AND_ASSERT_MES(n == sig.s.size(), false, "Signature scalar vector is the wrong size!");
+            for (size_t i = 0; i < n; ++i)
+                CHECK_AND_ASSERT_MES(sc_check(sig.s[i].bytes) == 0, false, "Bad signature scalar!");
+            CHECK_AND_ASSERT_MES(sc_check(sig.c1.bytes) == 0, false, "Bad signature commitment!");
+            CHECK_AND_ASSERT_MES(!(sig.I == rct::identity()), false, "Bad key image!");
+
+            // Cache commitment offset for efficient subtraction later
+            ge_p3 C_offset_p3;
+            CHECK_AND_ASSERT_MES(ge_frombytes_vartime(&C_offset_p3, C_offset.bytes) == 0, false, "point conv failed");
+            ge_cached C_offset_cached;
+            ge_p3_to_cached(&C_offset_cached, &C_offset_p3);
+
+            // Prepare key images
+            key c = copy(sig.c1);
+            key D_8 = scalarmult8(sig.D);
+            CHECK_AND_ASSERT_MES(!(D_8 == rct::identity()), false, "Bad auxiliary key image!");
+            geDsmp I_precomp;
+            geDsmp D_precomp;
+            precomp(I_precomp.k,sig.I);
+            precomp(D_precomp.k,D_8);
+
+            // Aggregation hashes
+            keyV mu_P_to_hash(2*n+4); // domain, I, D, P, C, C_offset
+            keyV mu_C_to_hash(2*n+4); // domain, I, D, P, C, C_offset
+            sc_0(mu_P_to_hash[0].bytes);
+            memcpy(mu_P_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_0,sizeof(config::HASH_KEY_CLSAG_AGG_0)-1);
+            sc_0(mu_C_to_hash[0].bytes);
+            memcpy(mu_C_to_hash[0].bytes,config::HASH_KEY_CLSAG_AGG_1,sizeof(config::HASH_KEY_CLSAG_AGG_1)-1);
+            for (size_t i = 1; i < n+1; ++i) {
+                mu_P_to_hash[i] = pubs[i-1].dest;
+                mu_C_to_hash[i] = pubs[i-1].dest;
             }
-            return CLSAG_Ver(message, Pi, Ci, clsag);
+            for (size_t i = n+1; i < 2*n+1; ++i) {
+                mu_P_to_hash[i] = pubs[i-n-1].mask;
+                mu_C_to_hash[i] = pubs[i-n-1].mask;
+            }
+            mu_P_to_hash[2*n+1] = sig.I;
+            mu_P_to_hash[2*n+2] = sig.D;
+            mu_P_to_hash[2*n+3] = C_offset;
+            mu_C_to_hash[2*n+1] = sig.I;
+            mu_C_to_hash[2*n+2] = sig.D;
+            mu_C_to_hash[2*n+3] = C_offset;
+            key mu_P, mu_C;
+            mu_P = hash_to_scalar(mu_P_to_hash);
+            mu_C = hash_to_scalar(mu_C_to_hash);
+
+            // Set up round hash
+            keyV c_to_hash(2*n+5); // domain, P, C, C_offset, message, L, R
+            sc_0(c_to_hash[0].bytes);
+            memcpy(c_to_hash[0].bytes,config::HASH_KEY_CLSAG_ROUND,sizeof(config::HASH_KEY_CLSAG_ROUND)-1);
+            for (size_t i = 1; i < n+1; ++i)
+            {
+                c_to_hash[i] = pubs[i-1].dest;
+                c_to_hash[i+n] = pubs[i-1].mask;
+            }
+            c_to_hash[2*n+1] = C_offset;
+            c_to_hash[2*n+2] = message;
+            key c_p; // = c[i]*mu_P
+            key c_c; // = c[i]*mu_C
+            key c_new;
+            key L;
+            key R;
+            geDsmp P_precomp;
+            geDsmp C_precomp;
+            geDsmp H_precomp;
+            size_t i = 0;
+            ge_p3 hash8_p3;
+            geDsmp hash_precomp;
+            ge_p3 temp_p3;
+            ge_p1p1 temp_p1;
+
+            while (i < n) {
+                sc_0(c_new.bytes);
+                sc_mul(c_p.bytes,mu_P.bytes,c.bytes);
+                sc_mul(c_c.bytes,mu_C.bytes,c.bytes);
+
+                // Precompute points for L/R
+                precomp(P_precomp.k,pubs[i].dest);
+
+                CHECK_AND_ASSERT_MES(ge_frombytes_vartime(&temp_p3, pubs[i].mask.bytes) == 0, false, "point conv failed");
+                ge_sub(&temp_p1,&temp_p3,&C_offset_cached);
+                ge_p1p1_to_p3(&temp_p3,&temp_p1);
+                ge_dsm_precomp(C_precomp.k,&temp_p3);
+
+                // Compute L
+                addKeys_aGbBcC(L,sig.s[i],c_p,P_precomp.k,c_c,C_precomp.k);
+
+                // Compute R
+                hash_to_p3(hash8_p3,pubs[i].dest);
+                ge_dsm_precomp(hash_precomp.k, &hash8_p3);
+                addKeys_aAbBcC(R,sig.s[i],hash_precomp.k,c_p,I_precomp.k,c_c,D_precomp.k);
+
+                c_to_hash[2*n+3] = L;
+                c_to_hash[2*n+4] = R;
+                c_new = hash_to_scalar(c_to_hash);
+                CHECK_AND_ASSERT_MES(!(c_new == rct::zero()), false, "Bad signature hash");
+                copy(c,c_new);
+
+                i = i + 1;
+            }
+            sc_sub(c_new.bytes,c.bytes,sig.c1.bytes);
+            return sc_isnonzero(c_new.bytes) == 0;
         }
         catch (...) { return false; }
     }
diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h
index ed82f6bc5..199ad9aef 100644
--- a/src/ringct/rctSigs.h
+++ b/src/ringct/rctSigs.h
@@ -77,9 +77,10 @@ namespace rct {
     mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev);
     bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows);
 
-    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout);
-    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const unsigned int l);
-    bool CLSAG_Ver(const key &message, const keyV & P, const keyV & C, const clsag & sig);
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const keyV & C_nonzero, const key & C_offset, const key & z, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout);
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const keyV & C_nonzero, const key & C_offset, const key & z, const unsigned int l);
+    clsag proveRctCLSAGSimple(const key &, const ctkeyV &, const ctkey &, const key &, const key &, const multisig_kLRki *, key *, key *, unsigned int, hw::device &);
+    bool verRctCLSAGSimple(const key &, const clsag &, const ctkeyV &, const key &);
 
     //proveRange and verRange
     //proveRange gives C, and mask such that \sumCi = C
diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp
index b0cec464c..fde14b229 100644
--- a/tests/performance_tests/main.cpp
+++ b/tests/performance_tests/main.cpp
@@ -61,7 +61,6 @@
 #include "crypto_ops.h"
 #include "multiexp.h"
 #include "sig_mlsag.h"
-#include "sig_clsag.h"
 
 namespace po = boost::program_options;
 
@@ -216,7 +215,6 @@ int main(int argc, char** argv)
   TEST_PERFORMANCE1(filter, p, test_cn_fast_hash, 16384);
 
   TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 11, true); // MLSAG verification
-  TEST_PERFORMANCE3(filter, p, test_sig_clsag, 11, true, 0); // CLSAG verification
 
   TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, false);
   TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, true);
diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp
index 75fe9ecfb..2388d647b 100644
--- a/tests/unit_tests/ringct.cpp
+++ b/tests/unit_tests/ringct.cpp
@@ -140,165 +140,163 @@ TEST(ringct, MG_sigs)
 
 TEST(ringct, CLSAG)
 {
-  const size_t ring_size = 11;
+  const size_t N = 11;
   const size_t idx = 5;
-  keyV P, C;
-  key p, z;
+  ctkeyV pubs;
+  key p, t, t2, u;
   const key message = identity();
-  key backup;
+  ctkey backup;
   clsag clsag;
 
-  for (size_t i = 0; i < ring_size; ++i)
+  for (size_t i = 0; i < N; ++i)
   {
-    key Sk, Pk;
-    skpkGen(Sk, Pk);
-    P.push_back(Pk);
-    skpkGen(Sk, Pk);
-    C.push_back(Pk);
-  }
-  skpkGen(p, P[idx]);
-  skpkGen(z, C[idx]);
+    key sk;
+    ctkey tmp;
 
-  // bad p at creation
-  clsag = CLSAG_Gen(zero(), P, p, C, z, idx); //, hw::get_device("default"));
-  ASSERT_FALSE(CLSAG_Ver(message, P, C, clsag));
+    skpkGen(sk, tmp.dest);
+    skpkGen(sk, tmp.mask);
+
+    pubs.push_back(tmp);
+  }
+
+  // Set P[idx]
+  skpkGen(p, pubs[idx].dest);
+
+  // Set C[idx]
+  t = skGen();
+  u = skGen();
+  addKeys2(pubs[idx].mask,t,u,H);
+
+  // Set commitment offset
+  key Cout;
+  t2 = skGen();
+  addKeys2(Cout,t2,u,H);
+
+  // Prepare generation inputs
+  ctkey insk;
+  insk.dest = p;
+  insk.mask = t;
+  
+  // bad message
+  clsag = rct::proveRctCLSAGSimple(zero(),pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default"));
+  ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
 
   // bad index at creation
   try
   {
-    clsag = CLSAG_Gen(message, P, p, C, z, (idx + 1) % ring_size); //, hw::get_device("default"));
-    ASSERT_FALSE(CLSAG_Ver(message, P, C, clsag));
+    clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,(idx + 1) % N,hw::get_device("default"));
+    ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
   }
   catch (...) { /* either exception, or failure to verify above */ }
 
   // bad z at creation
   try
   {
-    clsag = CLSAG_Gen(message, P, p, C, skGen(), idx); //, hw::get_device("default"));
-    ASSERT_FALSE(CLSAG_Ver(message, P, C, clsag));
+    ctkey insk2;
+    insk2.dest = insk.dest;
+    insk2.mask = skGen();
+    clsag = rct::proveRctCLSAGSimple(message,pubs,insk2,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default"));
+    ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
   }
   catch (...) { /* either exception, or failure to verify above */ }
 
   // bad C at creation
-  backup = C[idx];
-  C[idx] = scalarmultBase(skGen());
+  backup = pubs[idx];
+  pubs[idx].mask = scalarmultBase(skGen());
   try
   {
-    clsag = CLSAG_Gen(message, P, p, C, z, idx); //, hw::get_device("default"));
-    ASSERT_FALSE(CLSAG_Ver(message, P, C, clsag));
+    clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default"));
+    ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
   }
   catch (...) { /* either exception, or failure to verify above */ }
-  C[idx] = backup;
+  pubs[idx] = backup;
 
   // bad p at creation
   try
   {
-    clsag = CLSAG_Gen(message, P, skGen(), C, z, idx); //, hw::get_device("default"));
-    ASSERT_FALSE(CLSAG_Ver(message, P, C, clsag));
+    ctkey insk2;
+    insk2.dest = skGen();
+    insk2.mask = insk.mask;
+    clsag = rct::proveRctCLSAGSimple(message,pubs,insk2,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default"));
+    ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
   }
   catch (...) { /* either exception, or failure to verify above */ }
 
   // bad P at creation
-  backup = P[idx];
-  P[idx] = scalarmultBase(skGen());
+  backup = pubs[idx];
+  pubs[idx].dest = scalarmultBase(skGen());
   try
   {
-    clsag = CLSAG_Gen(message, P, p, C, z, idx); //, hw::get_device("default"));
-    ASSERT_FALSE(CLSAG_Ver(message, P, C, clsag));
+    clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default"));
+    ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
   }
   catch (...) { /* either exception, or failure to verify above */ }
-  P[idx] = backup;
+  pubs[idx] = backup;
 
-  // good
-  clsag = CLSAG_Gen(message, P, p, C, z, idx); //, hw::get_device("default"));
-  ASSERT_TRUE(CLSAG_Ver(message, P, C, clsag));
-
-  // bad message at verification
-  ASSERT_FALSE(CLSAG_Ver(zero(), P, C, clsag));
-
-  // bad real P at verification
-  backup = P[idx];
-  P[idx] = scalarmultBase(skGen());
-  ASSERT_FALSE(CLSAG_Ver(zero(), P, C, clsag));
-  P[idx] = backup;
-
-  // bad fake P at verification
-  backup = P[(idx + 1) % ring_size];
-  P[(idx + 1) % ring_size] = scalarmultBase(skGen());
-  ASSERT_FALSE(CLSAG_Ver(zero(), P, C, clsag));
-  P[(idx + 1) % ring_size] = backup;
-
-  // bad real C at verification
-  backup = C[idx];
-  C[idx] = scalarmultBase(skGen());
-  ASSERT_FALSE(CLSAG_Ver(zero(), P, C, clsag));
-  C[idx] = backup;
-
-  // bad fake C at verification
-  backup = C[(idx + 1) % ring_size];
-  C[(idx + 1) % ring_size] = scalarmultBase(skGen());
-  ASSERT_FALSE(CLSAG_Ver(zero(), P, C, clsag));
-  C[(idx + 1) % ring_size] = backup;
+  // Test correct signature
+  clsag = rct::proveRctCLSAGSimple(message,pubs,insk,t2,Cout,NULL,NULL,NULL,idx,hw::get_device("default"));
+  ASSERT_TRUE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
 
   // empty s
   auto sbackup = clsag.s;
   clsag.s.clear();
-  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
+  ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
   clsag.s = sbackup;
 
   // too few s elements
-  backup = clsag.s.back();
+  key backup_key;
+  backup_key = clsag.s.back();
   clsag.s.pop_back();
-  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
-  clsag.s.push_back(backup);
+  ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
+  clsag.s.push_back(backup_key);
 
   // too many s elements
   clsag.s.push_back(skGen());
-  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
+  ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
   clsag.s.pop_back();
 
   // bad s in clsag at verification
   for (auto &s: clsag.s)
   {
-    backup = s;
+    backup_key = s;
     s = skGen();
-    ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
-    s = backup;
+    ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
+    s = backup_key;
   }
 
   // bad c1 in clsag at verification
-  backup = clsag.c1;
+  backup_key = clsag.c1;
   clsag.c1 = skGen();
-  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
-  clsag.c1 = backup;
+  ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
+  clsag.c1 = backup_key;
 
   // bad I in clsag at verification
-  backup = clsag.I;
+  backup_key = clsag.I;
   clsag.I = scalarmultBase(skGen());
-  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
-  clsag.I = backup;
+  ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
+  clsag.I = backup_key;
 
   // bad D in clsag at verification
-  backup = clsag.D;
+  backup_key = clsag.D;
   clsag.D = scalarmultBase(skGen());
-  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
-  clsag.D = backup;
+  ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
+  clsag.D = backup_key;
 
   // D not in main subgroup in clsag at verification
-  backup = clsag.D;
+  backup_key = clsag.D;
   rct::key x;
   ASSERT_TRUE(epee::string_tools::hex_to_pod("c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", x));
   clsag.D = rct::addKeys(clsag.D, x);
-  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
-  clsag.D = backup;
+  ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
+  clsag.D = backup_key;
 
   // swapped I and D in clsag at verification
   std::swap(clsag.I, clsag.D);
-  ASSERT_FALSE(CLSAG_Ver(identity(), P, C, clsag));
+  ASSERT_FALSE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
   std::swap(clsag.I, clsag.D);
 
   // check it's still good, in case we failed to restore
-  ASSERT_TRUE(CLSAG_Ver(message, P, C, clsag));
+  ASSERT_TRUE(rct::verRctCLSAGSimple(message,clsag,pubs,Cout));
 }
 
 TEST(ringct, range_proofs)

From 5aa1575e91459e0ed787ccc88d82dc9d86d8e21c Mon Sep 17 00:00:00 2001
From: Sarang Noether <32460187+SarangNoether@users.noreply.github.com>
Date: Wed, 11 Mar 2020 10:42:17 -0400
Subject: [PATCH 5/9] CLSAG verification performance test

---
 tests/performance_tests/main.cpp    |  2 +
 tests/performance_tests/sig_clsag.h | 60 ++++++++++++++++++-----------
 2 files changed, 40 insertions(+), 22 deletions(-)

diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp
index fde14b229..dcd10703c 100644
--- a/tests/performance_tests/main.cpp
+++ b/tests/performance_tests/main.cpp
@@ -61,6 +61,7 @@
 #include "crypto_ops.h"
 #include "multiexp.h"
 #include "sig_mlsag.h"
+#include "sig_clsag.h"
 
 namespace po = boost::program_options;
 
@@ -215,6 +216,7 @@ int main(int argc, char** argv)
   TEST_PERFORMANCE1(filter, p, test_cn_fast_hash, 16384);
 
   TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 11, true); // MLSAG verification
+  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 11, 1); // CLSAG verification (with commitment offset)
 
   TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, false);
   TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, true);
diff --git a/tests/performance_tests/sig_clsag.h b/tests/performance_tests/sig_clsag.h
index 2f738679c..255eac63b 100644
--- a/tests/performance_tests/sig_clsag.h
+++ b/tests/performance_tests/sig_clsag.h
@@ -32,14 +32,17 @@
 
 #include "ringct/rctSigs.h"
 #include "cryptonote_basic/cryptonote_basic.h"
+#include "device/device.hpp"
 
 #include "single_tx_test_base.h"
 
-template<size_t ring_size, bool ver, size_t index>
+using namespace rct;
+
+template<size_t ring_size, size_t index>
 class test_sig_clsag : public single_tx_test_base
 {
 public:
-  static const size_t n = ring_size;
+  static const size_t N = ring_size;
   static const size_t loop_count = 1000;
   static const size_t l = index;
 
@@ -48,36 +51,49 @@ public:
     if (!single_tx_test_base::init())
       return false;
 
-    p = rct::skGen();
-    z = rct::skGen();
-    P = rct::skvGen(n);
-    C = rct::skvGen(n);
-    for (size_t i = 0 ; i < n; i++)
+    message = identity();
+
+    pubs.reserve(N);
+    for (size_t i = 0; i < N; i++)
     {
-        P[i] = rct::scalarmultBase(P[i]);
-        C[i] = rct::scalarmultBase(C[i]);
+        key sk;
+        ctkey tmp;
+
+        skpkGen(sk, tmp.dest);
+        skpkGen(sk, tmp.mask);
+
+        pubs.push_back(tmp);
     }
-    P[l] = rct::scalarmultBase(p);
-    C[l] = rct::scalarmultBase(z);
+
+    key p;
+    skpkGen(p,pubs[l].dest);
     
-    sig = CLSAG_Gen(rct::identity(),P,p,C,z,l);
+    key t,u;
+    t = skGen();
+    u = skGen();
+    addKeys2(pubs[l].mask,t,u,H);
+
+    key t2;
+    t2 = skGen();
+    addKeys2(C_offset,t2,u,H);
+
+    ctkey insk;
+    insk.dest = p;
+    insk.mask = t;
+
+    sig = proveRctCLSAGSimple(message,pubs,insk,t2,C_offset,NULL,NULL,NULL,l,hw::get_device("default"));
 
     return true;
   }
 
   bool test()
   {
-    if (ver)
-      return CLSAG_Ver(rct::identity(),P,C,sig);
-    else
-      CLSAG_Gen(rct::identity(),P,p,C,z,l);
-    return true;
+    return verRctCLSAGSimple(message,sig,pubs,C_offset);
   }
 
 private:
-  rct::key p;
-  rct::key z;
-  rct::keyV P;
-  rct::keyV C;
-  rct::clsag sig;
+  ctkeyV pubs;
+  key C_offset;
+  clsag sig;
+  key message;
 };

From f964a92c57d53e6ae38e664a178c9b7f56dcfd4a Mon Sep 17 00:00:00 2001
From: Sarang Noether <32460187+SarangNoether@users.noreply.github.com>
Date: Tue, 21 Apr 2020 00:19:13 -0400
Subject: [PATCH 6/9] Updated MLSAG and CLSAG tests for consistency

---
 tests/performance_tests/main.cpp    | 14 +++++-
 tests/performance_tests/sig_clsag.h |  7 ++-
 tests/performance_tests/sig_mlsag.h | 73 ++++++++++++++++++-----------
 3 files changed, 63 insertions(+), 31 deletions(-)

diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp
index dcd10703c..675a8c19f 100644
--- a/tests/performance_tests/main.cpp
+++ b/tests/performance_tests/main.cpp
@@ -215,8 +215,18 @@ int main(int argc, char** argv)
   TEST_PERFORMANCE1(filter, p, test_cn_fast_hash, 32);
   TEST_PERFORMANCE1(filter, p, test_cn_fast_hash, 16384);
 
-  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 11, true); // MLSAG verification
-  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 11, 1); // CLSAG verification (with commitment offset)
+  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 8, 1); // MLSAG verification
+  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 16, 1);
+  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 32, 1);
+  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 64, 1);
+  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 128, 1);
+  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 256, 1);
+  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 8, 1); // CLSAG verification
+  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 16, 1);
+  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 32, 1);
+  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 64, 1);
+  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 128, 1);
+  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 256, 1);
 
   TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, false);
   TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, true);
diff --git a/tests/performance_tests/sig_clsag.h b/tests/performance_tests/sig_clsag.h
index 255eac63b..c15668adb 100644
--- a/tests/performance_tests/sig_clsag.h
+++ b/tests/performance_tests/sig_clsag.h
@@ -51,8 +51,9 @@ public:
     if (!single_tx_test_base::init())
       return false;
 
-    message = identity();
+    message = skGen();
 
+    // Random signing/commitment keys
     pubs.reserve(N);
     for (size_t i = 0; i < N; i++)
     {
@@ -65,18 +66,22 @@ public:
         pubs.push_back(tmp);
     }
 
+    // Signing key
     key p;
     skpkGen(p,pubs[l].dest);
     
+    // Commitment key
     key t,u;
     t = skGen();
     u = skGen();
     addKeys2(pubs[l].mask,t,u,H);
 
+    // Offset
     key t2;
     t2 = skGen();
     addKeys2(C_offset,t2,u,H);
 
+    // Final signing keys
     ctkey insk;
     insk.dest = p;
     insk.mask = t;
diff --git a/tests/performance_tests/sig_mlsag.h b/tests/performance_tests/sig_mlsag.h
index fc987fcf5..c9745b504 100644
--- a/tests/performance_tests/sig_mlsag.h
+++ b/tests/performance_tests/sig_mlsag.h
@@ -32,56 +32,73 @@
 
 #include "ringct/rctSigs.h"
 #include "cryptonote_basic/cryptonote_basic.h"
+#include "device/device.hpp"
 
 #include "single_tx_test_base.h"
 
-template<size_t ring_size, bool ver>
+using namespace rct;
+
+template<size_t ring_size, size_t index>
 class test_sig_mlsag : public single_tx_test_base
 {
 public:
-  static const size_t cols = ring_size;
-  static const size_t rows = 2; // 1 spend + 1 commitment
+  static const size_t N = ring_size;
   static const size_t loop_count = 1000;
+  static const size_t l = index;
 
   bool init()
   {
     if (!single_tx_test_base::init())
       return false;
 
-    rct::keyV xtmp = rct::skvGen(rows);
-    rct::keyM xm = rct::keyMInit(rows, cols);// = [[None]*N] #just used to generate test public keys
-    sk = rct::skvGen(rows);
-    P  = rct::keyMInit(rows, cols);// = keyM[[None]*N] #stores the public keys;
-    ind = 0; // fixed spend index
-    for (size_t j = 0 ; j < rows ; j++)
+    message = skGen();
+
+    // Random signing/commitment keys
+    pubs.reserve(N);
+    for (size_t i = 0; i < N; i++)
     {
-        for (size_t i = 0 ; i < cols ; i++)
-        {
-            xm[i][j] = rct::skGen();
-            P[i][j] = rct::scalarmultBase(xm[i][j]);
-        }
+        key sk;
+        ctkey tmp;
+
+        skpkGen(sk, tmp.dest);
+        skpkGen(sk, tmp.mask);
+
+        pubs.push_back(tmp);
     }
-    for (size_t j = 0 ; j < rows ; j++)
-    {
-        sk[j] = xm[ind][j];
-    }
-    IIccss = MLSAG_Gen(rct::identity(), P, sk, NULL, NULL, ind, rows-1, hw::get_device("default"));
+
+    // Signing key
+    key p;
+    skpkGen(p,pubs[l].dest);
+
+    // Commitment key
+    key t,u;
+    t = skGen();
+    u = skGen();
+    addKeys2(pubs[l].mask,t,u,H);
+
+    // Offset
+    key t2;
+    t2 = skGen();
+    addKeys2(C_offset,t2,u,H);
+
+    // Final signing keys
+    ctkey insk;
+    insk.dest = p;
+    insk.mask = t;
+
+    sig = proveRctMGSimple(message,pubs,insk,t2,C_offset,NULL,NULL,l,hw::get_device("default"));
 
     return true;
   }
 
   bool test()
   {
-    if (ver)
-      return MLSAG_Ver(rct::identity(), P, IIccss, rows-1);
-    else
-      MLSAG_Gen(rct::identity(), P, sk, NULL, NULL, ind, rows-1, hw::get_device("default"));
-    return true;
+      return verRctMGSimple(message,sig,pubs,C_offset);
   }
 
 private:
-  rct::keyV sk;
-  rct::keyM P;
-  size_t ind;
-  rct::mgSig IIccss;
+  ctkeyV pubs;
+  key C_offset;
+  mgSig sig;
+  key message;
 };

From aff87b5f6a3eb1f41b5fb31db9b1ccb8568cc340 Mon Sep 17 00:00:00 2001
From: Sarang Noether <32460187+SarangNoether@users.noreply.github.com>
Date: Tue, 12 May 2020 13:53:10 -0400
Subject: [PATCH 7/9] Added balance check to MLSAG/CLSAG performance tests

---
 tests/performance_tests/main.cpp    |  26 ++--
 tests/performance_tests/sig_clsag.h | 176 +++++++++++++++++++---------
 tests/performance_tests/sig_mlsag.h | 174 ++++++++++++++++++---------
 3 files changed, 257 insertions(+), 119 deletions(-)

diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp
index 675a8c19f..e59bb52fd 100644
--- a/tests/performance_tests/main.cpp
+++ b/tests/performance_tests/main.cpp
@@ -215,18 +215,20 @@ int main(int argc, char** argv)
   TEST_PERFORMANCE1(filter, p, test_cn_fast_hash, 32);
   TEST_PERFORMANCE1(filter, p, test_cn_fast_hash, 16384);
 
-  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 8, 1); // MLSAG verification
-  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 16, 1);
-  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 32, 1);
-  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 64, 1);
-  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 128, 1);
-  TEST_PERFORMANCE2(filter, p, test_sig_mlsag, 256, 1);
-  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 8, 1); // CLSAG verification
-  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 16, 1);
-  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 32, 1);
-  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 64, 1);
-  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 128, 1);
-  TEST_PERFORMANCE2(filter, p, test_sig_clsag, 256, 1);
+  TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 4, 2, 2); // MLSAG verification
+  TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 8, 2, 2);
+  TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 16, 2, 2);
+  TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 32, 2, 2);
+  TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 64, 2, 2);
+  TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 128, 2, 2);
+  TEST_PERFORMANCE3(filter, p, test_sig_mlsag, 256, 2, 2);
+  TEST_PERFORMANCE3(filter, p, test_sig_clsag, 4, 2, 2); // CLSAG verification
+  TEST_PERFORMANCE3(filter, p, test_sig_clsag, 8, 2, 2);
+  TEST_PERFORMANCE3(filter, p, test_sig_clsag, 16, 2, 2);
+  TEST_PERFORMANCE3(filter, p, test_sig_clsag, 32, 2, 2);
+  TEST_PERFORMANCE3(filter, p, test_sig_clsag, 64, 2, 2);
+  TEST_PERFORMANCE3(filter, p, test_sig_clsag, 128, 2, 2);
+  TEST_PERFORMANCE3(filter, p, test_sig_clsag, 256, 2, 2);
 
   TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, false);
   TEST_PERFORMANCE2(filter, p, test_ringct_mlsag, 11, true);
diff --git a/tests/performance_tests/sig_clsag.h b/tests/performance_tests/sig_clsag.h
index c15668adb..c59e1e869 100644
--- a/tests/performance_tests/sig_clsag.h
+++ b/tests/performance_tests/sig_clsag.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2014-2019, The Monero Project
+// Copyright (c) 2014-2020, The Monero Project
 // 
 // All rights reserved.
 // 
@@ -31,74 +31,142 @@
 #pragma once
 
 #include "ringct/rctSigs.h"
-#include "cryptonote_basic/cryptonote_basic.h"
+#include "ringct/rctTypes.h"
 #include "device/device.hpp"
 
-#include "single_tx_test_base.h"
-
 using namespace rct;
 
-template<size_t ring_size, size_t index>
-class test_sig_clsag : public single_tx_test_base
+template<size_t a_N, size_t a_T, size_t a_w>
+class test_sig_clsag
 {
-public:
-  static const size_t N = ring_size;
-  static const size_t loop_count = 1000;
-  static const size_t l = index;
+    public:
+        static const size_t loop_count = 1000;
+        static const size_t N = a_N;
+        static const size_t T = a_T;
+        static const size_t w = a_w;
 
-  bool init()
-  {
-    if (!single_tx_test_base::init())
-      return false;
+        bool init()
+        {
+            pubs.reserve(N);
+            pubs.resize(N);
 
-    message = skGen();
+            r = keyV(w); // M[l[u]] = Com(0,r[u])
 
-    // Random signing/commitment keys
-    pubs.reserve(N);
-    for (size_t i = 0; i < N; i++)
-    {
-        key sk;
-        ctkey tmp;
+            a = keyV(w); // P[l[u]] = Com(a[u],s[u])
+            s = keyV(w);
 
-        skpkGen(sk, tmp.dest);
-        skpkGen(sk, tmp.mask);
+            Q = keyV(T); // Q[j] = Com(b[j],t[j])
+            b = keyV(T);
+            t = keyV(T);
 
-        pubs.push_back(tmp);
-    }
+            // Random keys
+            key temp;
+            for (size_t k = 0; k < N; k++)
+            {
+                skpkGen(temp,pubs[k].dest);
+                skpkGen(temp,pubs[k].mask);
+            }
 
-    // Signing key
-    key p;
-    skpkGen(p,pubs[l].dest);
-    
-    // Commitment key
-    key t,u;
-    t = skGen();
-    u = skGen();
-    addKeys2(pubs[l].mask,t,u,H);
+            // Signing and commitment keys (assumes fixed signing indices 0,1,...,w-1 for this test)
+            // TODO: random signing indices
+            C_offsets = keyV(w); // P[l[u]] - C_offsets[u] = Com(0,s[u]-s1[u])
+            s1 = keyV(w);
+            key a_sum = zero();
+            key s1_sum = zero();
+            messages = keyV(w);
+            for (size_t u = 0; u < w; u++)
+            {
+                skpkGen(r[u],pubs[u].dest); // M[u] = Com(0,r[u])
 
-    // Offset
-    key t2;
-    t2 = skGen();
-    addKeys2(C_offset,t2,u,H);
+                a[u] = skGen(); // P[u] = Com(a[u],s[u])
+                s[u] = skGen();
+                addKeys2(pubs[u].mask,s[u],a[u],H);
 
-    // Final signing keys
-    ctkey insk;
-    insk.dest = p;
-    insk.mask = t;
+                s1[u] = skGen(); // C_offsets[u] = Com(a[u],s1[u])
+                addKeys2(C_offsets[u],s1[u],a[u],H);
 
-    sig = proveRctCLSAGSimple(message,pubs,insk,t2,C_offset,NULL,NULL,NULL,l,hw::get_device("default"));
+                sc_add(a_sum.bytes,a_sum.bytes,a[u].bytes);
+                sc_add(s1_sum.bytes,s1_sum.bytes,s1[u].bytes);
 
-    return true;
-  }
+                messages[u] = skGen();
+            }
 
-  bool test()
-  {
-    return verRctCLSAGSimple(message,sig,pubs,C_offset);
-  }
+            // Outputs
+            key b_sum = zero();
+            key t_sum = zero();
+            for (size_t j = 0; j < T-1; j++)
+            {
+                b[j] = skGen(); // Q[j] = Com(b[j],t[j])
+                t[j] = skGen();
+                addKeys2(Q[j],t[j],b[j],H);
 
-private:
-  ctkeyV pubs;
-  key C_offset;
-  clsag sig;
-  key message;
+                sc_add(b_sum.bytes,b_sum.bytes,b[j].bytes);
+                sc_add(t_sum.bytes,t_sum.bytes,t[j].bytes);
+            }
+            // Value/mask balance for Q[T-1]
+            sc_sub(b[T-1].bytes,a_sum.bytes,b_sum.bytes);
+            sc_sub(t[T-1].bytes,s1_sum.bytes,t_sum.bytes);
+            addKeys2(Q[T-1],t[T-1],b[T-1],H);
+
+            // Build proofs
+            sigs.reserve(w);
+            sigs.resize(0);
+            ctkey sk;
+            for (size_t u = 0; u < w; u++)
+            {
+                sk.dest = r[u];
+                sk.mask = s[u];
+
+                sigs.push_back(proveRctCLSAGSimple(messages[u],pubs,sk,s1[u],C_offsets[u],NULL,NULL,NULL,u,hw::get_device("default")));
+            }
+
+            return true;
+        }
+
+        bool test()
+        {
+            for (size_t u = 0; u < w; u++)
+            {
+                if (!verRctCLSAGSimple(messages[u],sigs[u],pubs,C_offsets[u]))
+                {
+                    return false;
+                }
+            }
+
+            // Check balanace
+            std::vector<MultiexpData> balance;
+            balance.reserve(w + T);
+            balance.resize(0);
+            key ZERO = zero();
+            key ONE = identity();
+            key MINUS_ONE;
+            sc_sub(MINUS_ONE.bytes,ZERO.bytes,ONE.bytes);
+            for (size_t u = 0; u < w; u++)
+            {
+                balance.push_back({ONE,C_offsets[u]});
+            }
+            for (size_t j = 0; j < T; j++)
+            {
+                balance.push_back({MINUS_ONE,Q[j]});
+            }
+            if (!(straus(balance) == ONE)) // group identity
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+    private:
+        ctkeyV pubs;
+        keyV Q;
+        keyV r;
+        keyV s;
+        keyV s1;
+        keyV t;
+        keyV a;
+        keyV b;
+        keyV C_offsets;
+        keyV messages;
+        std::vector<clsag> sigs;
 };
diff --git a/tests/performance_tests/sig_mlsag.h b/tests/performance_tests/sig_mlsag.h
index c9745b504..89645e155 100644
--- a/tests/performance_tests/sig_mlsag.h
+++ b/tests/performance_tests/sig_mlsag.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2014-2019, The Monero Project
+// Copyright (c) 2014-2020, The Monero Project
 // 
 // All rights reserved.
 // 
@@ -31,74 +31,142 @@
 #pragma once
 
 #include "ringct/rctSigs.h"
-#include "cryptonote_basic/cryptonote_basic.h"
+#include "ringct/rctTypes.h"
 #include "device/device.hpp"
 
-#include "single_tx_test_base.h"
-
 using namespace rct;
 
-template<size_t ring_size, size_t index>
-class test_sig_mlsag : public single_tx_test_base
+template<size_t a_N, size_t a_T, size_t a_w>
+class test_sig_mlsag
 {
-public:
-  static const size_t N = ring_size;
-  static const size_t loop_count = 1000;
-  static const size_t l = index;
+    public:
+        static const size_t loop_count = 1000;
+        static const size_t N = a_N;
+        static const size_t T = a_T;
+        static const size_t w = a_w;
 
-  bool init()
-  {
-    if (!single_tx_test_base::init())
-      return false;
+        bool init()
+        {
+            pubs.reserve(N);
+            pubs.resize(N);
 
-    message = skGen();
+            r = keyV(w); // M[l[u]] = Com(0,r[u])
 
-    // Random signing/commitment keys
-    pubs.reserve(N);
-    for (size_t i = 0; i < N; i++)
-    {
-        key sk;
-        ctkey tmp;
+            a = keyV(w); // P[l[u]] = Com(a[u],s[u])
+            s = keyV(w);
 
-        skpkGen(sk, tmp.dest);
-        skpkGen(sk, tmp.mask);
+            Q = keyV(T); // Q[j] = Com(b[j],t[j])
+            b = keyV(T);
+            t = keyV(T);
 
-        pubs.push_back(tmp);
-    }
+            // Random keys
+            key temp;
+            for (size_t k = 0; k < N; k++)
+            {
+                skpkGen(temp,pubs[k].dest);
+                skpkGen(temp,pubs[k].mask);
+            }
 
-    // Signing key
-    key p;
-    skpkGen(p,pubs[l].dest);
+            // Signing and commitment keys (assumes fixed signing indices 0,1,...,w-1 for this test)
+            // TODO: random signing indices
+            C_offsets = keyV(w); // P[l[u]] - C_offsets[u] = Com(0,s[u]-s1[u])
+            s1 = keyV(w);
+            key a_sum = zero();
+            key s1_sum = zero();
+            messages = keyV(w);
+            for (size_t u = 0; u < w; u++)
+            {
+                skpkGen(r[u],pubs[u].dest); // M[u] = Com(0,r[u])
 
-    // Commitment key
-    key t,u;
-    t = skGen();
-    u = skGen();
-    addKeys2(pubs[l].mask,t,u,H);
+                a[u] = skGen(); // P[u] = Com(a[u],s[u])
+                s[u] = skGen();
+                addKeys2(pubs[u].mask,s[u],a[u],H);
 
-    // Offset
-    key t2;
-    t2 = skGen();
-    addKeys2(C_offset,t2,u,H);
+                s1[u] = skGen(); // C_offsets[u] = Com(a[u],s1[u])
+                addKeys2(C_offsets[u],s1[u],a[u],H);
 
-    // Final signing keys
-    ctkey insk;
-    insk.dest = p;
-    insk.mask = t;
+                sc_add(a_sum.bytes,a_sum.bytes,a[u].bytes);
+                sc_add(s1_sum.bytes,s1_sum.bytes,s1[u].bytes);
 
-    sig = proveRctMGSimple(message,pubs,insk,t2,C_offset,NULL,NULL,l,hw::get_device("default"));
+                messages[u] = skGen();
+            }
 
-    return true;
-  }
+            // Outputs
+            key b_sum = zero();
+            key t_sum = zero();
+            for (size_t j = 0; j < T-1; j++)
+            {
+                b[j] = skGen(); // Q[j] = Com(b[j],t[j])
+                t[j] = skGen();
+                addKeys2(Q[j],t[j],b[j],H);
 
-  bool test()
-  {
-      return verRctMGSimple(message,sig,pubs,C_offset);
-  }
+                sc_add(b_sum.bytes,b_sum.bytes,b[j].bytes);
+                sc_add(t_sum.bytes,t_sum.bytes,t[j].bytes);
+            }
+            // Value/mask balance for Q[T-1]
+            sc_sub(b[T-1].bytes,a_sum.bytes,b_sum.bytes);
+            sc_sub(t[T-1].bytes,s1_sum.bytes,t_sum.bytes);
+            addKeys2(Q[T-1],t[T-1],b[T-1],H);
 
-private:
-  ctkeyV pubs;
-  key C_offset;
-  mgSig sig;
-  key message;
+            // Build proofs
+            sigs.reserve(w);
+            sigs.resize(0);
+            ctkey sk;
+            for (size_t u = 0; u < w; u++)
+            {
+                sk.dest = r[u];
+                sk.mask = s[u];
+
+                sigs.push_back(proveRctMGSimple(messages[u],pubs,sk,s1[u],C_offsets[u],NULL,NULL,u,hw::get_device("default")));
+            }
+
+            return true;
+        }
+
+        bool test()
+        {
+            for (size_t u = 0; u < w; u++)
+            {
+                if (!verRctMGSimple(messages[u],sigs[u],pubs,C_offsets[u]))
+                {
+                    return false;
+                }
+            }
+
+            // Check balanace
+            std::vector<MultiexpData> balance;
+            balance.reserve(w + T);
+            balance.resize(0);
+            key ZERO = zero();
+            key ONE = identity();
+            key MINUS_ONE;
+            sc_sub(MINUS_ONE.bytes,ZERO.bytes,ONE.bytes);
+            for (size_t u = 0; u < w; u++)
+            {
+                balance.push_back({ONE,C_offsets[u]});
+            }
+            for (size_t j = 0; j < T; j++)
+            {
+                balance.push_back({MINUS_ONE,Q[j]});
+            }
+            if (!(straus(balance) == ONE)) // group identity
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+    private:
+        ctkeyV pubs;
+        keyV Q;
+        keyV r;
+        keyV s;
+        keyV s1;
+        keyV t;
+        keyV a;
+        keyV b;
+        keyV C_offsets;
+        keyV messages;
+        std::vector<mgSig> sigs;
 };

From 703944c4d4faf6ad86a69ec3808a87b20ce76a32 Mon Sep 17 00:00:00 2001
From: Sarang Noether <32460187+SarangNoether@users.noreply.github.com>
Date: Fri, 27 Mar 2020 15:29:32 -0400
Subject: [PATCH 8/9] CLSAG device support

---
 src/device/device.hpp         |  4 ++++
 src/device/device_default.cpp | 23 +++++++++++++++++++++++
 src/device/device_default.hpp |  4 ++++
 src/ringct/rctSigs.cpp        | 33 ++++++++++++++-------------------
 src/ringct/rctSigs.h          |  4 ++--
 5 files changed, 47 insertions(+), 21 deletions(-)

diff --git a/src/device/device.hpp b/src/device/device.hpp
index ef973c9f4..582eb2242 100644
--- a/src/device/device.hpp
+++ b/src/device/device.hpp
@@ -231,6 +231,10 @@ namespace hw {
         virtual bool  mlsag_hash(const rct::keyV &long_message, rct::key &c) = 0;
         virtual bool  mlsag_sign(const rct::key &c, const rct::keyV &xx, const rct::keyV &alpha, const size_t rows, const size_t dsRows, rct::keyV &ss) = 0;
 
+        virtual bool clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) = 0;
+        virtual bool clsag_hash(const rct::keyV &data, rct::key &hash) = 0;
+        virtual bool clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) = 0;
+
         virtual bool  close_tx(void) = 0;
 
         virtual bool  has_ki_cold_sync(void) const { return false; }
diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp
index 096cb35ba..145197212 100644
--- a/src/device/device_default.cpp
+++ b/src/device/device_default.cpp
@@ -402,6 +402,29 @@ namespace hw {
             return true;
         }
 
+        bool device_default::clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) {
+            rct::skpkGen(a,aG); // aG = a*G
+            rct::scalarmultKey(aH,H,a); // aH = a*H
+            rct::scalarmultKey(I,H,p); // I = p*H
+            rct::scalarmultKey(D,H,z); // D = z*H
+            return true;
+        }
+
+        bool device_default::clsag_hash(const rct::keyV &data, rct::key &hash) {
+            hash = rct::hash_to_scalar(data);
+            return true;
+        }
+
+        bool device_default::clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) {
+            rct::key s0_p_mu_P;
+            sc_mul(s0_p_mu_P.bytes,mu_P.bytes,p.bytes);
+            rct::key s0_add_z_mu_C;
+            sc_muladd(s0_add_z_mu_C.bytes,mu_C.bytes,z.bytes,s0_p_mu_P.bytes);
+            sc_mulsub(s.bytes,c.bytes,s0_add_z_mu_C.bytes,a.bytes);
+
+            return true;
+        }
+
         bool device_default::close_tx() {
             return true;
         }
diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp
index bdd99f89c..2493bd67d 100644
--- a/src/device/device_default.hpp
+++ b/src/device/device_default.hpp
@@ -134,6 +134,10 @@ namespace hw {
             bool  mlsag_hash(const rct::keyV &long_message, rct::key &c) override;
             bool  mlsag_sign(const rct::key &c, const rct::keyV &xx, const rct::keyV &alpha, const size_t rows, const size_t dsRows, rct::keyV &ss) override;
 
+            bool clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) override;
+            bool clsag_hash(const rct::keyV &data, rct::key &hash) override;
+            bool clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) override;
+
             bool  close_tx(void) override;
         };
 
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index 2e56dad58..2a7b36b66 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -173,7 +173,7 @@ namespace rct {
     //   P[l] == p*G
     //   C[l] == z*G
     //   C[i] == C_nonzero[i] - C_offset (for hashing purposes) for all i
-    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout) {
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout, hw::device &hwdev) {
         clsag sig;
         size_t n = P.size(); // ring size
         CHECK_AND_ASSERT_THROW_MES(n == C.size(), "Signing and commitment key vector sizes must match!");
@@ -189,16 +189,21 @@ namespace rct {
         ge_p3_tobytes(H.bytes,&H_p3);
 
         key D;
-        scalarmultKey(D,H,z);
+
+        // Initial values
+        key a;
+        key aG;
+        key aH;
 
         // Multisig
         if (kLRki)
         {
             sig.I = kLRki->ki;
+            scalarmultKey(D,H,z);
         }
         else
         {
-            scalarmultKey(sig.I,H,p);
+            hwdev.clsag_prepare(p,z,sig.I,D,H,a,aG,aH);
         }
 
         geDsmp I_precomp;
@@ -209,13 +214,6 @@ namespace rct {
         // Offset key image
         scalarmultKey(sig.D,D,INV_EIGHT);
 
-        // Initial values
-        key a;
-        key aG;
-        key aH;
-        skpkGen(a,aG);
-        scalarmultKey(aH,H,a);
-
         // Aggregation hashes
         keyV mu_P_to_hash(2*n+4); // domain, I, D, P, C, C_offset
         keyV mu_C_to_hash(2*n+4); // domain, I, D, P, C, C_offset
@@ -266,7 +264,7 @@ namespace rct {
             c_to_hash[2*n+3] = aG;
             c_to_hash[2*n+4] = aH;
         }
-        c = hash_to_scalar(c_to_hash);
+        hwdev.clsag_hash(c_to_hash,c);
         
         size_t i;
         i = (l + 1) % n;
@@ -305,7 +303,7 @@ namespace rct {
 
             c_to_hash[2*n+3] = L;
             c_to_hash[2*n+4] = R;
-            c_new = hash_to_scalar(c_to_hash);
+            hwdev.clsag_hash(c_to_hash,c_new);
             copy(c,c_new);
             
             i = (i + 1) % n;
@@ -314,11 +312,8 @@ namespace rct {
         }
 
         // Compute final scalar
-        key s0_p_mu_P;
-        sc_mul(s0_p_mu_P.bytes,mu_P.bytes,p.bytes);
-        key s0_add_z_mu_C;
-        sc_muladd(s0_add_z_mu_C.bytes,mu_C.bytes,z.bytes,s0_p_mu_P.bytes);
-        sc_mulsub(sig.s[l].bytes,c.bytes,s0_add_z_mu_C.bytes,a.bytes);
+        hwdev.clsag_sign(c,a,p,z,mu_P,mu_C,sig.s[l]);
+        memwipe(&a, sizeof(key));
 
         if (mscout)
           *mscout = c;
@@ -329,7 +324,7 @@ namespace rct {
     }
 
     clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l) {
-        return CLSAG_Gen(message, P, p, C, z, C_nonzero, C_offset, l, NULL, NULL, NULL);
+        return CLSAG_Gen(message, P, p, C, z, C_nonzero, C_offset, l, NULL, NULL, NULL, hw::get_device("default"));
     }
 
     // MLSAG signatures
@@ -748,7 +743,7 @@ namespace rct {
 
         sk[0] = copy(inSk.dest);
         sc_sub(sk[1].bytes, inSk.mask.bytes, a.bytes);
-        clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], C_nonzero, Cout, index, kLRki, mscout, mspout);
+        clsag result = CLSAG_Gen(message, P, sk[0], C, sk[1], C_nonzero, Cout, index, kLRki, mscout, mspout, hwdev);
         memwipe(sk.data(), sk.size() * sizeof(key));
         return result;
     }
diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h
index 199ad9aef..a0346b34e 100644
--- a/src/ringct/rctSigs.h
+++ b/src/ringct/rctSigs.h
@@ -77,8 +77,8 @@ namespace rct {
     mgSig MLSAG_Gen(const key &message, const keyM & pk, const keyV & xx, const multisig_kLRki *kLRki, key *mscout, const unsigned int index, size_t dsRows, hw::device &hwdev);
     bool MLSAG_Ver(const key &message, const keyM &pk, const mgSig &sig, size_t dsRows);
 
-    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const keyV & C_nonzero, const key & C_offset, const key & z, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout);
-    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const keyV & C_nonzero, const key & C_offset, const key & z, const unsigned int l);
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l, const multisig_kLRki *kLRki, key *mscout, key *mspout, hw::device &hwdev);
+    clsag CLSAG_Gen(const key &message, const keyV & P, const key & p, const keyV & C, const key & z, const keyV & C_nonzero, const key & C_offset, const unsigned int l);
     clsag proveRctCLSAGSimple(const key &, const ctkeyV &, const ctkey &, const key &, const key &, const multisig_kLRki *, key *, key *, unsigned int, hw::device &);
     bool verRctCLSAGSimple(const key &, const clsag &, const ctkeyV &, const key &);
 

From 1660fe8a2508cef20dc3f5cb53c88bf17ca3e3ed Mon Sep 17 00:00:00 2001
From: cslashm <cslashm@gmail.com>
Date: Tue, 31 Mar 2020 17:11:51 +0200
Subject: [PATCH 9/9] draft support of clsag

---
 src/device/device_ledger.cpp | 152 +++++++++++++++++++++++++++++++++++
 src/device/device_ledger.hpp |   5 ++
 2 files changed, 157 insertions(+)

diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp
index c59b648bd..4e89f835d 100644
--- a/src/device/device_ledger.cpp
+++ b/src/device/device_ledger.cpp
@@ -299,6 +299,7 @@ namespace hw {
     #define INS_PREFIX_HASH                     0x7D
     #define INS_VALIDATE                        0x7C
     #define INS_MLSAG                           0x7E
+    #define INS_CLSAG                           0x7F
     #define INS_CLOSE_TX                        0x80
 
     #define INS_GET_TX_PROOF                    0xA0
@@ -2121,6 +2122,157 @@ namespace hw {
         return true;
     }
 
+    bool device_ledger::clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) {
+        AUTO_LOCK_CMD();
+        #ifdef DEBUG_HWDEVICE
+        const rct::key p_x   = hw::ledger::decrypt(p);
+        const rct::key z_x   = hw::ledger::decrypt(z);
+        rct::key       I_x;
+        rct::key       D_x;
+        const rct::key H_x   = H;
+        rct::key       a_x;
+        rct::key       aG_x;
+        rct::key       aH_x;
+        this->controle_device->clsag_prepare(p_x, z_x, I_x, D_x, H_x, a_x, aG_x, aH_x);
+        #endif
+
+        /*
+        rct::skpkGen(a,aG); // aG = a*G
+        rct::scalarmultKey(aH,H,a); // aH = a*H
+        rct::scalarmultKey(I,H,p); // I = p*H
+        rct::scalarmultKey(D,H,z); // D = z*H
+        */
+        int offset = set_command_header_noopt(INS_CLSAG, 0x01);
+        //p
+        this->send_secret(p.bytes, offset);
+        //z
+        this->send_secret(z.bytes, offset);
+        //H
+        memmove(this->buffer_send+offset, H.bytes, 32);
+        offset += 32;
+
+        this->buffer_send[4] = offset-5;
+        this->length_send = offset;
+        this->exchange();
+
+        offset = 0;
+        //a
+        this->receive_secret(a.bytes, offset);
+        //aG
+        memmove(aG.bytes, this->buffer_recv+offset, 32);
+        offset +=32;
+        //aH
+        memmove(aH.bytes, this->buffer_recv+offset, 32);
+        offset +=32;
+        //I = pH
+        memmove(I.bytes, this->buffer_recv+offset, 32);
+        offset +=32;
+        //D = zH
+        memmove(D.bytes, this->buffer_recv+offset, 32);
+        offset +=32;
+
+        #ifdef DEBUG_HWDEVICE
+        hw::ledger::check32("clsag_prepare", "I", (char*)I_x.bytes, (char*)I.bytes);
+        hw::ledger::check32("clsag_prepare", "D", (char*)D_x.bytes, (char*)D.bytes);
+        hw::ledger::check32("clsag_prepare", "a", (char*)a_x.bytes, (char*)a.bytes);
+        hw::ledger::check32("clsag_prepare", "aG", (char*)aG_x.bytes, (char*)aG.bytes);
+        hw::ledger::check32("clsag_prepare", "aH", (char*)aH_x.bytes, (char*)aH.bytes);
+        #endif
+
+        return true;
+    }
+
+    bool device_ledger::clsag_hash(const rct::keyV &data, rct::key &hash) {
+        AUTO_LOCK_CMD();
+
+        #ifdef DEBUG_HWDEVICE
+        const rct::keyV data_x  = data;
+        rct::key        hash_x;
+        this->controle_device->mlsag_hash(data_x, hash_x);
+        #endif
+
+        size_t cnt;
+        int offset;
+
+        cnt = data.size();
+        for (size_t i = 0; i<cnt; i++) {
+          offset = set_command_header(INS_CLSAG, 0x02, i+1);
+          //options
+          this->buffer_send[offset] = (i==(cnt-1))?0x00:0x80;  //last
+          offset += 1;
+          //msg part
+          memmove(this->buffer_send+offset, data[i].bytes, 32);
+          offset += 32;
+
+          this->buffer_send[4] = offset-5;
+          this->length_send = offset;
+          this->exchange();
+        }
+
+        //c/hash
+        memmove(hash.bytes, &this->buffer_recv[0], 32);
+
+        #ifdef DEBUG_HWDEVICE
+        hw::ledger::check32("mlsag_hash", "hash", (char*)hash_x.bytes, (char*)hash.bytes);
+        #endif
+        return true;
+    }
+
+    bool device_ledger::clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) {
+        AUTO_LOCK_CMD();
+
+        #ifdef DEBUG_HWDEVICE
+        const rct::key c_x    = c;
+        const rct::key a_x    = hw::ledger::decrypt(a);
+        const rct::key p_x    = hw::ledger::decrypt(p);
+        const rct::key z_x    = hw::ledger::decrypt(z);
+        const rct::key mu_P_x = mu_P;
+        const rct::key mu_C_x = mu_C;
+        rct::key       s_x;
+        this->controle_device->clsag_sign(c_x, a_x, p_x, z_x, mu_P_x, mu_C_x, s_x);
+        #endif
+
+        /*
+        rct::key s0_p_mu_P;
+        sc_mul(s0_p_mu_P.bytes,mu_P.bytes,p.bytes);
+        rct::key s0_add_z_mu_C;
+        sc_muladd(s0_add_z_mu_C.bytes,mu_C.bytes,z.bytes,s0_p_mu_P.bytes);
+        sc_mulsub(s.bytes,c.bytes,s0_add_z_mu_C.bytes,a.bytes);
+        */
+
+        int offset = set_command_header_noopt(INS_CLSAG, 0x03);
+
+        //c
+        //discard, unse internal one
+        //a
+        this->send_secret(a.bytes, offset);
+        //p
+        this->send_secret(p.bytes, offset);
+        //z
+        this->send_secret(z.bytes, offset);
+        //mu_P
+        memmove(this->buffer_send+offset, mu_P.bytes, 32);
+        offset += 32;
+        //mu_C
+        memmove(this->buffer_send+offset, mu_C.bytes, 32);
+        offset += 32;
+
+        this->buffer_send[4] = offset-5;
+        this->length_send = offset;
+        this->exchange();
+
+        offset = 0;
+        //s
+        memmove(s.bytes, this->buffer_recv+offset, 32);
+
+        #ifdef DEBUG_HWDEVICE
+        hw::ledger::check32("clsag_sign", "s", (char*)s_x.bytes, (char*)s.bytes);
+        #endif
+
+        return true;
+    }
+
+
     bool device_ledger::close_tx() {
         AUTO_LOCK_CMD();
         send_simple(INS_CLOSE_TX);
diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp
index 4036035c8..d3ec08288 100644
--- a/src/device/device_ledger.hpp
+++ b/src/device/device_ledger.hpp
@@ -297,6 +297,11 @@ namespace hw {
         bool  mlsag_hash(const rct::keyV &long_message, rct::key &c) override;
         bool  mlsag_sign( const rct::key &c, const rct::keyV &xx, const rct::keyV &alpha, const size_t rows, const size_t dsRows, rct::keyV &ss) override;
 
+        bool clsag_prepare(const rct::key &p, const rct::key &z, rct::key &I, rct::key &D, const rct::key &H, rct::key &a, rct::key &aG, rct::key &aH) override;
+        bool clsag_hash(const rct::keyV &data, rct::key &hash) override;
+        bool clsag_sign(const rct::key &c, const rct::key &a, const rct::key &p, const rct::key &z, const rct::key &mu_P, const rct::key &mu_C, rct::key &s) override;
+
+
         bool  close_tx(void) override;
 
     };