aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/crypto/crypto-ops.c100
-rw-r--r--src/crypto/crypto-ops.h2
-rw-r--r--src/cryptonote_basic/cryptonote_boost_serialization.h27
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.cpp9
-rw-r--r--src/cryptonote_config.h4
-rw-r--r--src/cryptonote_core/blockchain.cpp56
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp3
-rw-r--r--src/device/device.hpp4
-rw-r--r--src/device/device_default.cpp23
-rw-r--r--src/device/device_default.hpp4
-rw-r--r--src/device/device_ledger.cpp158
-rw-r--r--src/device/device_ledger.hpp5
-rw-r--r--src/device_trezor/trezor/protocol.hpp2
-rw-r--r--src/hardforks/hardforks.cpp5
-rw-r--r--src/ringct/rctOps.cpp17
-rw-r--r--src/ringct/rctOps.h4
-rw-r--r--src/ringct/rctSigs.cpp421
-rw-r--r--src/ringct/rctSigs.h6
-rw-r--r--src/ringct/rctTypes.cpp2
-rw-r--r--src/ringct/rctTypes.h150
-rw-r--r--src/rpc/core_rpc_server.cpp2
-rw-r--r--src/wallet/api/wallet.cpp1
-rw-r--r--src/wallet/wallet2.cpp94
-rw-r--r--src/wallet/wallet2.h2
24 files changed, 995 insertions, 106 deletions
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_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
@@ -245,6 +244,15 @@ namespace boost
}
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)
{
a & x.mask;
@@ -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 8051ee9fa..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
@@ -226,6 +227,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/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.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/device/device_ledger.cpp b/src/device/device_ledger.cpp
index 30964848d..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
@@ -1857,7 +1858,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 +1875,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 +1896,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;
@@ -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;
};
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/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..2a7b36b66 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,167 @@ 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)
+ //
+ // 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, 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!");
+ 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((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;
+ hash_to_p3(H_p3,P[l]);
+ key H;
+ ge_p3_tobytes(H.bytes,&H_p3);
+
+ key D;
+
+ // Initial values
+ key a;
+ key aG;
+ key aH;
+
+ // Multisig
+ if (kLRki)
+ {
+ sig.I = kLRki->ki;
+ scalarmultKey(D,H,z);
+ }
+ else
+ {
+ hwdev.clsag_prepare(p,z,sig.I,D,H,a,aG,aH);
+ }
+
+ 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);
+
+ // 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] = 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_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+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_nonzero[i-1];
+ }
+ 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+3] = kLRki->L;
+ c_to_hash[2*n+4] = kLRki->R;
+ }
+ else
+ {
+ c_to_hash[2*n+3] = aG;
+ c_to_hash[2*n+4] = aH;
+ }
+ hwdev.clsag_hash(c_to_hash,c);
+
+ 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+3] = L;
+ c_to_hash[2*n+4] = R;
+ hwdev.clsag_hash(c_to_hash,c_new);
+ copy(c,c_new);
+
+ i = (i + 1) % n;
+ if (i == 0)
+ copy(sig.c1,c);
+ }
+
+ // Compute final scalar
+ hwdev.clsag_sign(c,a,p,z,mu_P,mu_C,sig.s[l]);
+ memwipe(&a, sizeof(key));
+
+ 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 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, hw::get_device("default"));
+ }
+
// MLSAG signatures
// See paper by Noether (https://eprint.iacr.org/2015/1098)
// This generalization allows for some dimensions not to require linkability;
@@ -427,7 +589,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)
@@ -555,6 +717,37 @@ 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, 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);
+ }
+
+ 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, hwdev);
+ memwipe(sk.data(), sk.size() * sizeof(key));
+ return result;
+ }
+
//Ring-ct MG sigs
//Prove:
@@ -634,6 +827,120 @@ namespace rct {
catch (...) { return false; }
}
+ bool verRctCLSAGSimple(const key &message, const clsag &sig, const ctkeyV & pubs, const key & C_offset) {
+ try
+ {
+ PERF_TIMER(verRctCLSAGSimple);
+ 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;
+ }
+ 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; }
+ }
+
//These functions get keys from blockchain
//replace these when connecting blockchain
@@ -726,7 +1033,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
@@ -774,7 +1081,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)
@@ -864,7 +1191,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
@@ -874,7 +1201,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++) {
@@ -888,9 +1218,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;
}
@@ -995,13 +1336,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
@@ -1095,7 +1445,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
@@ -1118,14 +1468,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;
}
}
@@ -1162,7 +1517,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;
@@ -1186,13 +1541,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;
@@ -1215,12 +1570,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");
@@ -1230,6 +1586,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);
@@ -1237,4 +1595,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 9227eab1e..a0346b34e 100644
--- a/src/ringct/rctSigs.h
+++ b/src/ringct/rctSigs.h
@@ -76,7 +76,11 @@ 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);
- //mgSig MLSAG_Gen_Old(const keyM & pk, const keyV & xx, const int index);
+
+ 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 &);
//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 ce11981ad..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()
};
@@ -163,6 +168,23 @@ 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)
+ // FIELD(I) - not serialized, it can be reconstructed
+ FIELD(D)
+ END_SERIALIZE()
+ };
+
//contains the data for an Borromean sig
// also contains the "Ci" values such that
// \sum Ci = C
@@ -234,6 +256,7 @@ namespace rct {
RCTTypeSimple = 2,
RCTTypeBulletproof = 3,
RCTTypeBulletproof2 = 4,
+ RCTTypeCLSAG = 5,
};
enum RangeProofType { RangeProofBorromean, RangeProofBulletproof, RangeProofMultiOutputBulletproof, RangeProofPaddedBulletproof };
struct RCTConfig {
@@ -262,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
@@ -291,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())
@@ -338,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
@@ -346,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)
@@ -386,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();
+ 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();
+ if (mg_elements - i > 1)
+ ar.delimit_array();
+ }
+ ar.end_array();
}
- ar.end_array();
- if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2)
+ if (type == RCTTypeBulletproof || type == RCTTypeBulletproof2 || type == RCTTypeCLSAG)
{
ar.tag("pseudoOuts");
ar.begin_array();
@@ -464,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()
@@ -636,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);
@@ -652,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");
@@ -668,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();