From 960c380727e43ba900a219ab876cb017c8f8762b Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Tue, 7 Dec 2021 12:41:51 -0600 Subject: [PATCH 001/197] Squash --- src/Makefile.am | 41 +- src/Makefile.test.include | 13 +- src/libspark/aead.cpp | 89 +++ src/libspark/aead.h | 29 + src/libspark/bpplus.cpp | 473 ++++++++++++++++ src/libspark/bpplus.h | 33 ++ src/libspark/bpplus_proof.h | 44 ++ src/libspark/chaum.cpp | 163 ++++++ src/libspark/chaum.h | 45 ++ src/libspark/chaum_proof.h | 32 ++ src/libspark/coin.cpp | 154 +++++ src/libspark/coin.h | 111 ++++ src/libspark/grootle.cpp | 564 +++++++++++++++++++ src/libspark/grootle.h | 53 ++ src/libspark/grootle_proof.h | 45 ++ src/libspark/hash.cpp | 148 +++++ src/libspark/hash.h | 25 + src/libspark/kdf.cpp | 57 ++ src/libspark/kdf.h | 22 + src/libspark/keys.cpp | 118 ++++ src/libspark/keys.h | 72 +++ src/libspark/mint_transaction.cpp | 43 ++ src/libspark/mint_transaction.h | 30 + src/libspark/params.cpp | 137 +++++ src/libspark/params.h | 67 +++ src/libspark/schnorr.cpp | 40 ++ src/libspark/schnorr.h | 22 + src/libspark/schnorr_proof.h | 27 + src/libspark/spend_transaction.cpp | 317 +++++++++++ src/libspark/spend_transaction.h | 68 +++ src/libspark/test/aead_test.cpp | 128 +++++ src/libspark/test/bpplus_test.cpp | 194 +++++++ src/libspark/test/chaum_test.cpp | 180 ++++++ src/libspark/test/coin_test.cpp | 107 ++++ src/libspark/test/encrypt_test.cpp | 52 ++ src/libspark/test/grootle_test.cpp | 153 +++++ src/libspark/test/mint_transaction_test.cpp | 44 ++ src/libspark/test/schnorr_test.cpp | 85 +++ src/libspark/test/spend_transaction_test.cpp | 110 ++++ src/libspark/test/transcript_test.cpp | 174 ++++++ src/libspark/transcript.cpp | 177 ++++++ src/libspark/transcript.h | 32 ++ src/libspark/util.cpp | 232 ++++++++ src/libspark/util.h | 84 +++ 44 files changed, 4832 insertions(+), 2 deletions(-) create mode 100644 src/libspark/aead.cpp create mode 100644 src/libspark/aead.h create mode 100644 src/libspark/bpplus.cpp create mode 100644 src/libspark/bpplus.h create mode 100644 src/libspark/bpplus_proof.h create mode 100644 src/libspark/chaum.cpp create mode 100644 src/libspark/chaum.h create mode 100644 src/libspark/chaum_proof.h create mode 100644 src/libspark/coin.cpp create mode 100644 src/libspark/coin.h create mode 100644 src/libspark/grootle.cpp create mode 100644 src/libspark/grootle.h create mode 100644 src/libspark/grootle_proof.h create mode 100644 src/libspark/hash.cpp create mode 100644 src/libspark/hash.h create mode 100644 src/libspark/kdf.cpp create mode 100644 src/libspark/kdf.h create mode 100644 src/libspark/keys.cpp create mode 100644 src/libspark/keys.h create mode 100644 src/libspark/mint_transaction.cpp create mode 100644 src/libspark/mint_transaction.h create mode 100644 src/libspark/params.cpp create mode 100644 src/libspark/params.h create mode 100644 src/libspark/schnorr.cpp create mode 100644 src/libspark/schnorr.h create mode 100644 src/libspark/schnorr_proof.h create mode 100644 src/libspark/spend_transaction.cpp create mode 100644 src/libspark/spend_transaction.h create mode 100644 src/libspark/test/aead_test.cpp create mode 100644 src/libspark/test/bpplus_test.cpp create mode 100644 src/libspark/test/chaum_test.cpp create mode 100644 src/libspark/test/coin_test.cpp create mode 100644 src/libspark/test/encrypt_test.cpp create mode 100644 src/libspark/test/grootle_test.cpp create mode 100644 src/libspark/test/mint_transaction_test.cpp create mode 100644 src/libspark/test/schnorr_test.cpp create mode 100644 src/libspark/test/spend_transaction_test.cpp create mode 100644 src/libspark/test/transcript_test.cpp create mode 100644 src/libspark/transcript.cpp create mode 100644 src/libspark/transcript.h create mode 100644 src/libspark/util.cpp create mode 100644 src/libspark/util.h diff --git a/src/Makefile.am b/src/Makefile.am index fdeab1406c..1b1cbee591 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -55,6 +55,7 @@ LIBBITCOIN_CONSENSUS=libbitcoin_consensus.a LIBBITCOIN_CLI=libbitcoin_cli.a LIBBITCOIN_UTIL=libbitcoin_util.a LIBLELANTUS=liblelantus.a +LIBSPARK=libspark.a LIBBITCOIN_CRYPTO=crypto/libbitcoin_crypto.a LIBBITCOINQT=qt/libfiroqt.a LIBSECP256K1=secp256k1/libsecp256k1.la @@ -86,7 +87,8 @@ EXTRA_LIBRARIES += \ $(LIBBITCOIN_WALLET) \ $(LIBBITCOIN_ZMQ) \ $(LIBFIRO_SIGMA) \ - $(LIBLELANTUS) + $(LIBLELANTUS) \ + $(LIBSPARK) lib_LTLIBRARIES = $(LIBBITCOINCONSENSUS) @@ -625,6 +627,42 @@ libbitcoin_util_a_SOURCES = \ crypto/MerkleTreeProof/merkle-tree.cpp \ $(BITCOIN_CORE_H) +libspark_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +libspark_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +libspark_a_SOURCES = \ + libspark/transcript.h \ + libspark/transcript.cpp \ + libspark/params.h \ + libspark/params.cpp \ + libspark/schnorr_proof.h \ + libspark/schnorr.h \ + libspark/schnorr.cpp \ + libspark/chaum_proof.h \ + libspark/chaum.h \ + libspark/chaum.cpp \ + libspark/coin.h \ + libspark/coin.cpp \ + libspark/bpplus_proof.h \ + libspark/bpplus.h \ + libspark/bpplus.cpp \ + libspark/grootle_proof.h \ + libspark/grootle.h \ + libspark/grootle.cpp \ + libspark/keys.h \ + libspark/keys.cpp \ + libspark/util.h \ + libspark/util.cpp \ + libspark/aead.h \ + libspark/aead.cpp \ + libspark/kdf.h \ + libspark/kdf.cpp \ + libspark/hash.h \ + libspark/hash.cpp \ + libspark/mint_transaction.h \ + libspark/mint_transaction.cpp \ + libspark/spend_transaction.h \ + libspark/spend_transaction.cpp + liblelantus_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) liblelantus_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) liblelantus_a_SOURCES = \ @@ -729,6 +767,7 @@ firod_LDADD = \ $(LIBBITCOIN_WALLET) \ $(LIBFIRO_SIGMA) \ $(LIBLELANTUS) \ + $(LIBSPARK) \ $(LIBBITCOIN_ZMQ) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 2e1e951287..27923c3a78 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -90,6 +90,16 @@ BITCOIN_TESTS = \ liblelantus/test/schnorr_test.cpp \ liblelantus/test/serialize_test.cpp \ liblelantus/test/sigma_extended_test.cpp \ + libspark/test/transcript_test.cpp \ + libspark/test/schnorr_test.cpp \ + libspark/test/chaum_test.cpp \ + libspark/test/bpplus_test.cpp \ + libspark/test/grootle_test.cpp \ + libspark/test/aead_test.cpp \ + libspark/test/encrypt_test.cpp \ + libspark/test/coin_test.cpp \ + libspark/test/mint_transaction_test.cpp \ + libspark/test/spend_transaction_test.cpp \ sigma/test/coin_spend_tests.cpp \ sigma/test/coin_tests.cpp \ sigma/test/primitives_tests.cpp \ @@ -199,7 +209,7 @@ test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) -ltor test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) $(EVENT_CFLAGS) -test_test_bitcoin_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBFIRO_SIGMA) $(LIBLELANTUS) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) \ +test_test_bitcoin_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBFIRO_SIGMA) $(LIBLELANTUS) $(LIBSPARK) $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) \ $(BACKTRACE_LIB) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_PTHREADS_LIBS) $(ZMQ_LIBS) $(ZLIB_LIBS) test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) if ENABLE_WALLET @@ -226,6 +236,7 @@ test_test_bitcoin_fuzzy_LDADD = \ $(LIBUNIVALUE) \ $(LIBBITCOIN_SERVER) \ $(LIBLELANTUS) \ + $(LIBSPARK) \ $(LIBBITCOIN_COMMON) \ $(LIBBITCOIN_UTIL) \ $(LIBBITCOIN_CONSENSUS) \ diff --git a/src/libspark/aead.cpp b/src/libspark/aead.cpp new file mode 100644 index 0000000000..d3686e255f --- /dev/null +++ b/src/libspark/aead.cpp @@ -0,0 +1,89 @@ +#include "aead.h" + +namespace spark { + +// Perform authenticated encryption with ChaCha20-Poly1305 +AEADEncryptedData AEAD::encrypt(const std::vector& key, const std::string additional_data, CDataStream& data) { + // Check key size + if (key.size() != AEAD_KEY_SIZE) { + throw std::invalid_argument("Bad AEAD key size"); + } + + // Set up the result structure + AEADEncryptedData result; + + // Internal size tracker; we know the size of the data already, and can ignore + int TEMP; + + // For our application, we can safely use a zero nonce since keys are never reused + std::vector iv; + iv.resize(AEAD_IV_SIZE); + + // Set up the cipher + EVP_CIPHER_CTX* ctx; + ctx = EVP_CIPHER_CTX_new(); + EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key.data(), iv.data()); + + // Include the associated data + std::vector additional_data_bytes(additional_data.begin(), additional_data.end()); + EVP_EncryptUpdate(ctx, NULL, &TEMP, additional_data_bytes.data(), additional_data_bytes.size()); + + // Encrypt the plaintext + result.ciphertext.resize(data.size()); + EVP_EncryptUpdate(ctx, result.ciphertext.data(), &TEMP, reinterpret_cast(data.data()), data.size()); + EVP_EncryptFinal_ex(ctx, NULL, &TEMP); + + // Get the tag + result.tag.resize(AEAD_TAG_SIZE); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, AEAD_TAG_SIZE, result.tag.data()); + + // Clean up + EVP_CIPHER_CTX_free(ctx); + + return result; +} + +// Perform authenticated decryption with ChaCha20-Poly1305 +CDataStream AEAD::decrypt_and_verify(const std::vector& key, const std::string additional_data, AEADEncryptedData& data) { + // Check key size + if (key.size() != AEAD_KEY_SIZE) { + throw std::invalid_argument("Bad AEAD key size"); + } + + // Set up the result + CDataStream result(SER_NETWORK, PROTOCOL_VERSION); + + // Internal size tracker; we know the size of the data already, and can ignore + int TEMP; + + // For our application, we can safely use a zero nonce since keys are never reused + std::vector iv; + iv.resize(AEAD_IV_SIZE); + + // Set up the cipher + EVP_CIPHER_CTX* ctx; + ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key.data(), iv.data()); + + // Include the associated data + std::vector additional_data_bytes(additional_data.begin(), additional_data.end()); + EVP_DecryptUpdate(ctx, NULL, &TEMP, additional_data_bytes.data(), additional_data_bytes.size()); + + // Decrypt the ciphertext + result.resize(data.ciphertext.size()); + EVP_DecryptUpdate(ctx, reinterpret_cast(result.data()), &TEMP, data.ciphertext.data(), data.ciphertext.size()); + + // Set the expected tag + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, AEAD_TAG_SIZE, data.tag.data()); + + // Decrypt and clean up + int ret = EVP_DecryptFinal_ex(ctx, NULL, &TEMP); + EVP_CIPHER_CTX_free(ctx); + if (ret != 1) { + throw std::runtime_error("Bad AEAD authentication"); + } + + return result; +} + +} diff --git a/src/libspark/aead.h b/src/libspark/aead.h new file mode 100644 index 0000000000..e7af8ba926 --- /dev/null +++ b/src/libspark/aead.h @@ -0,0 +1,29 @@ +#ifndef FIRO_SPARK_AEAD_H +#define FIRO_SPARK_AEAD_H +#include +#include "util.h" + +namespace spark { + +struct AEADEncryptedData { + std::vector ciphertext; + std::vector tag; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(ciphertext); + READWRITE(tag); + } +}; + +class AEAD { +public: + static AEADEncryptedData encrypt(const std::vector& key, const std::string additional_data, CDataStream& data); + static CDataStream decrypt_and_verify(const std::vector& key, const std::string associated_data, AEADEncryptedData& data); +}; + +} + +#endif diff --git a/src/libspark/bpplus.cpp b/src/libspark/bpplus.cpp new file mode 100644 index 0000000000..224b7a57ef --- /dev/null +++ b/src/libspark/bpplus.cpp @@ -0,0 +1,473 @@ +#include "bpplus.h" +#include "transcript.h" + +namespace spark { + +// Useful scalar constants +const Scalar ZERO = Scalar((uint64_t) 0); +const Scalar ONE = Scalar((uint64_t) 1); +const Scalar TWO = Scalar((uint64_t) 2); + +BPPlus::BPPlus( + const GroupElement& G_, + const GroupElement& H_, + const std::vector& Gi_, + const std::vector& Hi_, + const std::size_t N_) + : G (G_) + , H (H_) + , Gi (Gi_) + , Hi (Hi_) + , N (N_) +{ + if (Gi.size() != Hi.size()) { + throw std::invalid_argument("Bad BPPlus generator sizes!"); + } + + // Bit length must be a power of two + if ((N & (N - 1) != 0)) { + throw std::invalid_argument("Bad BPPlus bit length!"); + } + + // Compute 2**N-1 for optimized verification + TWO_N_MINUS_ONE = TWO; + for (int i = 0; i < log2(N); i++) { + TWO_N_MINUS_ONE *= TWO_N_MINUS_ONE; + } + TWO_N_MINUS_ONE -= ONE; +} + +static inline std::size_t log2(std::size_t n) { + std::size_t l = 0; + while ((n >>= 1) != 0) { + l++; + } + + return l; +} + +void BPPlus::prove( + const std::vector& v, + const std::vector& r, + const std::vector& C, + BPPlusProof& proof) { + // Check statement validity + std::size_t M = C.size(); + if (N*M > Gi.size()) { + throw std::invalid_argument("Bad BPPlus statement!"); + } + if (!(v.size() == M && r.size() == M)) { + throw std::invalid_argument("Bad BPPlus statement!"); + } + for (std::size_t j = 0; j < M; j++) { + if (!(G*v[j] + H*r[j] == C[j])) { + throw std::invalid_argument("Bad BPPlus statement!"); + } + } + + // Set up transcript + Transcript transcript("SPARK_BPPLUS"); + transcript.add("G", G); + transcript.add("H", H); + transcript.add("Gi", Gi); + transcript.add("Hi", Hi); + transcript.add("N", Scalar(N)); + transcript.add("C", C); + + // Decompose bits + std::vector> bits; + bits.resize(M); + for (std::size_t j = 0; j < M; j++) { + v[j].get_bits(bits[j]); + } + + // Compute aL, aR + std::vector aL, aR; + aL.reserve(N*M); + aR.reserve(N*M); + for (std::size_t j = 0; j < M; ++j) + { + for (std::size_t i = 1; i <= N; ++i) + { + aL.emplace_back(uint64_t(bits[j][bits[j].size() - i])); + aR.emplace_back(Scalar(uint64_t(bits[j][bits[j].size() - i])) - ONE); + } + } + + // Compute A + Scalar alpha; + alpha.randomize(); + + std::vector A_points; + std::vector A_scalars; + A_points.reserve(2*N*M + 1); + A_points.reserve(2*N*M + 1); + + A_points.emplace_back(H); + A_scalars.emplace_back(alpha); + for (std::size_t i = 0; i < N*M; i++) { + A_points.emplace_back(Gi[i]); + A_scalars.emplace_back(aL[i]); + A_points.emplace_back(Hi[i]); + A_scalars.emplace_back(aR[i]); + } + secp_primitives::MultiExponent A_multiexp(A_points, A_scalars); + proof.A = A_multiexp.get_multiple(); + transcript.add("A", proof.A); + + // Challenges + Scalar y = transcript.challenge("y"); + Scalar z = transcript.challenge("z"); + Scalar z_square = z.square(); + + // Challenge powers + std::vector y_powers; + y_powers.resize(M*N + 2); + y_powers[0] = ZERO; + y_powers[1] = y; + for (std::size_t i = 2; i < M*N + 2; i++) { + y_powers[i] = y_powers[i-1]*y; + } + + // Compute d + std::vector d; + d.resize(M*N); + d[0] = z_square; + for (std::size_t i = 1; i < N; i++) { + d[i] = TWO*d[i-1]; + } + for (std::size_t j = 1; j < M; j++) { + for (std::size_t i = 0; i < N; i++) { + d[j*N+i] = d[(j-1)*N+i]*z_square; + } + } + + // Compute aL1, aR1 + std::vector aL1, aR1; + for (std::size_t i = 0; i < N*M; i++) { + aL1.emplace_back(aL[i] - z); + aR1.emplace_back(aR[i] + d[i]*y_powers[N*M - i] + z); + } + + // Compute alpha1 + Scalar alpha1 = alpha; + Scalar z_even_powers = 1; + for (std::size_t j = 0; j < M; j++) { + z_even_powers *= z_square; + alpha1 += z_even_powers*r[j]*y_powers[N*M+1]; + } + + // Run the inner product rounds + std::vector Gi1(Gi); + std::vector Hi1(Hi); + std::vector a1(aL1); + std::vector b1(aR1); + std::size_t N1 = N*M; + + while (N1 > 1) { + N1 /= 2; + + Scalar dL, dR; + dL.randomize(); + dR.randomize(); + + // Compute cL, cR + Scalar cL, cR; + for (std::size_t i = 0; i < N1; i++) { + cL += a1[i]*y_powers[i+1]*b1[i+N1]; + cR += a1[i+N1]*y_powers[N1]*y_powers[i+1]*b1[i]; + } + + // Compute L, R + GroupElement L_, R_; + std::vector L_points, R_points; + std::vector L_scalars, R_scalars; + L_points.reserve(2*N1 + 2); + R_points.reserve(2*N1 + 2); + L_scalars.reserve(2*N1 + 2); + R_scalars.reserve(2*N1 + 2); + Scalar y_N1_inverse = y_powers[N1].inverse(); + for (std::size_t i = 0; i < N1; i++) { + L_points.emplace_back(Gi1[i+N1]); + L_scalars.emplace_back(a1[i]*y_N1_inverse); + L_points.emplace_back(Hi1[i]); + L_scalars.emplace_back(b1[i+N1]); + + R_points.emplace_back(Gi1[i]); + R_scalars.emplace_back(a1[i+N1]*y_powers[N1]); + R_points.emplace_back(Hi1[i+N1]); + R_scalars.emplace_back(b1[i]); + } + L_points.emplace_back(G); + L_scalars.emplace_back(cL); + L_points.emplace_back(H); + L_scalars.emplace_back(dL); + R_points.emplace_back(G); + R_scalars.emplace_back(cR); + R_points.emplace_back(H); + R_scalars.emplace_back(dR); + + secp_primitives::MultiExponent L_multiexp(L_points, L_scalars); + secp_primitives::MultiExponent R_multiexp(R_points, R_scalars); + L_ = L_multiexp.get_multiple(); + R_ = R_multiexp.get_multiple(); + proof.L.emplace_back(L_); + proof.R.emplace_back(R_); + + transcript.add("L", L_); + transcript.add("R", R_); + Scalar e = transcript.challenge("e"); + Scalar e_inverse = e.inverse(); + + // Compress round elements + for (std::size_t i = 0; i < N1; i++) { + Gi1[i] = Gi1[i]*e_inverse + Gi1[i+N1]*(e*y_N1_inverse); + Hi1[i] = Hi1[i]*e + Hi1[i+N1]*e_inverse; + a1[i] = a1[i]*e + a1[i+N1]*y_powers[N1]*e_inverse; + b1[i] = b1[i]*e_inverse + b1[i+N1]*e; + } + Gi1.resize(N1); + Hi1.resize(N1); + a1.resize(N1); + b1.resize(N1); + + // Update alpha1 + alpha1 = dL*e.square() + alpha1 + dR*e_inverse.square(); + } + + // Final proof elements + Scalar r_, s_, d_, eta_; + r_.randomize(); + s_.randomize(); + d_.randomize(); + eta_.randomize(); + + proof.A1 = Gi1[0]*r_ + Hi1[0]*s_ + G*(r_*y*b1[0] + s_*y*a1[0]) + H*d_; + proof.B = G*(r_*y*s_) + H*eta_; + + transcript.add("A1", proof.A1); + transcript.add("B", proof.B); + Scalar e1 = transcript.challenge("e1"); + + proof.r1 = r_ + a1[0]*e1; + proof.s1 = s_ + b1[0]*e1; + proof.d1 = eta_ + d_*e1 + alpha1*e1.square(); +} + +bool BPPlus::verify(const std::vector& C, const BPPlusProof& proof) { + std::vector> C_batch = {C}; + std::vector proof_batch = {proof}; + + return verify(C_batch, proof_batch); +} + +bool BPPlus::verify(const std::vector>& C, const std::vector& proofs) { + // Preprocess all proofs + if (!(C.size() == proofs.size())) { + return false; + } + std::size_t N_proofs = proofs.size(); + std::size_t max_M = 0; // maximum number of aggregated values across all proofs + + // Check aggregated input consistency + for (std::size_t k = 0; k < N_proofs; k++) { + std::size_t M = C[k].size(); + + // Require a power of two + if (M == 0) { + return false; + } + if ((M & (M - 1)) != 0) { + return false; + } + + // Track the maximum value + if (M > max_M) { + max_M = M; + } + + // Check inner produce round consistency + std::size_t rounds = proofs[k].L.size(); + if (proofs[k].R.size() != rounds) { + return false; + } + if (log2(N*M) != rounds) { + return false; + } + } + + // Check the bounds on the batch + if (max_M*N > Gi.size() || max_M*N > Hi.size()) { + return false; + } + + // Set up final multiscalar multiplication and common scalars + std::vector points; + std::vector scalars; + Scalar G_scalar, H_scalar; + + // Interleave the Gi and Hi scalars + for (std::size_t i = 0; i < max_M*N; i++) { + points.emplace_back(Gi[i]); + scalars.emplace_back(ZERO); + points.emplace_back(Hi[i]); + scalars.emplace_back(ZERO); + } + + // Process each proof and add to the batch + for (std::size_t k_proofs = 0; k_proofs < N_proofs; k_proofs++) { + const BPPlusProof proof = proofs[k_proofs]; + const std::size_t M = C[k_proofs].size(); + const std::size_t rounds = proof.L.size(); + + // Weight this proof in the batch + Scalar w = ZERO; + while (w == ZERO) { + w.randomize(); + } + + // Set up transcript + Transcript transcript("SPARK_BPPLUS"); + transcript.add("G", G); + transcript.add("H", H); + transcript.add("Gi", Gi); + transcript.add("Hi", Hi); + transcript.add("N", Scalar(N)); + transcript.add("C", C[k_proofs]); + transcript.add("A", proof.A); + + // Get challenges + Scalar y = transcript.challenge("y"); + Scalar y_inverse = y.inverse(); + Scalar y_NM = y; + for (std::size_t i = 0; i < rounds; i++) { + y_NM = y_NM.square(); + } + Scalar y_NM_1 = y_NM*y; + + Scalar z = transcript.challenge("z"); + Scalar z_square = z.square(); + + std::vector e; + std::vector e_inverse; + for (std::size_t j = 0; j < rounds; j++) { + transcript.add("L", proof.L[j]); + transcript.add("R", proof.R[j]); + e.emplace_back(transcript.challenge("e")); + e_inverse.emplace_back(e[j].inverse()); + } + + transcript.add("A1", proof.A1); + transcript.add("B", proof.B); + Scalar e1 = transcript.challenge("e1"); + Scalar e1_square = e1.square(); + + // C_j: -e1**2 * z**(2*(j + 1)) * y**(N*M + 1) * w + Scalar C_scalar = e1_square.negate()*z_square*y_NM_1*w; + for (std::size_t j = 0; j < M; j++) { + points.emplace_back(C[k_proofs][j]); + scalars.emplace_back(C_scalar); + + C_scalar *= z.square(); + } + + // B: -w + points.emplace_back(proof.B); + scalars.emplace_back(w.negate()); + + // A1: -w*e1 + points.emplace_back(proof.A1); + scalars.emplace_back(w.negate()*e1); + + // A: -w*e1**2 + points.emplace_back(proof.A); + scalars.emplace_back(w.negate()*e1_square); + + // H: w*d1 + H_scalar += w*proof.d1; + + // Compute d + std::vector d; + d.resize(N*M); + d[0] = z_square; + for (std::size_t i = 1; i < N; i++) { + d[i] = d[i-1] + d[i-1]; + } + for (std::size_t j = 1; j < M; j++) { + for (std::size_t i = 0; i < N; i++) { + d[j*N + i] = d[(j - 1)*N + i]*z_square; + } + } + + // Sum the elements of d + Scalar sum_d = z_square; + Scalar temp_z = sum_d; + std::size_t temp_2M = 2*M; + while (temp_2M > 2) { + sum_d += sum_d*temp_z; + temp_z = temp_z.square(); + temp_2M /= 2; + } + sum_d *= TWO_N_MINUS_ONE; + + // Sum the powers of y + Scalar sum_y; + Scalar track = y; + for (std::size_t i = 0; i < N*M; i++) { + sum_y += track; + track *= y; + } + + // G: w*(r1*y*s1 + e1**2*(y**(N*M + 1)*z*sum_d + (z**2-z)*sum_y)) + G_scalar += w*(proof.r1*y*proof.s1 + e1_square*(y_NM_1*z*sum_d + (z_square - z)*sum_y)); + + // Track some iterated exponential terms + Scalar iter_y_inv = ONE; // y.inverse()**i + Scalar iter_y_NM = y_NM; // y**(N*M - i) + + // Gi, Hi + for (std::size_t i = 0; i < N*M; i++) { + Scalar g = proof.r1*e1*iter_y_inv; + Scalar h = proof.s1*e1; + for (std::size_t j = 0; j < rounds; j++) { + if ((i >> j) & 1) { + g *= e[rounds-j-1]; + h *= e_inverse[rounds-j-1]; + } else { + h *= e[rounds-j-1]; + g *= e_inverse[rounds-j-1]; + } + } + + // Gi + scalars[2*i] += w*(g + e1_square*z); + + // Hi + scalars[2*i+1] += w*(h - e1_square*(d[i]*iter_y_NM+z)); + + // Update the iterated values + iter_y_inv *= y_inverse; + iter_y_NM *= y_inverse; + } + + // L, R + for (std::size_t j = 0; j < rounds; j++) { + points.emplace_back(proof.L[j]); + scalars.emplace_back(w*(e1_square.negate()*e[j].square())); + points.emplace_back(proof.R[j]); + scalars.emplace_back(w*(e1_square.negate()*e_inverse[j].square())); + } + } + + // Add the common generators + points.emplace_back(G); + scalars.emplace_back(G_scalar); + points.emplace_back(H); + scalars.emplace_back(H_scalar); + + // Test the batch + secp_primitives::MultiExponent multiexp(points, scalars); + return multiexp.get_multiple().isInfinity(); +} + +} \ No newline at end of file diff --git a/src/libspark/bpplus.h b/src/libspark/bpplus.h new file mode 100644 index 0000000000..9c6983ec7f --- /dev/null +++ b/src/libspark/bpplus.h @@ -0,0 +1,33 @@ +#ifndef FIRO_LIBSPARK_BPPLUS_H +#define FIRO_LIBSPARK_BPPLUS_H + +#include "bpplus_proof.h" +#include + +namespace spark { + +class BPPlus { +public: + BPPlus( + const GroupElement& G, + const GroupElement& H, + const std::vector& Gi, + const std::vector& Hi, + const std::size_t N); + + void prove(const std::vector& v, const std::vector& r, const std::vector& C, BPPlusProof& proof); + bool verify(const std::vector& C, const BPPlusProof& proof); // single proof + bool verify(const std::vector>& C, const std::vector& proofs); // batch of proofs + +private: + GroupElement G; + GroupElement H; + std::vector Gi; + std::vector Hi; + std::size_t N; + Scalar TWO_N_MINUS_ONE; +}; + +} + +#endif diff --git a/src/libspark/bpplus_proof.h b/src/libspark/bpplus_proof.h new file mode 100644 index 0000000000..214dbc9a0e --- /dev/null +++ b/src/libspark/bpplus_proof.h @@ -0,0 +1,44 @@ +#ifndef FIRO_LIBSPARK_BPPLUS_PROOF_H +#define FIRO_LIBSPARK_BPPLUS_PROOF_H + +#include "params.h" + +namespace spark { + +class BPPlusProof{ +public: + + static inline int int_log2(std::size_t number) { + assert(number != 0); + + int l2 = 0; + while ((number >>= 1) != 0) + l2++; + + return l2; + } + + inline std::size_t memoryRequired() const { + return 3*GroupElement::memoryRequired() + 3*Scalar::memoryRequired() + L.size()*GroupElement::memoryRequired() + R.size()*GroupElement::memoryRequired(); + } + + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(A); + READWRITE(A1); + READWRITE(B); + READWRITE(r1); + READWRITE(s1); + READWRITE(d1); + READWRITE(L); + READWRITE(R); + } + + GroupElement A, A1, B; + Scalar r1, s1, d1; + std::vector L, R; +}; +} + +#endif diff --git a/src/libspark/chaum.cpp b/src/libspark/chaum.cpp new file mode 100644 index 0000000000..e130ef2539 --- /dev/null +++ b/src/libspark/chaum.cpp @@ -0,0 +1,163 @@ +#include "chaum.h" +#include "transcript.h" + +namespace spark { + +Chaum::Chaum(const GroupElement& F_, const GroupElement& G_, const GroupElement& H_, const GroupElement& U_): + F(F_), G(G_), H(H_), U(U_) { +} + +Scalar Chaum::challenge( + const Scalar& mu, + const std::vector& S, + const std::vector& T, + const GroupElement& A1, + const std::vector& A2 +) { + Transcript transcript("SPARK_CHAUM"); + transcript.add("F", F); + transcript.add("G", G); + transcript.add("H", H); + transcript.add("U", U); + transcript.add("mu", mu); + transcript.add("S", S); + transcript.add("T", T); + transcript.add("A1", A1); + transcript.add("A2", A2); + + return transcript.challenge("c"); +} + +void Chaum::prove( + const Scalar& mu, + const std::vector& x, + const std::vector& y, + const std::vector& z, + const std::vector& S, + const std::vector& T, + ChaumProof& proof +) { + // Check statement validity + std::size_t n = x.size(); + if (!(y.size() == n && z.size() == n && S.size() == n && T.size() == n)) { + throw std::invalid_argument("Bad Chaum statement!"); + } + for (std::size_t i = 0; i < n; i++) { + if (!(F*x[i] + G*y[i] + H*z[i] == S[i] && T[i]*x[i] + G*y[i] == U)) { + throw std::invalid_argument("Bad Chaum statement!"); + } + } + + std::vector r; + r.resize(n); + std::vector s; + s.resize(n); + for (std::size_t i = 0; i < n; i++) { + r[i].randomize(); + s[i].randomize(); + } + Scalar t; + t.randomize(); + + proof.A1 = H*t; + proof.A2.resize(n); + for (std::size_t i = 0; i < n; i++) { + proof.A1 += F*r[i] + G*s[i]; + proof.A2[i] = T[i]*r[i] + G*s[i]; + } + + Scalar c = challenge(mu, S, T, proof.A1, proof.A2); + + proof.t1.resize(n); + proof.t3 = t; + Scalar c_power(c); + for (std::size_t i = 0; i < n; i++) { + proof.t1[i] = r[i] + c_power*x[i]; + proof.t2 += s[i] + c_power*y[i]; + proof.t3 += c_power*z[i]; + c_power *= c; + } +} + +bool Chaum::verify( + const Scalar& mu, + const std::vector& S, + const std::vector& T, + ChaumProof& proof +) { + // Check proof semantics + std::size_t n = S.size(); + if (!(T.size() == n && proof.A2.size() == n && proof.t1.size() == n)) { + throw std::invalid_argument("Bad Chaum semantics!"); + } + + Scalar c = challenge(mu, S, T, proof.A1, proof.A2); + std::vector c_powers; + c_powers.emplace_back(c); + for (std::size_t i = 1; i < n; i++) { + c_powers.emplace_back(c_powers[i-1]*c); + } + + // Weight the verification equations + Scalar w; + while (w.isZero()) { + w.randomize(); + } + + std::vector scalars; + std::vector points; + scalars.reserve(3*n + 5); + points.reserve(3*n + 5); + + // F + Scalar F_scalar; + for (std::size_t i = 0; i < n; i++) { + F_scalar -= proof.t1[i]; + } + scalars.emplace_back(F_scalar); + points.emplace_back(F); + + // G + scalars.emplace_back(proof.t2.negate() - w*proof.t2); + points.emplace_back(G); + + // H + scalars.emplace_back(proof.t3.negate()); + points.emplace_back(H); + + // U + Scalar U_scalar; + for (std::size_t i = 0; i < n; i++) { + U_scalar += c_powers[i]; + } + U_scalar *= w; + scalars.emplace_back(U_scalar); + points.emplace_back(U); + + // A1 + scalars.emplace_back(Scalar((uint64_t) 1)); + points.emplace_back(proof.A1); + + // {A2} + for (std::size_t i = 0; i < n; i++) { + scalars.emplace_back(w); + points.emplace_back(proof.A2[i]); + } + + // {S} + for (std::size_t i = 0; i < n; i++) { + scalars.emplace_back(c_powers[i]); + points.emplace_back(S[i]); + } + + // {T} + for (std::size_t i = 0; i < n; i++) { + scalars.emplace_back(w.negate()*proof.t1[i]); + points.emplace_back(T[i]); + } + + secp_primitives::MultiExponent multiexp(points, scalars); + return multiexp.get_multiple().isInfinity(); +} + +} diff --git a/src/libspark/chaum.h b/src/libspark/chaum.h new file mode 100644 index 0000000000..b15868b84c --- /dev/null +++ b/src/libspark/chaum.h @@ -0,0 +1,45 @@ +#ifndef FIRO_LIBSPARK_CHAUM_H +#define FIRO_LIBSPARK_CHAUM_H + +#include "chaum_proof.h" +#include + +namespace spark { + +class Chaum { +public: + Chaum(const GroupElement& F, const GroupElement& G, const GroupElement& H, const GroupElement& U); + + void prove( + const Scalar& mu, + const std::vector& x, + const std::vector& y, + const std::vector& z, + const std::vector& S, + const std::vector& T, + ChaumProof& proof + ); + bool verify( + const Scalar& mu, + const std::vector& S, + const std::vector& T, + ChaumProof& proof + ); + +private: + Scalar challenge( + const Scalar& mu, + const std::vector& S, + const std::vector& T, + const GroupElement& A1, + const std::vector& A2 + ); + const GroupElement& F; + const GroupElement& G; + const GroupElement& H; + const GroupElement& U; +}; + +} + +#endif diff --git a/src/libspark/chaum_proof.h b/src/libspark/chaum_proof.h new file mode 100644 index 0000000000..1885b883c1 --- /dev/null +++ b/src/libspark/chaum_proof.h @@ -0,0 +1,32 @@ +#ifndef FIRO_LIBSPARK_CHAUM_PROOF_H +#define FIRO_LIBSPARK_CHAUM_PROOF_H + +#include "params.h" + +namespace spark { + +class ChaumProof{ +public: + inline std::size_t memoryRequired() const { + return GroupElement::memoryRequired() + A2.size()*GroupElement::memoryRequired() + t1.size()*Scalar::memoryRequired() + 2*Scalar::memoryRequired(); + } + + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(A1); + READWRITE(A2); + READWRITE(t1); + READWRITE(t2); + READWRITE(t3); + } + +public: + GroupElement A1; + std::vector A2; + std::vector t1; + Scalar t2, t3; +}; +} + +#endif diff --git a/src/libspark/coin.cpp b/src/libspark/coin.cpp new file mode 100644 index 0000000000..66644808ee --- /dev/null +++ b/src/libspark/coin.cpp @@ -0,0 +1,154 @@ +#include "coin.h" + +namespace spark { + +using namespace secp_primitives; + +Coin::Coin() {} + +Coin::Coin( + const Params* params, + const char type, + const Scalar& k, + const Address& address, + const uint64_t v, + const std::string memo +) { + this->params = params; + + // Validate the type + if (type != COIN_TYPE_MINT && type != COIN_TYPE_SPEND) { + throw std::invalid_argument("Bad coin type"); + } + this->type = type; + + + // + // Common elements to both coin types + // + + // Construct the recovery key + this->K = SparkUtils::hash_div(address.get_d())*SparkUtils::hash_k(k); + + // Construct the serial commitment + this->S = this->params->get_F()*SparkUtils::hash_ser(k) + address.get_Q2(); + + // Construct the value commitment + this->C = this->params->get_G()*Scalar(v) + this->params->get_H()*SparkUtils::hash_val(k); + + // Check the memo validity, and pad if needed + if (memo.size() > this->params->get_memo_bytes()) { + throw std::invalid_argument("Memo is too large"); + } + std::vector memo_bytes(memo.begin(), memo.end()); + std::vector padded_memo(memo_bytes); + padded_memo.resize(this->params->get_memo_bytes()); + + // + // Type-specific elements + // + + if (this->type == COIN_TYPE_MINT) { + this->v = v; + + // Encrypt recipient data + MintCoinRecipientData r; + r.d = address.get_d(); + r.k = k; + r.memo = std::string(memo.begin(), memo.end()); + CDataStream r_stream(SER_NETWORK, PROTOCOL_VERSION); + r_stream << r; + this->r_ = AEAD::encrypt(SparkUtils::kdf_aead(address.get_Q1()*SparkUtils::hash_k(k)), "Mint coin data", r_stream); + } else { + // Encrypt recipient data + SpendCoinRecipientData r; + r.v = v; + r.d = address.get_d(); + r.k = k; + r.memo = std::string(memo.begin(), memo.end()); + CDataStream r_stream(SER_NETWORK, PROTOCOL_VERSION); + r_stream << r; + this->r_ = AEAD::encrypt(SparkUtils::kdf_aead(address.get_Q1()*SparkUtils::hash_k(k)), "Spend coin data", r_stream); + } +} + +// Validate a coin for identification +bool Coin::validate( + const IncomingViewKey& incoming_view_key, + IdentifiedCoinData& data +) { + // Check recovery key + if (SparkUtils::hash_div(data.d)*SparkUtils::hash_k(data.k) != this->K) { + return false; + } + + // Check value commitment + if (this->params->get_G()*Scalar(data.v) + this->params->get_H()*SparkUtils::hash_val(data.k) != this->C) { + return false; + } + + // Check serial commitment + data.i = incoming_view_key.get_diversifier(data.d); + + if (this->params->get_F()*(SparkUtils::hash_ser(data.k) + SparkUtils::hash_Q2(incoming_view_key.get_s1(), data.i)) + incoming_view_key.get_P2() != this->S) { + return false; + } + + return true; +} + +// Recover a coin +RecoveredCoinData Coin::recover(const FullViewKey& full_view_key, const IdentifiedCoinData& data) { + RecoveredCoinData recovered_data; + recovered_data.s = SparkUtils::hash_ser(data.k) + SparkUtils::hash_Q2(full_view_key.get_s1(), data.i) + full_view_key.get_s2(); + recovered_data.T = (this->params->get_U() + full_view_key.get_D().inverse())*recovered_data.s.inverse(); + + return recovered_data; +} + +// Identify a coin +IdentifiedCoinData Coin::identify(const IncomingViewKey& incoming_view_key) { + IdentifiedCoinData data; + + // Deserialization means this process depends on the coin type + if (this->type == COIN_TYPE_MINT) { + MintCoinRecipientData r; + + try { + // Decrypt recipient data + CDataStream stream = AEAD::decrypt_and_verify(SparkUtils::kdf_aead(this->K*incoming_view_key.get_s1()), "Mint coin data", this->r_); + stream >> r; + } catch (...) { + throw std::runtime_error("Unable to identify coin"); + } + + data.d = r.d; + data.v = this->v; + data.k = r.k; + data.memo = r.memo; + } else { + SpendCoinRecipientData r; + + try { + // Decrypt recipient data + CDataStream stream = AEAD::decrypt_and_verify(SparkUtils::kdf_aead(this->K*incoming_view_key.get_s1()), "Spend coin data", this->r_); + stream >> r; + } catch (...) { + throw std::runtime_error("Unable to identify coin"); + } + + data.d = r.d; + data.v = r.v; + data.k = r.k; + data.memo = r.memo; + } + + // Validate the coin + if (!validate(incoming_view_key, data)) { + throw std::runtime_error("Malformed coin"); + } + + return data; +} + +} diff --git a/src/libspark/coin.h b/src/libspark/coin.h new file mode 100644 index 0000000000..1d935b14a0 --- /dev/null +++ b/src/libspark/coin.h @@ -0,0 +1,111 @@ +#ifndef FIRO_SPARK_COIN_H +#define FIRO_SPARK_COIN_H +#include "bpplus.h" +#include "keys.h" +#include +#include "params.h" +#include "aead.h" +#include "util.h" + +namespace spark { + +using namespace secp_primitives; + +// Flags for coin types: those generated from mints, and those generated from spends +const char COIN_TYPE_MINT = 0; +const char COIN_TYPE_SPEND = 1; + +struct IdentifiedCoinData { + uint64_t i; // diversifier + std::vector d; // encrypted diversifier + uint64_t v; // value + Scalar k; // nonce + std::string memo; // memo +}; + +struct RecoveredCoinData { + Scalar s; // serial + GroupElement T; // tag +}; + +// Data to be encrypted for the recipient of a coin generated in a mint transaction +struct MintCoinRecipientData { + std::vector d; // encrypted diversifier + Scalar k; // nonce + std::string memo; // memo + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(d); + READWRITE(k); + READWRITE(memo); + } +}; + +// Data to be encrypted for the recipient of a coin generated in a spend transaction +struct SpendCoinRecipientData { + uint64_t v; // value + std::vector d; // encrypted diversifier + Scalar k; // nonce + std::string memo; // memo + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(v); + READWRITE(d); + READWRITE(k); + READWRITE(memo); + } +}; + +class Coin { +public: + Coin(); + Coin( + const Params* params, + const char type, + const Scalar& k, + const Address& address, + const uint64_t v, + const std::string memo + ); + + // Given an incoming view key, extract the coin's nonce, diversifier, value, and memo + IdentifiedCoinData identify(const IncomingViewKey& incoming_view_key); + + // Given a full view key, extract the coin's serial number and tag + RecoveredCoinData recover(const FullViewKey& full_view_key, const IdentifiedCoinData& data); + +protected: + bool validate(const IncomingViewKey& incoming_view_key, IdentifiedCoinData& data); + +public: + const Params* params; + char type; // type flag + GroupElement S, K, C; // serial commitment, recovery key, value commitment + AEADEncryptedData r_; // encrypted recipient data + uint64_t v; // value + + // Serialization depends on the coin type + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(type); + READWRITE(S); + READWRITE(K); + READWRITE(C); + READWRITE(r_); + + if (type == COIN_TYPE_MINT) { + READWRITE(v); + } + } +}; + +} + +#endif diff --git a/src/libspark/grootle.cpp b/src/libspark/grootle.cpp new file mode 100644 index 0000000000..9d1ebdb7d9 --- /dev/null +++ b/src/libspark/grootle.cpp @@ -0,0 +1,564 @@ +#include "grootle.h" +#include "transcript.h" + +namespace spark { + +// Useful scalar constants +const Scalar ZERO = Scalar(uint64_t(0)); +const Scalar ONE = Scalar(uint64_t(1)); +const Scalar TWO = Scalar(uint64_t(2)); + +Grootle::Grootle( + const GroupElement& H_, + const std::vector& Gi_, + const std::vector& Hi_, + const std::size_t n_, + const std::size_t m_) + : H (H_) + , Gi (Gi_) + , Hi (Hi_) + , n (n_) + , m (m_) +{ + if (!(n > 1 && m > 1)) { + throw std::invalid_argument("Bad Grootle size parameters!"); + } + if (Gi.size() != n*m || Hi.size() != n*m) { + throw std::invalid_argument("Bad Grootle generator size!"); + } +} + +// Compute a delta function vector +static inline std::vector convert_to_sigma(std::size_t num, const std::size_t n, const std::size_t m) { + std::vector result; + result.reserve(n*m); + + for (std::size_t j = 0; j < m; j++) { + for (std::size_t i = 0; i < n; i++) { + if (i == (num % n)) { + result.emplace_back(ONE); + } else { + result.emplace_back(ZERO); + } + } + num /= n; + } + + return result; +} + +// Decompose an integer with arbitrary base and padded size +static inline std::vector decompose(std::size_t num, const std::size_t n, const std::size_t m) { + std::vector result; + result.reserve(m); + + while (num != 0) { + result.emplace_back(num % n); + num /= n; + } + result.resize(m); + + return result; +} + +// Compute a double Pedersen vector commitment +static inline GroupElement vector_commit(const std::vector& Gi, const std::vector& Hi, const std::vector& a, const std::vector& b, const GroupElement& H, const Scalar& r) { + return secp_primitives::MultiExponent(Gi, a).get_multiple() + secp_primitives::MultiExponent(Hi, b).get_multiple() + H*r; +} + +// Compute a convolution with a degree-one polynomial +static inline void convolve(const Scalar& x_1, const Scalar& x_0, std::vector& coefficients) { + if (coefficients.empty()) { + throw std::runtime_error("Empty convolution coefficient vector!"); + } + + std::size_t degree = coefficients.size() - 1; + coefficients.emplace_back(x_1*coefficients[degree]); + for (std::size_t i = degree; i >=1; i--) { + coefficients[i] = x_0*coefficients[i] + x_1*coefficients[i-1]; + } + coefficients[0] *= x_0; +} + +static bool compute_fs( + const GrootleProof& proof, + const Scalar& x, + std::vector& f_, + const std::size_t n, + const std::size_t m) { + for (std::size_t j = 0; j < proof.f.size(); ++j) { + if(proof.f[j] == x) + return false; + } + + f_.reserve(n * m); + for (std::size_t j = 0; j < m; ++j) + { + f_.push_back(Scalar(uint64_t(0))); + Scalar temp; + std::size_t k = n - 1; + for (std::size_t i = 0; i < k; ++i) + { + temp += proof.f[j * k + i]; + f_.emplace_back(proof.f[j * k + i]); + } + f_[j * n] = x - temp; + } + return true; +} + +static void compute_batch_fis( + Scalar& f_sum, + const Scalar& f_i, + int j, + const std::vector& f, + const Scalar& y, + std::vector::iterator& ptr, + std::vector::iterator start_ptr, + std::vector::iterator end_ptr, + const std::size_t n) { + j--; + if (j == -1) + { + if(ptr >= start_ptr && ptr < end_ptr){ + *ptr++ += f_i * y; + f_sum += f_i; + } + return; + } + + Scalar t; + + for (std::size_t i = 0; i < n; i++) + { + t = f[j * n + i]; + t *= f_i; + + compute_batch_fis(f_sum, t, j, f, y, ptr, start_ptr, end_ptr, n); + } +} + +void Grootle::prove( + const std::size_t l, + const Scalar& s, + const std::vector& S, + const GroupElement& S1, + const Scalar& v, + const std::vector& V, + const GroupElement& V1, + GrootleProof& proof) { + // Check statement validity + std::size_t N = (std::size_t) pow(n, m); // padded input size + std::size_t size = S.size(); // actual input size + if (l >= size) { + throw std::invalid_argument("Bad Grootle secret index!"); + } + if (V.size() != S.size()) { + throw std::invalid_argument("Bad Grootle input vector sizes!"); + } + if (size > N || size == 0) { + throw std::invalid_argument("Bad Grootle size parameter!"); + } + if (S[l] + S1.inverse() != H*s) { + throw std::invalid_argument("Bad Grootle proof statement!"); + } + if (V[l] + V1.inverse() != H*v) { + throw std::invalid_argument("Bad Grootle proof statement!"); + } + + // Set up transcript + Transcript transcript("SPARK_GROOTLE"); + transcript.add("H", H); + transcript.add("Gi", Gi); + transcript.add("Hi", Hi); + transcript.add("n", Scalar(n)); + transcript.add("m", Scalar(m)); + transcript.add("S", S); + transcript.add("S1", S1); + transcript.add("V", V); + transcript.add("V1", V1); + + // Compute A + std::vector a; + a.resize(n*m); + for (std::size_t j = 0; j < m; j++) { + for (std::size_t i = 1; i < n; i++) { + a[j*n + i].randomize(); + a[j*n] -= a[j*n + i]; + } + } + std::vector d; + d.resize(n*m); + for (std::size_t i = 0; i < n*m; i++) { + d[i] = a[i].square().negate(); + } + Scalar rA; + rA.randomize(); + proof.A = vector_commit(Gi, Hi, a, d, H, rA); + + // Compute B + std::vector sigma = convert_to_sigma(l, n, m); + std::vector c; + c.resize(n*m); + for (std::size_t i = 0; i < n*m; i++) { + c[i] = a[i]*(ONE - TWO*sigma[i]); + } + Scalar rB; + rB.randomize(); + proof.B = vector_commit(Gi, Hi, sigma, c, H, rB); + + // Compute convolution terms + std::vector> P_i_j; + P_i_j.resize(size); + for (std::size_t i = 0; i < size - 1; ++i) + { + std::vector& coefficients = P_i_j[i]; + std::vector I = decompose(i, n, m); + coefficients.push_back(a[I[0]]); + coefficients.push_back(sigma[I[0]]); + for (std::size_t j = 1; j < m; ++j) { + convolve(sigma[j*n + I[j]], a[j*n + I[j]], coefficients); + } + } + + /* + * To optimize calculation of sum of all polynomials indices 's' = size-1 through 'n^m-1' we use the + * fact that sum of all of elements in each row of 'a' array is zero. Computation is done by going + * through n-ary representation of 's' and increasing "digit" at each position to 'n-1' one by one. + * During every step digits at higher positions are fixed and digits at lower positions go through all + * possible combinations with a total corresponding polynomial sum of 'x^j'. + * + * The math behind optimization (TeX notation): + * + * \sum_{i=s+1}^{N-1}p_i(x) = + * \sum_{j=0}^{m-1} + * \left[ + * \left( \sum_{i=s_j+1}^{n-1}(\delta_{l_j,i}x+a_{j,i}) \right) + * \left( \prod_{k=j}^{m-1}(\delta_{l_k,s_k}x+a_{k,s_k}) \right) + * x^j + * \right] + */ + + std::vector I = decompose(size - 1, n, m); + std::vector lj = decompose(l, n, m); + + std::vector p_i_sum; + p_i_sum.emplace_back(ONE); + std::vector> partial_p_s; + + // Pre-calculate product parts and calculate p_s(x) at the same time, put the latter into p_i_sum + for (std::ptrdiff_t j = m - 1; j >= 0; j--) { + partial_p_s.push_back(p_i_sum); + convolve(sigma[j*n + I[j]], a[j*n + I[j]], p_i_sum); + } + + for (std::size_t j = 0; j < m; j++) { + // \sum_{i=s_j+1}^{n-1}(\delta_{l_j,i}x+a_{j,i}) + Scalar a_sum(uint64_t(0)); + for (std::size_t i = I[j] + 1; i < n; i++) + a_sum += a[j * n + i]; + Scalar x_sum(uint64_t(lj[j] >= I[j]+1 ? 1 : 0)); + + // Multiply by \prod_{k=j}^{m-1}(\delta_{l_k,s_k}x+a_{k,s_k}) + std::vector &polynomial = partial_p_s[m - j - 1]; + convolve(x_sum, a_sum, polynomial); + + // Multiply by x^j and add to the result + for (std::size_t k = 0; k < m - j; k++) + p_i_sum[j + k] += polynomial[k]; + } + + P_i_j[size - 1] = p_i_sum; + + // Perform the commitment offsets + std::vector S_offset(S); + std::vector V_offset(V); + GroupElement S1_inverse = S1.inverse(); + GroupElement V1_inverse = V1.inverse(); + for (std::size_t k = 0; k < S_offset.size(); k++) { + S_offset[k] += S1_inverse; + V_offset[k] += V1_inverse; + } + + // Generate masks + std::vector rho_S, rho_V; + rho_S.resize(m); + rho_V.resize(m); + for (std::size_t j = 0; j < m; j++) { + rho_S[j].randomize(); + rho_V[j].randomize(); + } + + proof.X.reserve(m); + proof.X1.reserve(m); + for (std::size_t j = 0; j < m; ++j) + { + std::vector P_i; + P_i.reserve(size); + for (std::size_t i = 0; i < size; ++i){ + P_i.emplace_back(P_i_j[i][j]); + } + + // S + secp_primitives::MultiExponent mult_S(S_offset, P_i); + proof.X.emplace_back(mult_S.get_multiple() + H*rho_S[j]); + + // V + secp_primitives::MultiExponent mult_V(V_offset, P_i); + proof.X1.emplace_back(mult_V.get_multiple() + H*rho_V[j]); + } + + // Challenge + transcript.add("A", proof.A); + transcript.add("B", proof.B); + transcript.add("X", proof.X); + transcript.add("X1", proof.X1); + Scalar x = transcript.challenge("x"); + + // Compute f + proof.f.reserve(m*(n - 1)); + for (std::size_t j = 0; j < m; j++) + { + for (std::size_t i = 1; i < n; i++) { + proof.f.emplace_back(sigma[(j * n) + i] * x + a[(j * n) + i]); + } + } + + // Compute zA, zC + proof.z = rB * x + rA; + + // Compute zS, zV + proof.zS = s * x.exponent(uint64_t(m)); + proof.zV = v * x.exponent(uint64_t(m)); + Scalar sumS, sumV; + + Scalar x_powers(uint64_t(1)); + for (std::size_t j = 0; j < m; ++j) { + sumS += (rho_S[j] * x_powers); + sumV += (rho_V[j] * x_powers); + x_powers *= x; + } + proof.zS -= sumS; + proof.zV -= sumV; +} + +// Verify a single proof +bool Grootle::verify( + const std::vector& S, + const GroupElement& S1, + const std::vector& V, + const GroupElement& V1, + const std::size_t size, + const GrootleProof& proof) { + std::vector S1_batch = {S1}; + std::vector V1_batch = {V1}; + std::vector size_batch = {size}; + std::vector proof_batch = {proof}; + + return verify(S, S1_batch, V, V1_batch, size_batch, proof_batch); +} + +// Verify a batch of proofs +bool Grootle::verify( + const std::vector& S, + const std::vector& S1, + const std::vector& V, + const std::vector& V1, + const std::vector& sizes, + const std::vector& proofs) { + // Sanity checks + if (n < 2 || m < 2) { + LogPrintf("Verifier parameters are invalid"); + return false; + } + std::size_t M = proofs.size(); + std::size_t N = (std::size_t)pow(n, m); + + if (S.size() == 0) { + LogPrintf("Cannot have empty commitment set"); + return false; + } + if (S.size() > N) { + LogPrintf("Commitment set is too large"); + return false; + } + if (S.size() != V.size()) { + LogPrintf("Commitment set sizes do not match"); + return false; + } + if (S1.size() != M || V1.size() != M) { + LogPrintf("Invalid number of offsets provided"); + return false; + } + if (sizes.size() != M) { + LogPrintf("Invalid set size vector size"); + return false; + } + + // Check proof semantics + for (std::size_t t = 0; t < M; t++) { + GrootleProof proof = proofs[t]; + if (proof.X.size() != m || proof.X1.size() != m) { + LogPrintf("Bad proof vector size!"); + return false; + } + if (proof.f.size() != m*(n-1)) { + LogPrintf("Bad proof vector size!"); + return false; + } + } + + // Commitment binding weight; intentionally restricted range for efficiency, but must be nonzero + // NOTE: this may initialize with a PRNG, which should be sufficient for this use + std::random_device generator; + std::uniform_int_distribution distribution; + Scalar bind_weight(ZERO); + while (bind_weight == ZERO) { + bind_weight = Scalar(distribution(generator)); + } + + // Bind the commitment lists + std::vector commits; + commits.reserve(S.size()); + for (std::size_t i = 0; i < S.size(); i++) { + commits.emplace_back(S[i] + V[i]*bind_weight); + } + + // Final batch multiscalar multiplication + Scalar H_scalar; + std::vector Gi_scalars; + std::vector Hi_scalars; + std::vector commit_scalars; + Gi_scalars.resize(n*m); + Hi_scalars.resize(n*m); + commit_scalars.resize(commits.size()); + + // Set up the final batch elements + std::vector points; + std::vector scalars; + std::size_t final_size = 1 + 2*m*n + commits.size(); // F, (Gi), (Hi), (commits) + for (std::size_t t = 0; t < M; t++) { + final_size += 2 + proofs[t].X.size() + proofs[t].X1.size(); // A, B, (Gs), (Gv) + } + points.reserve(final_size); + scalars.reserve(final_size); + + // Index decomposition, which is common among all proofs + std::vector > I_; + I_.reserve(commits.size()); + I_.resize(commits.size()); + for (std::size_t i = 0; i < commits.size(); i++) { + I_[i] = decompose(i, n, m); + } + + // Process all proofs + for (std::size_t t = 0; t < M; t++) { + GrootleProof proof = proofs[t]; + + // Reconstruct the challenge + Transcript transcript("SPARK_GROOTLE"); + transcript.add("H", H); + transcript.add("Gi", Gi); + transcript.add("Hi", Hi); + transcript.add("n", Scalar(n)); + transcript.add("m", Scalar(m)); + transcript.add("S", std::vector(S.begin() + S.size() - sizes[t], S.end())); + transcript.add("S1", S1[t]); + transcript.add("V", std::vector(V.begin() + V.size() - sizes[t], V.end())); + transcript.add("V1", V1[t]); + transcript.add("A", proof.A); + transcript.add("B", proof.B); + transcript.add("X", proof.X); + transcript.add("X1", proof.X1); + Scalar x = transcript.challenge("x"); + + // Generate nonzero random verifier weights (the randomization already asserts nonzero) + Scalar w1, w2; + w1.randomize(); + w2.randomize(); + + // Reconstruct f-matrix + std::vector f_; + if (!compute_fs(proof, x, f_, n, m)) { + LogPrintf("Invalid matrix reconstruction"); + return false; + } + + // Effective set size + const std::size_t size = sizes[t]; + + // A, B (and associated commitments) + points.emplace_back(proof.A); + scalars.emplace_back(w1.negate()); + points.emplace_back(proof.B); + scalars.emplace_back(x.negate() * w1); + + H_scalar += proof.z * w1; + for (std::size_t i = 0; i < m * n; i++) { + Gi_scalars[i] += f_[i] * w1; + Hi_scalars[i] += f_[i]*(x - f_[i]) * w1; + } + + // Input sets + H_scalar += (proof.zS + bind_weight * proof.zV) * w2.negate(); + + Scalar f_sum; + Scalar f_i(uint64_t(1)); + std::vector::iterator ptr = commit_scalars.begin() + commits.size() - size; + compute_batch_fis(f_sum, f_i, m, f_, w2, ptr, ptr, ptr + size - 1, n); + + Scalar pow(uint64_t(1)); + std::vector f_part_product; + for (std::ptrdiff_t j = m - 1; j >= 0; j--) { + f_part_product.push_back(pow); + pow *= f_[j*n + I_[size - 1][j]]; + } + + Scalar x_powers(uint64_t(1)); + for (std::size_t j = 0; j < m; j++) { + Scalar fi_sum(uint64_t(0)); + for (std::size_t i = I_[size - 1][j] + 1; i < n; i++) + fi_sum += f_[j*n + i]; + pow += fi_sum * x_powers * f_part_product[m - j - 1]; + x_powers *= x; + } + + f_sum += pow; + commit_scalars[commits.size() - 1] += pow * w2; + + // S1, V1 + points.emplace_back(S1[t] + V1[t] * bind_weight); + scalars.emplace_back(f_sum * w2.negate()); + + // (X), (X1) + x_powers = Scalar(uint64_t(1)); + for (std::size_t j = 0; j < m; j++) { + points.emplace_back(proof.X[j] + proof.X1[j] * bind_weight); + scalars.emplace_back(x_powers.negate() * w2); + x_powers *= x; + } + } + + // Add common generators + points.emplace_back(H); + scalars.emplace_back(H_scalar); + for (std::size_t i = 0; i < m * n; i++) { + points.emplace_back(Gi[i]); + scalars.emplace_back(Gi_scalars[i]); + points.emplace_back(Hi[i]); + scalars.emplace_back(Hi_scalars[i]); + } + for (std::size_t i = 0; i < commits.size(); i++) { + points.emplace_back(commits[i]); + scalars.emplace_back(commit_scalars[i]); + } + + // Verify the batch + secp_primitives::MultiExponent result(points, scalars); + if (result.get_multiple().isInfinity()) { + return true; + } + return false; +} + +} \ No newline at end of file diff --git a/src/libspark/grootle.h b/src/libspark/grootle.h new file mode 100644 index 0000000000..d8b03d7d2d --- /dev/null +++ b/src/libspark/grootle.h @@ -0,0 +1,53 @@ +#ifndef FIRO_LIBSPARK_GROOTLE_H +#define FIRO_LIBSPARK_GROOTLE_H + +#include "grootle_proof.h" +#include +#include +#include "util.h" + +namespace spark { + +class Grootle { + +public: + Grootle( + const GroupElement& H, + const std::vector& Gi, + const std::vector& Hi, + const std::size_t n, + const std::size_t m + ); + + void prove(const std::size_t l, + const Scalar& s, + const std::vector& S, + const GroupElement& S1, + const Scalar& v, + const std::vector& V, + const GroupElement& V1, + GrootleProof& proof); + bool verify(const std::vector& S, + const GroupElement& S1, + const std::vector& V, + const GroupElement& V1, + const std::size_t size, + const GrootleProof& proof); // single proof + bool verify(const std::vector& S, + const std::vector& S1, + const std::vector& V, + const std::vector& V1, + const std::vector& sizes, + const std::vector& proofs); // batch of proofs + +private: + GroupElement H; + std::vector Gi; + std::vector Hi; + std::size_t n; + std::size_t m; +}; + +} + +#endif diff --git a/src/libspark/grootle_proof.h b/src/libspark/grootle_proof.h new file mode 100644 index 0000000000..3530f7e343 --- /dev/null +++ b/src/libspark/grootle_proof.h @@ -0,0 +1,45 @@ +#ifndef FIRO_LIBSPARK_GROOTLE_PROOF_H +#define FIRO_LIBSPARK_GROOTLE_PROOF_H + +#include "params.h" + +namespace spark { + +class GrootleProof { +public: + + inline std::size_t memoryRequired() const { + return 2*GroupElement::memoryRequired() + X.size()*GroupElement::memoryRequired() + X1.size()*GroupElement::memoryRequired() + f.size()*Scalar::memoryRequired() + 3*Scalar::memoryRequired(); + } + + inline std::size_t memoryRequired(int n, int m) const { + return 2*GroupElement::memoryRequired() + 2*m*GroupElement::memoryRequired() + m*(n-1)*Scalar::memoryRequired() + 3*Scalar::memoryRequired(); + } + + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(A); + READWRITE(B); + READWRITE(X); + READWRITE(X1); + READWRITE(f); + READWRITE(z); + READWRITE(zS); + READWRITE(zV); + } + +public: + GroupElement A; + GroupElement B; + std::vector X; + std::vector X1; + std::vector f; + Scalar z; + Scalar zS; + Scalar zV; +}; + +} + +#endif diff --git a/src/libspark/hash.cpp b/src/libspark/hash.cpp new file mode 100644 index 0000000000..050b8e1c8d --- /dev/null +++ b/src/libspark/hash.cpp @@ -0,0 +1,148 @@ +#include "hash.h" + +namespace spark { + +using namespace secp_primitives; + +// Set up a labeled hash function +Hash::Hash(const std::string label) { + this->ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(this->ctx, EVP_blake2b512(), NULL); + + // Write the protocol and mode information + std::vector protocol(LABEL_PROTOCOL.begin(), LABEL_PROTOCOL.end()); + EVP_DigestUpdate(this->ctx, protocol.data(), protocol.size()); + EVP_DigestUpdate(this->ctx, &HASH_MODE_FUNCTION, sizeof(HASH_MODE_FUNCTION)); + + // Include the label with size + include_size(label.size()); + std::vector label_bytes(label.begin(), label.end()); + EVP_DigestUpdate(this->ctx, label_bytes.data(), label_bytes.size()); +} + +// Clean up +Hash::~Hash() { + EVP_MD_CTX_free(this->ctx); +} + +// Include serialized data in the hash function +void Hash::include(CDataStream& data) { + include_size(data.size()); + EVP_DigestUpdate(this->ctx, reinterpret_cast(data.data()), data.size()); +} + +// Finalize the hash function to a scalar +Scalar Hash::finalize_scalar() { + // Ensure we can properly populate a scalar + if (EVP_MD_size(EVP_blake2b512()) < SCALAR_ENCODING) { + throw std::runtime_error("Bad hash size!"); + } + + std::vector hash; + hash.resize(EVP_MD_size(EVP_blake2b512())); + unsigned char counter = 0; + + EVP_MD_CTX* state_counter; + state_counter = EVP_MD_CTX_new(); + EVP_DigestInit_ex(state_counter, EVP_blake2b512(), NULL); + + EVP_MD_CTX* state_finalize; + state_finalize = EVP_MD_CTX_new(); + EVP_DigestInit_ex(state_finalize, EVP_blake2b512(), NULL); + + while (1) { + // Prepare temporary state for counter testing + EVP_MD_CTX_copy_ex(state_counter, this->ctx); + + // Embed the counter + EVP_DigestUpdate(state_counter, &counter, sizeof(counter)); + + // Finalize the hash with a temporary state + EVP_MD_CTX_copy_ex(state_finalize, state_counter); + unsigned int TEMP; // We already know the digest length! + EVP_DigestFinal_ex(state_finalize, hash.data(), &TEMP); + + // Check for scalar validity + Scalar candidate; + try { + candidate.deserialize(hash.data()); + + EVP_MD_CTX_free(state_counter); + EVP_MD_CTX_free(state_finalize); + + return candidate; + } catch (...) { + counter++; + } + } +} + +// Finalize the hash function to a group element +GroupElement Hash::finalize_group() { + const int GROUP_ENCODING = 34; + const unsigned char ZERO = 0; + + // Ensure we can properly populate a + if (EVP_MD_size(EVP_blake2b512()) < GROUP_ENCODING) { + throw std::runtime_error("Bad hash size!"); + } + + std::vector hash; + hash.resize(EVP_MD_size(EVP_blake2b512())); + unsigned char counter = 0; + + EVP_MD_CTX* state_counter; + state_counter = EVP_MD_CTX_new(); + EVP_DigestInit_ex(state_counter, EVP_blake2b512(), NULL); + + EVP_MD_CTX* state_finalize; + state_finalize = EVP_MD_CTX_new(); + EVP_DigestInit_ex(state_finalize, EVP_blake2b512(), NULL); + + while (1) { + // Prepare temporary state for counter testing + EVP_MD_CTX_copy_ex(state_counter, this->ctx); + + // Embed the counter + EVP_DigestUpdate(state_counter, &counter, sizeof(counter)); + + // Finalize the hash with a temporary state + EVP_MD_CTX_copy_ex(state_finalize, state_counter); + unsigned int TEMP; // We already know the digest length! + EVP_DigestFinal_ex(state_finalize, hash.data(), &TEMP); + + // Assemble the serialized input: + // bytes 0..31: x coordinate + // byte 32: even/odd + // byte 33: zero (this point is not infinity) + unsigned char candidate_bytes[GROUP_ENCODING]; + memcpy(candidate_bytes, hash.data(), 33); + memcpy(candidate_bytes + 33, &ZERO, 1); + GroupElement candidate; + try { + candidate.deserialize(candidate_bytes); + + // Deserialization can succeed even with an invalid result + if (!candidate.isMember()) { + counter++; + continue; + } + + EVP_MD_CTX_free(state_counter); + EVP_MD_CTX_free(state_finalize); + + return candidate; + } catch (...) { + counter++; + } + } +} + +// Include a serialized size in the hash function +void Hash::include_size(std::size_t size) { + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << size; + EVP_DigestUpdate(this->ctx, reinterpret_cast(stream.data()), stream.size()); +} + +} \ No newline at end of file diff --git a/src/libspark/hash.h b/src/libspark/hash.h new file mode 100644 index 0000000000..dfd63ccb5e --- /dev/null +++ b/src/libspark/hash.h @@ -0,0 +1,25 @@ +#ifndef FIRO_SPARK_HASH_H +#define FIRO_SPARK_HASH_H +#include +#include "util.h" + +namespace spark { + +using namespace secp_primitives; + +class Hash { +public: + Hash(const std::string label); + ~Hash(); + void include(CDataStream& data); + Scalar finalize_scalar(); + GroupElement finalize_group(); + +private: + void include_size(std::size_t size); + EVP_MD_CTX* ctx; +}; + +} + +#endif diff --git a/src/libspark/kdf.cpp b/src/libspark/kdf.cpp new file mode 100644 index 0000000000..920974e976 --- /dev/null +++ b/src/libspark/kdf.cpp @@ -0,0 +1,57 @@ +#include "kdf.h" + +namespace spark { + +// Set up a labeled KDF +KDF::KDF(const std::string label) { + this->ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(this->ctx, EVP_blake2b512(), NULL); + + // Write the protocol and mode information + std::vector protocol(LABEL_PROTOCOL.begin(), LABEL_PROTOCOL.end()); + EVP_DigestUpdate(this->ctx, protocol.data(), protocol.size()); + EVP_DigestUpdate(this->ctx, &HASH_MODE_KDF, sizeof(HASH_MODE_KDF)); + + // Include the label with size + include_size(label.size()); + std::vector label_bytes(label.begin(), label.end()); + EVP_DigestUpdate(this->ctx, label_bytes.data(), label_bytes.size()); +} + +// Clean up +KDF::~KDF() { + EVP_MD_CTX_free(this->ctx); +} + +// Include serialized data in the KDF +void KDF::include(CDataStream& data) { + include_size(data.size()); + EVP_DigestUpdate(this->ctx, reinterpret_cast(data.data()), data.size()); +} + +// Finalize the KDF with arbitrary size +std::vector KDF::finalize(std::size_t size) { + // Assert valid size + const std::size_t hash_size = EVP_MD_size(EVP_blake2b512()); + if (size > hash_size) { + throw std::invalid_argument("Requested KDF size is too large"); + } + + std::vector result; + result.resize(hash_size); + + unsigned int TEMP; + EVP_DigestFinal_ex(this->ctx, result.data(), &TEMP); + result.resize(size); + + return result; +} + +// Include a serialized size in the KDF +void KDF::include_size(std::size_t size) { + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << size; + EVP_DigestUpdate(this->ctx, reinterpret_cast(stream.data()), stream.size()); +} + +} \ No newline at end of file diff --git a/src/libspark/kdf.h b/src/libspark/kdf.h new file mode 100644 index 0000000000..1c5348b0e6 --- /dev/null +++ b/src/libspark/kdf.h @@ -0,0 +1,22 @@ +#ifndef FIRO_SPARK_KDF_H +#define FIRO_SPARK_KDF_H +#include +#include "util.h" + +namespace spark { + +class KDF { +public: + KDF(const std::string label); + ~KDF(); + void include(CDataStream& data); + std::vector finalize(std::size_t size); + +private: + void include_size(std::size_t size); + EVP_MD_CTX* ctx; +}; + +} + +#endif diff --git a/src/libspark/keys.cpp b/src/libspark/keys.cpp new file mode 100644 index 0000000000..58047c4cbe --- /dev/null +++ b/src/libspark/keys.cpp @@ -0,0 +1,118 @@ +#include "keys.h" + +namespace spark { + +using namespace secp_primitives; + +SpendKey::SpendKey() {} +SpendKey::SpendKey(const Params* params) { + this->params = params; + this->s1.randomize(); + this->s2.randomize(); + this->r.randomize(); +} + +const Params* SpendKey::get_params() const { + return this->params; +} + +const Scalar& SpendKey::get_s1() const { + return this->s1; +} + +const Scalar& SpendKey::get_s2() const { + return this->s2; +} + +const Scalar& SpendKey::get_r() const { + return this->r; +} + +FullViewKey::FullViewKey() {} +FullViewKey::FullViewKey(const SpendKey& spend_key) { + this->params = spend_key.get_params(); + this->s1 = spend_key.get_s1(); + this->s2 = spend_key.get_s2(); + this->D = this->params->get_G()*spend_key.get_r(); + this->P2 = this->params->get_F()*this->s2 + this->D; +} + +const Params* FullViewKey::get_params() const { + return this->params; +} + +const Scalar& FullViewKey::get_s1() const { + return this->s1; +} + +const Scalar& FullViewKey::get_s2() const { + return this->s2; +} + +const GroupElement& FullViewKey::get_D() const { + return this->D; +} + +const GroupElement& FullViewKey::get_P2() const { + return this->P2; +} + +IncomingViewKey::IncomingViewKey() {} +IncomingViewKey::IncomingViewKey(const FullViewKey& full_view_key) { + this->params = full_view_key.get_params(); + this->s1 = full_view_key.get_s1(); + this->P2 = full_view_key.get_P2(); +} + +const Params* IncomingViewKey::get_params() const { + return this->params; +} + +const Scalar& IncomingViewKey::get_s1() const { + return this->s1; +} + +const GroupElement& IncomingViewKey::get_P2() const { + return this->P2; +} + +uint64_t IncomingViewKey::get_diversifier(const std::vector& d) const { + // Assert proper size + if (d.size() != AES_BLOCKSIZE) { + throw std::invalid_argument("Bad encrypted diversifier"); + } + + // Decrypt the diversifier; this is NOT AUTHENTICATED and MUST be externally checked for validity against a claimed address + std::vector key = SparkUtils::kdf_diversifier(this->s1); + uint64_t i = SparkUtils::diversifier_decrypt(key, d); + + return i; +} + +Address::Address() {} +Address::Address(const IncomingViewKey& incoming_view_key, const uint64_t i) { + // Encrypt the diversifier + std::vector key = SparkUtils::kdf_diversifier(incoming_view_key.get_s1()); + this->params = incoming_view_key.get_params(); + this->d = SparkUtils::diversifier_encrypt(key, i); + this->Q1 = SparkUtils::hash_div(this->d)*incoming_view_key.get_s1(); + this->Q2 = this->params->get_F()*SparkUtils::hash_Q2(incoming_view_key.get_s1(), i) + incoming_view_key.get_P2(); +} + +const Params* Address::get_params() const { + return this->params; +} + +const std::vector& Address::get_d() const { + return this->d; +} + +const GroupElement& Address::get_Q1() const { + return this->Q1; +} + +const GroupElement& Address::get_Q2() const { + return this->Q2; +} + +} diff --git a/src/libspark/keys.h b/src/libspark/keys.h new file mode 100644 index 0000000000..1f34c80bc9 --- /dev/null +++ b/src/libspark/keys.h @@ -0,0 +1,72 @@ +#ifndef FIRO_SPARK_KEYS_H +#define FIRO_SPARK_KEYS_H +#include "params.h" +#include "util.h" + +namespace spark { + +using namespace secp_primitives; + +class SpendKey { +public: + SpendKey(); + SpendKey(const Params* params); + const Params* get_params() const; + const Scalar& get_s1() const; + const Scalar& get_s2() const; + const Scalar& get_r() const; + +private: + const Params* params; + Scalar s1, s2, r; +}; + +class FullViewKey { +public: + FullViewKey(); + FullViewKey(const SpendKey& spend_key); + const Params* get_params() const; + const Scalar& get_s1() const; + const Scalar& get_s2() const; + const GroupElement& get_D() const; + const GroupElement& get_P2() const; + +private: + const Params* params; + Scalar s1, s2; + GroupElement D, P2; +}; + +class IncomingViewKey { +public: + IncomingViewKey(); + IncomingViewKey(const FullViewKey& full_view_key); + const Params* get_params() const; + const Scalar& get_s1() const; + const GroupElement& get_P2() const; + uint64_t get_diversifier(const std::vector& d) const; + +private: + const Params* params; + Scalar s1; + GroupElement P2; +}; + +class Address { +public: + Address(); + Address(const IncomingViewKey& incoming_view_key, const uint64_t i); + const Params* get_params() const; + const std::vector& get_d() const; + const GroupElement& get_Q1() const; + const GroupElement& get_Q2() const; + +private: + const Params* params; + std::vector d; + GroupElement Q1, Q2; +}; + +} + +#endif diff --git a/src/libspark/mint_transaction.cpp b/src/libspark/mint_transaction.cpp new file mode 100644 index 0000000000..7c99e6f498 --- /dev/null +++ b/src/libspark/mint_transaction.cpp @@ -0,0 +1,43 @@ +#include "mint_transaction.h" + +namespace spark { + +MintTransaction::MintTransaction( + const Params* params, + const Address& address, + uint64_t v, + const std::string memo +) { + this->params = params; + + // Generate the coin + Scalar k; + k.randomize(); + this->coin = Coin( + this->params, + COIN_TYPE_MINT, + k, + address, + v, + memo + ); + + // Generate the balance proof + Schnorr schnorr(this->params->get_H()); + schnorr.prove( + SparkUtils::hash_val(k), + this->coin.C + this->params->get_G().inverse()*Scalar(v), + this->balance_proof + ); +} + +bool MintTransaction::verify() { + // Verify the balance proof + Schnorr schnorr(this->params->get_H()); + return schnorr.verify( + this->coin.C + this->params->get_G().inverse()*Scalar(this->coin.v), + this->balance_proof + ); +} + +} diff --git a/src/libspark/mint_transaction.h b/src/libspark/mint_transaction.h new file mode 100644 index 0000000000..7e14d45138 --- /dev/null +++ b/src/libspark/mint_transaction.h @@ -0,0 +1,30 @@ +#ifndef FIRO_SPARK_MINT_TRANSACTION_H +#define FIRO_SPARK_MINT_TRANSACTION_H +#include "keys.h" +#include "coin.h" +#include "schnorr.h" +#include "util.h" + +namespace spark { + +using namespace secp_primitives; + +class MintTransaction { +public: + MintTransaction( + const Params* params, + const Address& address, + uint64_t v, + const std::string memo + ); + bool verify(); + +private: + const Params* params; + Coin coin; + SchnorrProof balance_proof; +}; + +} + +#endif diff --git a/src/libspark/params.cpp b/src/libspark/params.cpp new file mode 100644 index 0000000000..a3ddb8392b --- /dev/null +++ b/src/libspark/params.cpp @@ -0,0 +1,137 @@ +#include "params.h" +#include "chainparams.h" +#include "util.h" + +namespace spark { + + CCriticalSection Params::cs_instance; + std::unique_ptr Params::instance; + +// Protocol parameters for deployment +Params const* Params::get_default() { + if (instance) { + return instance.get(); + } else { + LOCK(cs_instance); + if (instance) { + return instance.get(); + } + + std::size_t memo_bytes = 32; + std::size_t max_M_range = 16; + std::size_t n_grootle = 16; + std::size_t m_grootle = 4; + + instance.reset(new Params(memo_bytes, max_M_range, n_grootle, m_grootle)); + return instance.get(); + } +} + +// Protocol parameters for testing +Params const* Params::get_test() { + if (instance) { + return instance.get(); + } else { + LOCK(cs_instance); + if (instance) { + return instance.get(); + } + + std::size_t memo_bytes = 32; + std::size_t max_M_range = 16; + std::size_t n_grootle = 2; + std::size_t m_grootle = 4; + + instance.reset(new Params(memo_bytes, max_M_range, n_grootle, m_grootle)); + return instance.get(); + } +} + +Params::Params( + const std::size_t memo_bytes, + const std::size_t max_M_range, + const std::size_t n_grootle, + const std::size_t m_grootle +) +{ + // Global generators + this->F = SparkUtils::hash_generator(LABEL_GENERATOR_F); + this->G = SparkUtils::hash_generator(LABEL_GENERATOR_G); + this->H = SparkUtils::hash_generator(LABEL_GENERATOR_H); + this->U = SparkUtils::hash_generator(LABEL_GENERATOR_U); + + // Coin parameters + this->memo_bytes = memo_bytes; + + // Range proof parameters + this->max_M_range = max_M_range; + this->G_range.resize(64*max_M_range); + this->H_range.resize(64*max_M_range); + for (std::size_t i = 0; i < 64*max_M_range; i++) { + this->G_range[i] = SparkUtils::hash_generator(LABEL_GENERATOR_G_RANGE + " " + std::to_string(i)); + this->H_range[i] = SparkUtils::hash_generator(LABEL_GENERATOR_H_RANGE + " " + std::to_string(i)); + } + + // One-of-many parameters + if (n_grootle < 2 || m_grootle < 3) { + throw std::invalid_argument("Bad Grootle parameteres"); + } + this->n_grootle = n_grootle; + this->m_grootle = m_grootle; + this->G_grootle.resize(n_grootle * m_grootle); + this->H_grootle.resize(n_grootle * m_grootle); + for (std::size_t i = 0; i < n_grootle * m_grootle; i++) { + this->G_grootle[i] = SparkUtils::hash_generator(LABEL_GENERATOR_G_GROOTLE + " " + std::to_string(i)); + this->H_grootle[i] = SparkUtils::hash_generator(LABEL_GENERATOR_H_GROOTLE + " " + std::to_string(i)); + } +} + +const GroupElement& Params::get_F() const { + return this->F; +} + +const GroupElement& Params::get_G() const { + return this->G; +} + +const GroupElement& Params::get_H() const { + return this->H; +} + +const GroupElement& Params::get_U() const { + return this->U; +} + +const std::size_t Params::get_memo_bytes() const { + return this->memo_bytes; +} + +const std::vector& Params::get_G_range() const { + return this->G_range; +} + +const std::vector& Params::get_H_range() const { + return this->H_range; +} + +const std::vector& Params::get_G_grootle() const { + return this->G_grootle; +} + +const std::vector& Params::get_H_grootle() const { + return this->H_grootle; +} + +std::size_t Params::get_max_M_range() const { + return this->max_M_range; +} + +std::size_t Params::get_n_grootle() const { + return this->n_grootle; +} + +std::size_t Params::get_m_grootle() const { + return this->m_grootle; +} + +} diff --git a/src/libspark/params.h b/src/libspark/params.h new file mode 100644 index 0000000000..e37855dab4 --- /dev/null +++ b/src/libspark/params.h @@ -0,0 +1,67 @@ +#ifndef FIRO_LIBSPARK_PARAMS_H +#define FIRO_LIBSPARK_PARAMS_H + +#include +#include +#include +#include + +using namespace secp_primitives; + +namespace spark { + +class Params { +public: + static Params const* get_default(); + static Params const* get_test(); + + const GroupElement& get_F() const; + const GroupElement& get_G() const; + const GroupElement& get_H() const; + const GroupElement& get_U() const; + + const std::size_t get_memo_bytes() const; + + std::size_t get_max_M_range() const; + const std::vector& get_G_range() const; + const std::vector& get_H_range() const; + + std::size_t get_n_grootle() const; + std::size_t get_m_grootle() const; + const std::vector& get_G_grootle() const; + const std::vector& get_H_grootle() const; + +private: + Params( + const std::size_t memo_bytes, + const std::size_t max_M_range, + const std::size_t n_grootle, + const std::size_t m_grootle + ); + +private: + static CCriticalSection cs_instance; + static std::unique_ptr instance; + + // Global generators + GroupElement F; + GroupElement G; + GroupElement H; + GroupElement U; + + // Coin parameters + std::size_t memo_bytes; + + // Range proof parameters + std::size_t max_M_range; + std::vector G_range, H_range; + + // One-of-many parameters + std::size_t n_grootle, m_grootle; + std::vector G_grootle; + std::vector H_grootle; +}; + +} + +#endif diff --git a/src/libspark/schnorr.cpp b/src/libspark/schnorr.cpp new file mode 100644 index 0000000000..d7ffce9b10 --- /dev/null +++ b/src/libspark/schnorr.cpp @@ -0,0 +1,40 @@ +#include "schnorr.h" +#include "transcript.h" + +namespace spark { + +Schnorr::Schnorr(const GroupElement& G_): + G(G_) { +} + +Scalar Schnorr::challenge( + const GroupElement& Y, + const GroupElement& A) { + Transcript transcript("SPARK_SCHNORR"); + transcript.add("G", G); + transcript.add("Y", Y); + transcript.add("A", A); + + return transcript.challenge("c"); +} + +void Schnorr::prove(const Scalar& y, const GroupElement& Y, SchnorrProof& proof) { + // Check statement validity + if (!(G*y == Y)) { + throw std::invalid_argument("Bad Schnorr statement!"); + } + + Scalar r; + r.randomize(); + GroupElement A = G*r; + proof.c = challenge(Y, A); + proof.t = r + proof.c*y; +} + +bool Schnorr::verify(const GroupElement& Y, SchnorrProof& proof) { + Scalar c = challenge(Y, G*proof.t + Y.inverse()*proof.c); + + return c == proof.c; +} + +} diff --git a/src/libspark/schnorr.h b/src/libspark/schnorr.h new file mode 100644 index 0000000000..7cc40b0df1 --- /dev/null +++ b/src/libspark/schnorr.h @@ -0,0 +1,22 @@ +#ifndef FIRO_LIBSPARK_SCHNORR_H +#define FIRO_LIBSPARK_SCHNORR_H + +#include "schnorr_proof.h" + +namespace spark { + +class Schnorr { +public: + Schnorr(const GroupElement& G); + + void prove(const Scalar& y, const GroupElement& Y, SchnorrProof& proof); + bool verify(const GroupElement& Y, SchnorrProof& proof); + +private: + Scalar challenge(const GroupElement& Y, const GroupElement& A); + const GroupElement& G; +}; + +} + +#endif diff --git a/src/libspark/schnorr_proof.h b/src/libspark/schnorr_proof.h new file mode 100644 index 0000000000..c5fa96c776 --- /dev/null +++ b/src/libspark/schnorr_proof.h @@ -0,0 +1,27 @@ +#ifndef FIRO_LIBSPARK_SCHNORR_PROOF_H +#define FIRO_LIBSPARK_SCHNORR_PROOF_H + +#include "params.h" + +namespace spark { + +class SchnorrProof{ +public: + inline std::size_t memoryRequired() const { + return 2*Scalar::memoryRequired(); + } + + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(c); + READWRITE(t); + } + +public: + Scalar c; + Scalar t; +}; +} + +#endif diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp new file mode 100644 index 0000000000..dc7585b4ac --- /dev/null +++ b/src/libspark/spend_transaction.cpp @@ -0,0 +1,317 @@ +#include "spend_transaction.h" + +namespace spark { + +SpendTransaction::SpendTransaction( + const Params* params, + const FullViewKey& full_view_key, + const SpendKey& spend_key, + const std::vector& in_coins, + const std::vector& inputs, + const uint64_t f, + const std::vector& outputs +) { + this->params = params; + + // Size parameters + const std::size_t w = inputs.size(); // number of consumed coins + const std::size_t t = outputs.size(); // number of generated coins + const std::size_t N = in_coins.size(); // size of cover set + + // Prepare input-related vectors + this->in_coins = in_coins; // input cover set + this->S1.reserve(w); // serial commitment offsets + this->C1.reserve(w); // value commitment offsets + this->grootle_proofs.reserve(w); // Grootle one-of-many proofs + this->T.reserve(w); // linking tags + + this->f = f; // fee + + // Prepare Chaum vectors + std::vector chaum_x, chaum_y, chaum_z; + + // Prepare output vector + this->out_coins.reserve(t); // coins + std::vector k; // nonces + + // Parse out serial and value commitments from the cover set for use in proofs + std::vector S, C; + S.resize(N); + C.resize(N); + for (std::size_t i = 0; i < N; i++) { + S[i] = in_coins[i].S; + C[i] = in_coins[i].C; + } + + // Prepare inputs + Grootle grootle( + this->params->get_H(), + this->params->get_G_grootle(), + this->params->get_H_grootle(), + this->params->get_n_grootle(), + this->params->get_m_grootle() + ); + for (std::size_t u = 0; u < w; u++) { + // Serial commitment offset + this->S1.emplace_back( + this->params->get_F()*inputs[u].s + + this->params->get_H().inverse()*SparkUtils::hash_ser1(inputs[u].s, full_view_key.get_D()) + + full_view_key.get_D() + ); + + // Value commitment offset + this->C1.emplace_back( + this->params->get_G()*Scalar(inputs[u].v) + + this->params->get_H()*SparkUtils::hash_val1(inputs[u].s, full_view_key.get_D()) + ); + + // Tags + this->T.emplace_back(inputs[u].T); + + // Grootle proof + this->grootle_proofs.emplace_back(); + std::size_t l = inputs[u].index; + grootle.prove( + l, + SparkUtils::hash_ser1(inputs[u].s, full_view_key.get_D()), + S, + this->S1.back(), + SparkUtils::hash_val(inputs[u].k) - SparkUtils::hash_val1(inputs[u].s, full_view_key.get_D()), + C, + this->C1.back(), + this->grootle_proofs.back() + ); + + // Chaum data + chaum_x.emplace_back(inputs[u].s); + chaum_y.emplace_back(spend_key.get_r()); + chaum_z.emplace_back(SparkUtils::hash_ser1(inputs[u].s, full_view_key.get_D()).negate()); + } + + // Generate output coins and prepare range proof vectors + std::vector range_v; + std::vector range_r; + std::vector range_C; + for (std::size_t j = 0; j < t; j++) { + // Nonce + k.emplace_back(); + k.back().randomize(); + + // Output coin + this->out_coins.emplace_back(); + this->out_coins.back() = Coin( + this->params, + COIN_TYPE_SPEND, + k.back(), + outputs[j].address, + outputs[j].v, + outputs[j].memo + ); + + // Range data + range_v.emplace_back(outputs[j].v); + range_r.emplace_back(SparkUtils::hash_val(k.back())); + range_C.emplace_back(this->out_coins.back().C); + } + + // Generate range proof + BPPlus range( + this->params->get_G(), + this->params->get_H(), + this->params->get_G_range(), + this->params->get_H_range(), + 64 + ); + range.prove( + range_v, + range_r, + range_C, + this->range_proof + ); + + // Generate the balance proof + Schnorr schnorr(this->params->get_H()); + GroupElement balance_statement; + Scalar balance_witness; + for (std::size_t u = 0; u < w; u++) { + balance_statement += this->C1[u]; + balance_witness += SparkUtils::hash_val1(inputs[u].s, full_view_key.get_D()); + } + for (std::size_t j = 0; j < t; j++) { + balance_statement += this->out_coins[j].C.inverse(); + balance_witness -= SparkUtils::hash_val(k[j]); + } + balance_statement += this->params->get_G()*Scalar(f); + schnorr.prove( + balance_witness, + balance_statement, + this->balance_proof + ); + + // Compute the binding hash + Scalar mu = hash_bind( + this->in_coins, + this->out_coins, + this->f, + this->S1, + this->C1, + this->T, + this->grootle_proofs, + this->balance_proof, + this->range_proof + ); + + // Compute the authorizing Chaum proof + Chaum chaum( + this->params->get_F(), + this->params->get_G(), + this->params->get_H(), + this->params->get_U() + ); + chaum.prove( + mu, + chaum_x, + chaum_y, + chaum_z, + this->S1, + this->T, + this->chaum_proof + ); +} + +bool SpendTransaction::verify() { + // Size parameters + const std::size_t w = this->grootle_proofs.size(); + const std::size_t t = this->out_coins.size(); + const std::size_t N = this->in_coins.size(); + + // Semantics + if (this->S1.size() != w || this->C1.size() != w || this->T.size() != w) { + throw std::invalid_argument("Bad spend transaction semantics"); + } + if (N > (std::size_t)pow(this->params->get_n_grootle(), this->params->get_m_grootle())) { + throw std::invalid_argument("Bad spend transaction semantics"); + } + + // Parse out serial and value commitments from the cover set for use in proofs + std::vector S, C; + S.resize(N); + C.resize(N); + for (std::size_t i = 0; i < N; i++) { + S[i] = this->in_coins[i].S; + C[i] = this->in_coins[i].C; + } + + // Parse out value commitments from the output set for use in proofs + std::vector C_out; + C_out.resize(t); + for (std::size_t j = 0; j < t; j++) { + C_out[j] = this->out_coins[j].C; + } + + // Consumed coins + Grootle grootle( + this->params->get_H(), + this->params->get_G_grootle(), + this->params->get_H_grootle(), + this->params->get_n_grootle(), + this->params->get_m_grootle() + ); + + // Verify all Grootle proofs in a batch + std::vector sizes; + for (std::size_t u = 0; u < w; u++) { + sizes.emplace_back(N); + } + if (!grootle.verify(S, this->S1, C, this->C1, sizes, this->grootle_proofs)) { + return false; + } + + // Compute the binding hash + Scalar mu = hash_bind( + this->in_coins, + this->out_coins, + this->f, + this->S1, + this->C1, + this->T, + this->grootle_proofs, + this->balance_proof, + this->range_proof + ); + + // Verify the authorizing Chaum proof + Chaum chaum( + this->params->get_F(), + this->params->get_G(), + this->params->get_H(), + this->params->get_U() + ); + if (!chaum.verify(mu, this->S1, this->T, this->chaum_proof)) { + return false; + } + + // Verify the aggregated range proof + BPPlus range( + this->params->get_G(), + this->params->get_H(), + this->params->get_G_range(), + this->params->get_H_range(), + 64 + ); + if (!range.verify(C_out, this->range_proof)) { + return false; + } + + // Verify the balance proof + Schnorr schnorr(this->params->get_H()); + GroupElement balance_statement; + for (std::size_t u = 0; u < w; u++) { + balance_statement += this->C1[u]; + } + for (std::size_t j = 0; j < t; j++) { + balance_statement += this->out_coins[j].C.inverse(); + } + balance_statement += this->params->get_G()*Scalar(this->f); + if(!schnorr.verify( + balance_statement, + this->balance_proof + )) { + return false; + } + + return true; +} + +// Hash-to-scalar function H_bind +Scalar SpendTransaction::hash_bind( + const std::vector& in_coins, + const std::vector& out_coins, + const uint64_t f, + const std::vector& S1, + const std::vector& C1, + const std::vector& T, + const std::vector& grootle_proofs, + const SchnorrProof& balance_proof, + const BPPlusProof& range_proof +) { + Hash hash(LABEL_HASH_BIND); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + + // Perform the serialization and hashing + stream << in_coins; + stream << out_coins; + stream << f; + stream << S1; + stream << C1; + stream << T; + stream << grootle_proofs; + stream << balance_proof; + stream << range_proof; + hash.include(stream); + + return hash.finalize_scalar(); +} + +} diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h new file mode 100644 index 0000000000..7110058eda --- /dev/null +++ b/src/libspark/spend_transaction.h @@ -0,0 +1,68 @@ +#ifndef FIRO_SPARK_SPEND_TRANSACTION_H +#define FIRO_SPARK_SPEND_TRANSACTION_H +#include "keys.h" +#include "coin.h" +#include "schnorr.h" +#include "util.h" +#include "grootle.h" +#include "bpplus.h" +#include "chaum.h" + +namespace spark { + +using namespace secp_primitives; + +struct InputCoinData { + std::size_t index; // index in cover set + Scalar s; // serial number + GroupElement T; // tag + uint64_t v; // value + Scalar k; // nonce +}; + +struct OutputCoinData { + Address address; + uint64_t v; + std::string memo; +}; + +class SpendTransaction { +public: + SpendTransaction( + const Params* params, + const FullViewKey& full_view_key, + const SpendKey& spend_key, + const std::vector& in_coins, + const std::vector& inputs, + const uint64_t f, + const std::vector& outputs + ); + bool verify(); + + static Scalar hash_bind( + const std::vector& in_coins, + const std::vector& out_coins, + const uint64_t f, + const std::vector& S1, + const std::vector& C1, + const std::vector& T, + const std::vector& grootle_proofs, + const SchnorrProof& balance_proof, + const BPPlusProof& range_proof + ); + +private: + const Params* params; + std::vector in_coins; + std::vector out_coins; + uint64_t f; + std::vector S1, C1, T; + std::vector grootle_proofs; + ChaumProof chaum_proof; + SchnorrProof balance_proof; + BPPlusProof range_proof; +}; + +} + +#endif diff --git a/src/libspark/test/aead_test.cpp b/src/libspark/test/aead_test.cpp new file mode 100644 index 0000000000..975fa495db --- /dev/null +++ b/src/libspark/test/aead_test.cpp @@ -0,0 +1,128 @@ +#include "../aead.h" + +#include "../../test/test_bitcoin.h" +#include + +namespace spark { + +BOOST_FIXTURE_TEST_SUITE(spark_aead_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(complete) +{ + // Key + std::string key_string = "Key prefix"; + std::vector key(key_string.begin(), key_string.end()); + key.resize(AEAD_KEY_SIZE); + + // Serialize + int message = 12345; + CDataStream ser(SER_NETWORK, PROTOCOL_VERSION); + ser << message; + + // Encrypt + AEADEncryptedData data = AEAD::encrypt(key, "Associated data", ser); + + // Decrypt + ser = AEAD::decrypt_and_verify(key, "Associated data", data); + + // Deserialize + int message_; + ser >> message_; + + BOOST_CHECK_EQUAL(message_, message); +} + +BOOST_AUTO_TEST_CASE(bad_tag) +{ + // Key + std::string key_string = "Key prefix"; + std::vector key(key_string.begin(), key_string.end()); + key.resize(AEAD_KEY_SIZE); + + // Serialize and encrypt a message + int message = 12345; + CDataStream ser(SER_NETWORK, PROTOCOL_VERSION); + ser << message; + AEADEncryptedData data = AEAD::encrypt(key, "Associated data", ser); + + // Serialize and encrypt an evil message + ser.clear(); + int evil_message = 666; + ser << evil_message; + AEADEncryptedData evil_data = AEAD::encrypt(key, "Associated data", ser); + + // Replace tag + data.tag = evil_data.tag; + + // Decrypt; this should fail + BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(key, "Associated data", data), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(bad_ciphertext) +{ + // Key + std::string key_string = "Key prefix"; + std::vector key(key_string.begin(), key_string.end()); + key.resize(AEAD_KEY_SIZE); + + // Serialize and encrypt a message + int message = 12345; + CDataStream ser(SER_NETWORK, PROTOCOL_VERSION); + ser << message; + AEADEncryptedData data = AEAD::encrypt(key, "Associated data", ser); + + // Serialize and encrypt an evil message + ser.clear(); + int evil_message = 666; + ser << evil_message; + AEADEncryptedData evil_data = AEAD::encrypt(key, "Associated data", ser); + + // Replace ciphertext + data.ciphertext = evil_data.ciphertext; + + // Decrypt; this should fail + BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(key, "Associated data", data), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(bad_associated_data) +{ + // Key + std::string key_string = "Key prefix"; + std::vector key(key_string.begin(), key_string.end()); + key.resize(AEAD_KEY_SIZE); + + // Serialize and encrypt a message + int message = 12345; + CDataStream ser(SER_NETWORK, PROTOCOL_VERSION); + ser << message; + AEADEncryptedData data = AEAD::encrypt(key, "Associated data", ser); + + // Decrypt; this should fail + BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(key, "Evil associated data", data), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(bad_key) +{ + // Key + std::string key_string = "Key prefix"; + std::vector key(key_string.begin(), key_string.end()); + key.resize(AEAD_KEY_SIZE); + + // Evil key + std::string evil_key_string = "Evil key prefix"; + std::vector evil_key(evil_key_string.begin(), evil_key_string.end()); + evil_key.resize(AEAD_KEY_SIZE); + + // Serialize and encrypt a message + int message = 12345; + CDataStream ser(SER_NETWORK, PROTOCOL_VERSION); + ser << message; + AEADEncryptedData data = AEAD::encrypt(key, "Associated data", ser); + + // Decrypt; this should fail + BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(evil_key, "Associated data", data), std::runtime_error); +} + +BOOST_AUTO_TEST_SUITE_END() + +} \ No newline at end of file diff --git a/src/libspark/test/bpplus_test.cpp b/src/libspark/test/bpplus_test.cpp new file mode 100644 index 0000000000..74ddb6f827 --- /dev/null +++ b/src/libspark/test/bpplus_test.cpp @@ -0,0 +1,194 @@ +#include "../bpplus.h" + +#include "../../test/test_bitcoin.h" +#include + +namespace spark { + +BOOST_FIXTURE_TEST_SUITE(spark_bpplus_tests, BasicTestingSetup) + +// Generate and verify a single aggregated proof +BOOST_AUTO_TEST_CASE(completeness_single) +{ + // Parameters + std::size_t N = 64; // bit length + std::size_t M = 4; // aggregation + + // Generators + GroupElement G, H; + G.randomize(); + H.randomize(); + + std::vector Gi, Hi; + Gi.resize(N*M); + Hi.resize(N*M); + for (std::size_t i = 0; i < N*M; i++) { + Gi[i].randomize(); + Hi[i].randomize(); + } + + // Commitments + std::vector v, r; + v.resize(M); + v[0] = Scalar(uint64_t(0)); + v[1] = Scalar(uint64_t(1)); + v[2] = Scalar(uint64_t(2)); + v[3] = Scalar(std::numeric_limits::max()); + r.resize(M); + std::vector C; + C.resize(M); + for (std::size_t j = 0; j < M; j++) { + r[j].randomize(); + C[j] = G*v[j] + H*r[j]; + } + + BPPlus bpplus(G, H, Gi, Hi, N); + BPPlusProof proof; + bpplus.prove(v, r, C, proof); + + BOOST_CHECK(bpplus.verify(C, proof)); +} + +// A single proof with invalid value +BOOST_AUTO_TEST_CASE(invalid_single) +{ + // Parameters + std::size_t N = 64; // bit length + std::size_t M = 4; // aggregation + + // Generators + GroupElement G, H; + G.randomize(); + H.randomize(); + + std::vector Gi, Hi; + Gi.resize(N*M); + Hi.resize(N*M); + for (std::size_t i = 0; i < N*M; i++) { + Gi[i].randomize(); + Hi[i].randomize(); + } + + // Commitments + std::vector v, r; + v.resize(M); + v[0] = Scalar(uint64_t(0)); + v[1] = Scalar(uint64_t(1)); + v[2] = Scalar(uint64_t(2)); + v[3] = Scalar(std::numeric_limits::max()) + Scalar(uint64_t(1)); // out of range + r.resize(M); + std::vector C; + C.resize(M); + for (std::size_t j = 0; j < M; j++) { + r[j].randomize(); + C[j] = G*v[j] + H*r[j]; + } + + BPPlus bpplus(G, H, Gi, Hi, N); + BPPlusProof proof; + bpplus.prove(v, r, C, proof); + + BOOST_CHECK(!bpplus.verify(C, proof)); +} + +// Generate and verify a batch of proofs with variable aggregation +BOOST_AUTO_TEST_CASE(completeness_batch) +{ + // Parameters + std::size_t N = 64; // bit length + std::size_t B = 4; // number of proofs in batch + + // Generators + GroupElement G, H; + G.randomize(); + H.randomize(); + + std::vector Gi, Hi; + Gi.resize(N*(1 << B)); + Hi.resize(N*(1 << B)); + for (std::size_t i = 0; i < N*(1 << B); i++) { + Gi[i].randomize(); + Hi[i].randomize(); + } + + BPPlus bpplus(G, H, Gi, Hi, N); + std::vector proofs; + proofs.resize(B); + std::vector> C; + + // Build each proof + for (std::size_t i = 0; i < B; i++) { + // Commitments + std::size_t M = 1 << i; + std::vector v, r; + v.resize(M); + r.resize(M); + std::vector C_; + C_.resize(M); + for (std::size_t j = 0; j < M; j++) { + v[j] = Scalar(uint64_t(j)); + r[j].randomize(); + C_[j] = G*v[j] + H*r[j]; + } + C.emplace_back(C_); + + bpplus.prove(v, r, C_, proofs[i]); + } + + BOOST_CHECK(bpplus.verify(C, proofs)); +} + +// An invalid batch of proofs +BOOST_AUTO_TEST_CASE(invalid_batch) +{ + // Parameters + std::size_t N = 64; // bit length + std::size_t B = 4; // number of proofs in batch + + // Generators + GroupElement G, H; + G.randomize(); + H.randomize(); + + std::vector Gi, Hi; + Gi.resize(N*(1 << B)); + Hi.resize(N*(1 << B)); + for (std::size_t i = 0; i < N*(1 << B); i++) { + Gi[i].randomize(); + Hi[i].randomize(); + } + + BPPlus bpplus(G, H, Gi, Hi, N); + std::vector proofs; + proofs.resize(B); + std::vector> C; + + // Build each proof + for (std::size_t i = 0; i < B; i++) { + // Commitments + std::size_t M = 1 << i; + std::vector v, r; + v.resize(M); + r.resize(M); + std::vector C_; + C_.resize(M); + for (std::size_t j = 0; j < M; j++) { + v[j] = Scalar(uint64_t(j)); + // Set one proof to an out-of-range value; + if (i == 0 && j == 0) { + v[j] = Scalar(std::numeric_limits::max()) + Scalar(uint64_t(1)); + } + r[j].randomize(); + C_[j] = G*v[j] + H*r[j]; + } + C.emplace_back(C_); + + bpplus.prove(v, r, C_, proofs[i]); + } + + BOOST_CHECK(!bpplus.verify(C, proofs)); +} + +BOOST_AUTO_TEST_SUITE_END() + +} diff --git a/src/libspark/test/chaum_test.cpp b/src/libspark/test/chaum_test.cpp new file mode 100644 index 0000000000..26281438bd --- /dev/null +++ b/src/libspark/test/chaum_test.cpp @@ -0,0 +1,180 @@ +#include "../chaum.h" +#include "../../streams.h" +#include "../../version.h" + +#include "../../test/test_bitcoin.h" +#include + +namespace spark { + +BOOST_FIXTURE_TEST_SUITE(spark_chaum_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(serialization) +{ + GroupElement F, G, H, U; + F.randomize(); + G.randomize(); + H.randomize(); + U.randomize(); + + const std::size_t n = 3; + + Scalar mu; + mu.randomize(); + std::vector x, y, z; + x.resize(n); + y.resize(n); + z.resize(n); + std::vector S, T; + S.resize(n); + T.resize(n); + for (std::size_t i = 0; i < n; i++) { + x[i].randomize(); + y[i].randomize(); + z[i].randomize(); + + S[i] = F*x[i] + G*y[i] + H*z[i]; + T[i] = (U + G*y[i].negate())*x[i].inverse(); + } + + ChaumProof proof; + + Chaum chaum(F, G, H, U); + chaum.prove(mu, x, y, z, S, T, proof); + + CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); + serialized << proof; + + ChaumProof deserialized; + serialized >> deserialized; + + BOOST_CHECK(proof.A1 == deserialized.A1); + BOOST_CHECK(proof.t2 == deserialized.t2); + BOOST_CHECK(proof.t3 == deserialized.t3); + for (std::size_t i = 0; i < n; i++) { + BOOST_CHECK(proof.A2[i] == deserialized.A2[i]); + BOOST_CHECK(proof.t1[i] == deserialized.t1[i]); + } +} + +BOOST_AUTO_TEST_CASE(completeness) +{ + GroupElement F, G, H, U; + F.randomize(); + G.randomize(); + H.randomize(); + U.randomize(); + + const std::size_t n = 3; + + Scalar mu; + mu.randomize(); + std::vector x, y, z; + x.resize(n); + y.resize(n); + z.resize(n); + std::vector S, T; + S.resize(n); + T.resize(n); + for (std::size_t i = 0; i < n; i++) { + x[i].randomize(); + y[i].randomize(); + z[i].randomize(); + + S[i] = F*x[i] + G*y[i] + H*z[i]; + T[i] = (U + G*y[i].negate())*x[i].inverse(); + } + + ChaumProof proof; + + Chaum chaum(F, G, H, U); + chaum.prove(mu, x, y, z, S, T, proof); + + BOOST_CHECK(chaum.verify(mu, S, T, proof)); +} + +BOOST_AUTO_TEST_CASE(bad_proofs) +{ + GroupElement F, G, H, U; + F.randomize(); + G.randomize(); + H.randomize(); + U.randomize(); + + const std::size_t n = 3; + + Scalar mu; + mu.randomize(); + std::vector x, y, z; + x.resize(n); + y.resize(n); + z.resize(n); + std::vector S, T; + S.resize(n); + T.resize(n); + for (std::size_t i = 0; i < n; i++) { + x[i].randomize(); + y[i].randomize(); + z[i].randomize(); + + S[i] = F*x[i] + G*y[i] + H*z[i]; + T[i] = (U + G*y[i].negate())*x[i].inverse(); + } + + ChaumProof proof; + + Chaum chaum(F, G, H, U); + chaum.prove(mu, x, y, z, S, T, proof); + + // Bad mu + Scalar evil_mu; + evil_mu.randomize(); + BOOST_CHECK(!(chaum.verify(evil_mu, S, T, proof))); + + // Bad S + for (std::size_t i = 0; i < n; i++) { + std::vector evil_S(S); + evil_S[i].randomize(); + BOOST_CHECK(!(chaum.verify(mu, evil_S, T, proof))); + } + + // Bad T + for (std::size_t i = 0; i < n; i++) { + std::vector evil_T(T); + evil_T[i].randomize(); + BOOST_CHECK(!(chaum.verify(mu, S, evil_T, proof))); + } + + // Bad A1 + ChaumProof evil_proof = proof; + evil_proof.A1.randomize(); + BOOST_CHECK(!(chaum.verify(mu, S, T, evil_proof))); + + // Bad A2 + for (std::size_t i = 0; i < n; i++) { + evil_proof = proof; + evil_proof.A2[i].randomize(); + BOOST_CHECK(!(chaum.verify(mu, S, T, evil_proof))); + } + + // Bad t1 + for (std::size_t i = 0; i < n; i++) { + evil_proof = proof; + evil_proof.t1[i].randomize(); + BOOST_CHECK(!(chaum.verify(mu, S, T, evil_proof))); + } + + // Bad t2 + evil_proof = proof; + evil_proof.t2.randomize(); + BOOST_CHECK(!(chaum.verify(mu, S, T, evil_proof))); + + // Bad t3 + evil_proof = proof; + evil_proof.t3.randomize(); + BOOST_CHECK(!(chaum.verify(mu, S, T, evil_proof))); +} + +BOOST_AUTO_TEST_SUITE_END() + +} diff --git a/src/libspark/test/coin_test.cpp b/src/libspark/test/coin_test.cpp new file mode 100644 index 0000000000..567069769b --- /dev/null +++ b/src/libspark/test/coin_test.cpp @@ -0,0 +1,107 @@ +#include "../coin.h" + +#include "../../test/test_bitcoin.h" +#include + +namespace spark { + +using namespace secp_primitives; + +BOOST_FIXTURE_TEST_SUITE(spark_coin_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(mint_identify_recover) +{ + // Parameters + const Params* params; + params = Params::get_default(); + + const uint64_t i = 12345; + const uint64_t v = 86; + const std::string memo = "Spam and eggs"; + + // Generate keys + SpendKey spend_key(params); + FullViewKey full_view_key(spend_key); + IncomingViewKey incoming_view_key(full_view_key); + + // Generate address + Address address(incoming_view_key, i); + + // Generate coin + Scalar k; + k.randomize(); + Coin coin = Coin( + params, + COIN_TYPE_MINT, + k, + address, + v, + memo + ); + + // Identify coin + IdentifiedCoinData i_data = coin.identify(incoming_view_key); + BOOST_CHECK_EQUAL(i_data.i, i); + BOOST_CHECK_EQUAL_COLLECTIONS(i_data.d.begin(), i_data.d.end(), address.get_d().begin(), address.get_d().end()); + BOOST_CHECK_EQUAL(i_data.v, v); + BOOST_CHECK_EQUAL(i_data.k, k); + BOOST_CHECK_EQUAL(i_data.memo, memo); + + // Recover coin + RecoveredCoinData r_data = coin.recover(full_view_key, i_data); + BOOST_CHECK_EQUAL( + params->get_F()*(SparkUtils::hash_ser(k) + SparkUtils::hash_Q2(incoming_view_key.get_s1(), i) + full_view_key.get_s2()) + full_view_key.get_D(), + params->get_F()*r_data.s + full_view_key.get_D() + ); + BOOST_CHECK_EQUAL(r_data.T*r_data.s + full_view_key.get_D(), params->get_U()); +} + +BOOST_AUTO_TEST_CASE(spend_identify_recover) +{ + // Parameters + const Params* params; + params = Params::get_default(); + + const uint64_t i = 12345; + const uint64_t v = 86; + const std::string memo = "Spam and eggs"; + + // Generate keys + SpendKey spend_key(params); + FullViewKey full_view_key(spend_key); + IncomingViewKey incoming_view_key(full_view_key); + + // Generate address + Address address(incoming_view_key, i); + + // Generate coin + Scalar k; + k.randomize(); + Coin coin = Coin( + params, + COIN_TYPE_SPEND, + k, + address, + v, + memo + ); + + // Identify coin + IdentifiedCoinData i_data = coin.identify(incoming_view_key); + BOOST_CHECK_EQUAL(i_data.i, i); + BOOST_CHECK_EQUAL_COLLECTIONS(i_data.d.begin(), i_data.d.end(), address.get_d().begin(), address.get_d().end()); + BOOST_CHECK_EQUAL(i_data.v, v); + BOOST_CHECK_EQUAL(i_data.k, k); + BOOST_CHECK_EQUAL(i_data.memo, memo); + + // Recover coin + RecoveredCoinData r_data = coin.recover(full_view_key, i_data); + BOOST_CHECK_EQUAL( + params->get_F()*(SparkUtils::hash_ser(k) + SparkUtils::hash_Q2(incoming_view_key.get_s1(), i) + full_view_key.get_s2()) + full_view_key.get_D(), + params->get_F()*r_data.s + full_view_key.get_D() + ); + BOOST_CHECK_EQUAL(r_data.T*r_data.s + full_view_key.get_D(), params->get_U()); +} +BOOST_AUTO_TEST_SUITE_END() + +} diff --git a/src/libspark/test/encrypt_test.cpp b/src/libspark/test/encrypt_test.cpp new file mode 100644 index 0000000000..d0849b81c8 --- /dev/null +++ b/src/libspark/test/encrypt_test.cpp @@ -0,0 +1,52 @@ +#include "../util.h" +#include + +#include "../../test/test_bitcoin.h" +#include + +namespace spark { + +BOOST_FIXTURE_TEST_SUITE(spark_encrypt_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(complete) +{ + // Key + std::string key_string = "Key prefix"; + std::vector key(key_string.begin(), key_string.end()); + key.resize(AES256_KEYSIZE); + + // Encrypt + uint64_t i = 12345; + std::vector d = SparkUtils::diversifier_encrypt(key, i); + + // Decrypt + uint64_t i_ = SparkUtils::diversifier_decrypt(key, d); + + BOOST_CHECK_EQUAL(i_, i); +} + +BOOST_AUTO_TEST_CASE(bad_key) +{ + // Key + std::string key_string = "Key prefix"; + std::vector key(key_string.begin(), key_string.end()); + key.resize(AES256_KEYSIZE); + + // Evil key + std::string evil_key_string = "Evil key prefix"; + std::vector evil_key(evil_key_string.begin(), evil_key_string.end()); + evil_key.resize(AES256_KEYSIZE); + + // Encrypt + uint64_t i = 12345; + std::vector d = SparkUtils::diversifier_encrypt(key, i); + + // Decrypt + uint64_t i_ = SparkUtils::diversifier_decrypt(evil_key, d); + + BOOST_CHECK_NE(i_, i); +} + +BOOST_AUTO_TEST_SUITE_END() + +} diff --git a/src/libspark/test/grootle_test.cpp b/src/libspark/test/grootle_test.cpp new file mode 100644 index 0000000000..7ac9bfbafd --- /dev/null +++ b/src/libspark/test/grootle_test.cpp @@ -0,0 +1,153 @@ +#include "../grootle.h" + +#include "../../test/test_bitcoin.h" +#include + +namespace spark { + +static std::vector random_group_vector(const std::size_t n) { + std::vector result; + result.resize(n); + for (std::size_t i = 0; i < n; i++) { + result[i].randomize(); + } + return result; +} + +BOOST_FIXTURE_TEST_SUITE(spark_grootle_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(batch) +{ + // Parameters + const std::size_t n = 4; + const std::size_t m = 3; + const std::size_t N = (std::size_t) std::pow(n, m); // N = 64 + + // Generators + GroupElement H; + H.randomize(); + std::vector Gi = random_group_vector(n*m); + std::vector Hi = random_group_vector(n*m); + + // Commitments + std::size_t commit_size = 60; // require padding + std::vector S = random_group_vector(commit_size); + std::vector V = random_group_vector(commit_size); + + // Generate valid commitments to zero + std::vector indexes = { 0, 1, 3, 59 }; + std::vector sizes = { 60, 60, 59, 16 }; + std::vector S1, V1; + std::vector s, v; + for (std::size_t index : indexes) { + Scalar s_, v_; + s_.randomize(); + v_.randomize(); + s.emplace_back(s_); + v.emplace_back(v_); + + S1.emplace_back(S[index]); + V1.emplace_back(V[index]); + + S[index] += H*s_; + V[index] += H*v_; + } + + // Prepare proving system + Grootle grootle(H, Gi, Hi, n, m); + std::vector proofs; + + for (std::size_t i = 0; i < indexes.size(); i++) { + proofs.emplace_back(); + std::vector S_(S.begin() + commit_size - sizes[i], S.end()); + std::vector V_(V.begin() + commit_size - sizes[i], V.end()); + grootle.prove( + indexes[i] - (commit_size - sizes[i]), + s[i], + S_, + S1[i], + v[i], + V_, + V1[i], + proofs.back() + ); + + // Verify single proof + BOOST_CHECK(grootle.verify(S, S1[i], V, V1[i], sizes[i], proofs.back())); + } + + BOOST_CHECK(grootle.verify(S, S1, V, V1, sizes, proofs)); +} + +BOOST_AUTO_TEST_CASE(invalid_batch) +{ + // Parameters + const std::size_t n = 4; + const std::size_t m = 3; + const std::size_t N = (std::size_t) std::pow(n, m); // N = 64 + + // Generators + GroupElement H; + H.randomize(); + std::vector Gi = random_group_vector(n*m); + std::vector Hi = random_group_vector(n*m); + + // Commitments + std::size_t commit_size = 60; // require padding + std::vector S = random_group_vector(commit_size); + std::vector V = random_group_vector(commit_size); + + // Generate valid commitments to zero + std::vector indexes = { 0, 1, 3, 59 }; + std::vector sizes = { 60, 60, 59, 16 }; + std::vector S1, V1; + std::vector s, v; + for (std::size_t index : indexes) { + Scalar s_, v_; + s_.randomize(); + v_.randomize(); + s.emplace_back(s_); + v.emplace_back(v_); + + S1.emplace_back(S[index]); + V1.emplace_back(V[index]); + + S[index] += H*s_; + V[index] += H*v_; + } + + // Prepare proving system + Grootle grootle(H, Gi, Hi, n, m); + std::vector proofs; + + for (std::size_t i = 0; i < indexes.size(); i++) { + proofs.emplace_back(); + std::vector S_(S.begin() + commit_size - sizes[i], S.end()); + std::vector V_(V.begin() + commit_size - sizes[i], V.end()); + grootle.prove( + indexes[i] - (commit_size - sizes[i]), + s[i], + S_, + S1[i], + v[i], + V_, + V1[i], + proofs.back() + ); + } + + BOOST_CHECK(grootle.verify(S, S1, V, V1, sizes, proofs)); + + // Add an invalid proof + proofs.emplace_back(proofs.back()); + S1.emplace_back(S1.back()); + V1.emplace_back(V1.back()); + S1.back().randomize(); + sizes.emplace_back(sizes.back()); + + BOOST_CHECK(!grootle.verify(S, S1, V, V1, sizes, proofs)); +} + +BOOST_AUTO_TEST_SUITE_END() + +} \ No newline at end of file diff --git a/src/libspark/test/mint_transaction_test.cpp b/src/libspark/test/mint_transaction_test.cpp new file mode 100644 index 0000000000..911d4d9e59 --- /dev/null +++ b/src/libspark/test/mint_transaction_test.cpp @@ -0,0 +1,44 @@ +#include "../mint_transaction.h" + +#include "../../test/test_bitcoin.h" +#include + +namespace spark { + +using namespace secp_primitives; + +BOOST_FIXTURE_TEST_SUITE(spark_mint_transaction_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(generate_verify) +{ + // Parameters + const Params* params; + params = Params::get_default(); + + const uint64_t i = 12345; + const uint64_t v = 86; + const std::string memo = "Spam and eggs"; + + // Generate keys + SpendKey spend_key(params); + FullViewKey full_view_key(spend_key); + IncomingViewKey incoming_view_key(full_view_key); + + // Generate address + Address address(incoming_view_key, i); + + // Generate mint transaction + MintTransaction t( + params, + address, + v, + memo + ); + + // Verify + BOOST_CHECK(t.verify()); +} + +BOOST_AUTO_TEST_SUITE_END() + +} diff --git a/src/libspark/test/schnorr_test.cpp b/src/libspark/test/schnorr_test.cpp new file mode 100644 index 0000000000..ea350ccaaf --- /dev/null +++ b/src/libspark/test/schnorr_test.cpp @@ -0,0 +1,85 @@ +#include "../schnorr.h" +#include "../../streams.h" +#include "../../version.h" + +#include "../../test/test_bitcoin.h" +#include + +namespace spark { + +BOOST_FIXTURE_TEST_SUITE(spark_schnorr_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(serialization) +{ + GroupElement G; + G.randomize(); + + Scalar y; + y.randomize(); + GroupElement Y = G*y; + + SchnorrProof proof; + + Schnorr schnorr(G); + schnorr.prove(y, Y, proof); + + CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); + serialized << proof; + + SchnorrProof deserialized; + serialized >> deserialized; + + BOOST_CHECK(proof.c == deserialized.c); + BOOST_CHECK(proof.t == deserialized.t); +} + +BOOST_AUTO_TEST_CASE(completeness) +{ + GroupElement G; + G.randomize(); + + Scalar y; + y.randomize(); + GroupElement Y = G*y; + + SchnorrProof proof; + + Schnorr schnorr(G); + schnorr.prove(y, Y, proof); + + BOOST_CHECK(schnorr.verify(Y, proof)); +} + +BOOST_AUTO_TEST_CASE(bad_proofs) +{ + GroupElement G; + G.randomize(); + + Scalar y; + y.randomize(); + GroupElement Y = G*y; + + SchnorrProof proof; + + Schnorr schnorr(G); + schnorr.prove(y, Y, proof); + + // Bad Y + GroupElement evil_Y; + evil_Y.randomize(); + BOOST_CHECK(!(schnorr.verify(evil_Y, proof))); + + // Bad c + SchnorrProof evil_proof = proof; + evil_proof.c.randomize(); + BOOST_CHECK(!(schnorr.verify(Y, evil_proof))); + + // Bad t + evil_proof = proof; + evil_proof.t.randomize(); + BOOST_CHECK(!(schnorr.verify(Y, evil_proof))); +} + +BOOST_AUTO_TEST_SUITE_END() + +} diff --git a/src/libspark/test/spend_transaction_test.cpp b/src/libspark/test/spend_transaction_test.cpp new file mode 100644 index 0000000000..a8cda7fc20 --- /dev/null +++ b/src/libspark/test/spend_transaction_test.cpp @@ -0,0 +1,110 @@ +#include "../spend_transaction.h" + +#include "../../test/test_bitcoin.h" +#include + +namespace spark { + +using namespace secp_primitives; + +BOOST_FIXTURE_TEST_SUITE(spark_spend_transaction_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(generate_verify) +{ + // Parameters + const Params* params; + params = Params::get_test(); + + const std::string memo = "Spam and eggs"; // arbitrary memo + + // Generate keys + SpendKey spend_key(params); + FullViewKey full_view_key(spend_key); + IncomingViewKey incoming_view_key(full_view_key); + + // Generate address + const uint64_t i = 12345; + Address address(incoming_view_key, i); + + // Mint some coins to the address + std::size_t N = (std::size_t) pow(params->get_n_grootle(), params->get_m_grootle()); + std::vector in_coins; + for (std::size_t i = 0; i < N; i++) { + Scalar k; + k.randomize(); + + uint64_t v = 12 + i; // arbitrary value + + in_coins.emplace_back(Coin( + params, + COIN_TYPE_MINT, + k, + address, + v, + memo + )); + } + + // Track values so we can set the fee to make the transaction balance + uint64_t f = 0; + + // Choose coins to spend, recover them, and prepare them for spending + std::vector spend_indices = { 1, 3, 5 }; + std::vector spend_coin_data; + const std::size_t w = spend_indices.size(); + for (std::size_t u = 0; u < w; u++) { + IdentifiedCoinData identified_coin_data = in_coins[spend_indices[u]].identify(incoming_view_key); + RecoveredCoinData recovered_coin_data = in_coins[spend_indices[u]].recover(full_view_key, identified_coin_data); + + spend_coin_data.emplace_back(); + spend_coin_data.back().index = spend_indices[u]; + spend_coin_data.back().k = identified_coin_data.k; + spend_coin_data.back().s = recovered_coin_data.s; + spend_coin_data.back().T = recovered_coin_data.T; + spend_coin_data.back().v = identified_coin_data.v; + + f -= identified_coin_data.v; + } + + // Generate new output coins and compute the fee + const std::size_t t = 2; + std::vector out_coin_data; + for (std::size_t j = 0; j < t; j++) { + out_coin_data.emplace_back(); + out_coin_data.back().address = address; + out_coin_data.back().v = 123 + j; // arbitrary value + out_coin_data.back().memo = memo; + + f += out_coin_data.back().v; + } + + // Assert the fee is correct + uint64_t fee_test = f; + for (std::size_t u = 0; u < w; u++) { + fee_test += spend_coin_data[u].v; + } + for (std::size_t j = 0; j < t; j++) { + fee_test -= out_coin_data[j].v; + } + if (fee_test != 0) { + throw std::runtime_error("Bad fee assertion"); + } + + // Generate spend transaction + SpendTransaction transaction( + params, + full_view_key, + spend_key, + in_coins, + spend_coin_data, + f, + out_coin_data + ); + + // Verify + BOOST_CHECK(transaction.verify()); +} + +BOOST_AUTO_TEST_SUITE_END() + +} diff --git a/src/libspark/test/transcript_test.cpp b/src/libspark/test/transcript_test.cpp new file mode 100644 index 0000000000..4ef9e1131d --- /dev/null +++ b/src/libspark/test/transcript_test.cpp @@ -0,0 +1,174 @@ +#include "../transcript.h" + +#include "../../test/test_bitcoin.h" +#include + +namespace spark { + +BOOST_FIXTURE_TEST_SUITE(spark_transcript_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(init) +{ + // Identical domain separators + Transcript transcript_1("Spam"); + Transcript transcript_2("Spam"); + BOOST_CHECK_EQUAL(transcript_1.challenge("x"), transcript_2.challenge("x")); + + // Distinct domain separators + transcript_1 = Transcript("Spam"); + transcript_2 = Transcript("Eggs"); + BOOST_CHECK_NE(transcript_1.challenge("x"), transcript_2.challenge("x")); +} + +BOOST_AUTO_TEST_CASE(challenge_labels) +{ + Transcript transcript_1("Spam"); + Transcript transcript_2("Spam"); + + // Identical challenge labels + BOOST_CHECK_EQUAL(transcript_1.challenge("x"), transcript_2.challenge("x")); + + // Distinct challenge labels + BOOST_CHECK_NE(transcript_1.challenge("x"), transcript_2.challenge("y")); +} + +BOOST_AUTO_TEST_CASE(add_types) +{ + // Add all fixed types and assert distinct challenges + const std::string domain = "Spam"; + Transcript transcript(domain); + + Scalar scalar; + scalar.randomize(); + transcript.add("Scalar", scalar); + Scalar ch_1 = transcript.challenge("x"); + + GroupElement group; + group.randomize(); + transcript.add("Group", group); + Scalar ch_2 = transcript.challenge("x"); + BOOST_CHECK_NE(ch_1, ch_2); + + std::vector scalars; + for (std::size_t i = 0; i < 3; i++) { + scalar.randomize(); + scalars.emplace_back(scalar); + } + Scalar ch_3 = transcript.challenge("x"); + BOOST_CHECK_NE(ch_2, ch_3); + + std::vector groups; + for (std::size_t i = 0; i < 3; i++) { + group.randomize(); + groups.emplace_back(group); + } + Scalar ch_4 = transcript.challenge("x"); + BOOST_CHECK_NE(ch_3, ch_4); + + const std::string data = "Arbitrary string"; + const std::vector data_char(data.begin(), data.end()); + transcript.add("Data", data_char); + Scalar ch_5 = transcript.challenge("x"); + BOOST_CHECK_NE(ch_4, ch_5); +} + +BOOST_AUTO_TEST_CASE(repeated_challenge) +{ + // Repeated challenges must be distinct, even with the same label + Transcript transcript("Eggs"); + + Scalar ch_1 = transcript.challenge("x"); + Scalar ch_2 = transcript.challenge("x"); + + BOOST_CHECK_NE(ch_1, ch_2); +} + +BOOST_AUTO_TEST_CASE(repeated_challenge_ordering) +{ + // Repeated challenges must respect ordering + Transcript prover("Spam"); + Transcript verifier("Spam"); + + Scalar prover_x = prover.challenge("x"); + Scalar prover_y = prover.challenge("y"); + + // Oh no, we mixed up the order + Scalar verifier_y = verifier.challenge("y"); + Scalar verifier_x = verifier.challenge("x"); + + BOOST_CHECK_NE(prover_x, verifier_x); + BOOST_CHECK_NE(prover_y, verifier_y); +} + +BOOST_AUTO_TEST_CASE(identical_transcripts) +{ + // Ensure that identical transcripts yield identical challenges + Transcript prover("Beer"); + Transcript verifier("Beer"); + + Scalar scalar; + scalar.randomize(); + GroupElement group; + group.randomize(); + + prover.add("Scalar", scalar); + verifier.add("Scalar", scalar); + prover.add("Group", group); + verifier.add("Group", group); + + BOOST_CHECK_EQUAL(prover.challenge("x"), verifier.challenge("x")); +} + +BOOST_AUTO_TEST_CASE(distinct_values) +{ + // Ensure that distinct transcript values yield distinct challenges + Transcript prover("Soda"); + Transcript verifier("Soda"); + + Scalar prover_scalar; + prover_scalar.randomize(); + Scalar verifier_scalar; + verifier_scalar.randomize(); + + prover.add("Scalar", prover_scalar); + verifier.add("Scalar", verifier_scalar); + + BOOST_CHECK_NE(prover.challenge("x"), verifier.challenge("x")); +} + +BOOST_AUTO_TEST_CASE(distinct_labels) +{ + // Ensure that distinct transcript labels yield distinct challenges + Transcript prover("Soda"); + Transcript verifier("Soda"); + + Scalar scalar; + scalar.randomize(); + + prover.add("Prover scalar", scalar); + verifier.add("Verifier scalar", scalar); + + BOOST_CHECK_NE(prover.challenge("x"), verifier.challenge("y")); +} + +BOOST_AUTO_TEST_CASE(converging) +{ + // Transcripts with distinct initial states but common post-challenge elements + Transcript transcript_1("Spam"); + Transcript transcript_2("Eggs"); + + Scalar ch_1 = transcript_1.challenge("x"); + Scalar ch_2 = transcript_1.challenge("x"); + + // Add a common element and assert the states still differ + Scalar scalar; + scalar.randomize(); + transcript_1.add("Scalar", scalar); + transcript_2.add("Scalar", scalar); + + BOOST_CHECK_NE(transcript_1.challenge("x"), transcript_2.challenge("x")); +} + +BOOST_AUTO_TEST_SUITE_END() + +} diff --git a/src/libspark/transcript.cpp b/src/libspark/transcript.cpp new file mode 100644 index 0000000000..238e2d3ae9 --- /dev/null +++ b/src/libspark/transcript.cpp @@ -0,0 +1,177 @@ +#include "transcript.h" + +namespace spark { + +using namespace secp_primitives; + +// Flags for transcript operations +const unsigned char FLAG_DOMAIN = 0; +const unsigned char FLAG_DATA = 1; +const unsigned char FLAG_VECTOR = 2; +const unsigned char FLAG_CHALLENGE = 3; + +// Initialize a transcript with a domain separator +Transcript::Transcript(const std::string domain) { + // Prepare the state + this->ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(this->ctx, EVP_blake2b512(), NULL); + + // Write the protocol and mode information + std::vector protocol(LABEL_PROTOCOL.begin(), LABEL_PROTOCOL.end()); + EVP_DigestUpdate(this->ctx, protocol.data(), protocol.size()); + EVP_DigestUpdate(this->ctx, &HASH_MODE_TRANSCRIPT, sizeof(HASH_MODE_TRANSCRIPT)); + + // Domain separator + include_flag(FLAG_DOMAIN); + include_label(domain); +} + +Transcript::~Transcript() { + EVP_MD_CTX_free(this->ctx); +} + +Transcript& Transcript::operator=(const Transcript& t) { + if (this == &t) { + return *this; + } + + EVP_MD_CTX_copy_ex(this->ctx, t.ctx); + + return *this; +} + +// Add a group element +void Transcript::add(const std::string label, const GroupElement& group_element) { + std::vector data; + data.resize(GroupElement::serialize_size); + group_element.serialize(data.data()); + + include_flag(FLAG_DATA); + include_label(label); + include_data(data); +} + +// Add a vector of group elements +void Transcript::add(const std::string label, const std::vector& group_elements) { + include_flag(FLAG_VECTOR); + size(group_elements.size()); + include_label(label); + for (std::size_t i = 0; i < group_elements.size(); i++) { + std::vector data; + data.resize(GroupElement::serialize_size); + group_elements[i].serialize(data.data()); + include_data(data); + } +} + +// Add a scalar +void Transcript::add(const std::string label, const Scalar& scalar) { + std::vector data; + data.resize(SCALAR_ENCODING); + scalar.serialize(data.data()); + + include_flag(FLAG_DATA); + include_label(label); + include_data(data); +} + +// Add a vector of scalars +void Transcript::add(const std::string label, const std::vector& scalars) { + include_flag(FLAG_VECTOR); + size(scalars.size()); + include_label(label); + for (std::size_t i = 0; i < scalars.size(); i++) { + std::vector data; + data.resize(SCALAR_ENCODING); + scalars[i].serialize(data.data()); + include_data(data); + } +} + +// Add arbitrary data +void Transcript::add(const std::string label, const std::vector& data) { + include_flag(FLAG_DATA); + include_label(label); + include_data(data); +} + +// Produce a challenge +Scalar Transcript::challenge(const std::string label) { + // Ensure we can properly populate a scalar + if (EVP_MD_size(EVP_blake2b512()) < SCALAR_ENCODING) { + throw std::runtime_error("Bad hash size!"); + } + + std::vector hash; + hash.resize(EVP_MD_size(EVP_blake2b512())); + unsigned char counter = 0; + + EVP_MD_CTX* state_counter; + state_counter = EVP_MD_CTX_new(); + EVP_DigestInit_ex(state_counter, EVP_blake2b512(), NULL); + + EVP_MD_CTX* state_finalize; + state_finalize = EVP_MD_CTX_new(); + EVP_DigestInit_ex(state_finalize, EVP_blake2b512(), NULL); + + include_flag(FLAG_CHALLENGE); + include_label(label); + + while (1) { + // Prepare temporary state for counter testing + EVP_MD_CTX_copy_ex(state_counter, this->ctx); + + // Embed the counter + EVP_DigestUpdate(state_counter, &counter, sizeof(counter)); + + // Finalize the hash with a temporary state + EVP_MD_CTX_copy_ex(state_finalize, state_counter); + unsigned int TEMP; // We already know the digest length! + EVP_DigestFinal_ex(state_finalize, hash.data(), &TEMP); + + // Check for scalar validity + Scalar candidate; + try { + candidate.deserialize(hash.data()); + EVP_MD_CTX_copy_ex(this->ctx, state_counter); + + EVP_MD_CTX_free(state_counter); + EVP_MD_CTX_free(state_finalize); + + return candidate; + } catch (...) { + counter++; + } + } +} + +// Encode and include a size +void Transcript::size(const std::size_t size_) { + Scalar size_scalar(size_); + std::vector size_data; + size_data.resize(SCALAR_ENCODING); + size_scalar.serialize(size_data.data()); + EVP_DigestUpdate(this->ctx, size_data.data(), size_data.size()); +} + +// Include a flag +void Transcript::include_flag(const unsigned char flag) { + EVP_DigestUpdate(this->ctx, &flag, sizeof(flag)); +} + +// Encode and include a label +void Transcript::include_label(const std::string label) { + std::vector bytes(label.begin(), label.end()); + include_data(bytes); +} + +// Encode and include data +void Transcript::include_data(const std::vector& data) { + // Include size + size(data.size()); + + // Include data + EVP_DigestUpdate(this->ctx, data.data(), data.size()); +} + +} diff --git a/src/libspark/transcript.h b/src/libspark/transcript.h new file mode 100644 index 0000000000..eef2f9f59b --- /dev/null +++ b/src/libspark/transcript.h @@ -0,0 +1,32 @@ +#ifndef FIRO_SPARK_TRANSCRIPT_H +#define FIRO_SPARK_TRANSCRIPT_H +#include +#include "util.h" + +namespace spark { + +using namespace secp_primitives; + +class Transcript { +public: + Transcript(const std::string); + Transcript& operator=(const Transcript&); + ~Transcript(); + void add(const std::string, const Scalar&); + void add(const std::string, const std::vector&); + void add(const std::string, const GroupElement&); + void add(const std::string, const std::vector&); + void add(const std::string, const std::vector&); + Scalar challenge(const std::string); + +private: + void size(const std::size_t size_); + void include_flag(const unsigned char); + void include_label(const std::string); + void include_data(const std::vector&); + EVP_MD_CTX* ctx; +}; + +} + +#endif diff --git a/src/libspark/util.cpp b/src/libspark/util.cpp new file mode 100644 index 0000000000..1f58f97e69 --- /dev/null +++ b/src/libspark/util.cpp @@ -0,0 +1,232 @@ +#include "util.h" + +namespace spark { + +using namespace secp_primitives; + +// Encrypt a diversifier using AES-256 +std::vector SparkUtils::diversifier_encrypt(const std::vector& key, const uint64_t i) { + // Serialize the diversifier + CDataStream i_stream(SER_NETWORK, PROTOCOL_VERSION); + i_stream << i; + + // Assert proper sizes + if (key.size() != AES256_KEYSIZE) { + throw std::invalid_argument("Bad diversifier encryption key size"); + } + + // Encrypt using padded AES-256 (CBC) using a zero IV + std::vector ciphertext; + ciphertext.resize(AES_BLOCKSIZE); + std::vector iv; + iv.resize(AES_BLOCKSIZE); + + AES256CBCEncrypt aes(key.data(), iv.data(), true); + aes.Encrypt(reinterpret_cast(i_stream.data()), i_stream.size(), ciphertext.data()); + + return ciphertext; +} + +// Decrypt a diversifier using AES-256 +uint64_t SparkUtils::diversifier_decrypt(const std::vector& key, const std::vector& d) { + // Assert proper sizes + if (key.size() != AES256_KEYSIZE) { + throw std::invalid_argument("Bad diversifier decryption key size"); + } + + // Decrypt using padded AES-256 (CBC) using a zero IV + CDataStream i_stream(SER_NETWORK, PROTOCOL_VERSION); + i_stream.resize(sizeof(uint64_t)); + + std::vector iv; + iv.resize(AES_BLOCKSIZE); + + AES256CBCDecrypt aes(key.data(), iv.data(), true); + aes.Decrypt(d.data(), d.size(), reinterpret_cast(i_stream.data())); + + // Deserialize the diversifier + uint64_t i; + i_stream >> i; + + return i; +} + +// Produce a uniformly-sampled group element from a label +GroupElement SparkUtils::hash_generator(const std::string label) { + const int GROUP_ENCODING = 34; + const unsigned char ZERO = 0; + + // Ensure we can properly populate a + if (EVP_MD_size(EVP_blake2b512()) < GROUP_ENCODING) { + throw std::runtime_error("Bad hash size!"); + } + + EVP_MD_CTX* ctx; + ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_blake2b512(), NULL); + + // Write the protocol and mode + std::vector protocol(LABEL_PROTOCOL.begin(), LABEL_PROTOCOL.end()); + EVP_DigestUpdate(ctx, protocol.data(), protocol.size()); + EVP_DigestUpdate(ctx, &HASH_MODE_GROUP_GENERATOR, sizeof(HASH_MODE_GROUP_GENERATOR)); + + // Write the label + std::vector bytes(label.begin(), label.end()); + EVP_DigestUpdate(ctx, bytes.data(), bytes.size()); + + std::vector hash; + hash.resize(EVP_MD_size(EVP_blake2b512())); + unsigned char counter = 0; + + EVP_MD_CTX* state_counter; + state_counter = EVP_MD_CTX_new(); + EVP_DigestInit_ex(state_counter, EVP_blake2b512(), NULL); + + EVP_MD_CTX* state_finalize; + state_finalize = EVP_MD_CTX_new(); + EVP_DigestInit_ex(state_finalize, EVP_blake2b512(), NULL); + + // Finalize the hash + while (1) { + // Prepare temporary state for counter testing + EVP_MD_CTX_copy_ex(state_counter, ctx); + + // Embed the counter + EVP_DigestUpdate(state_counter, &counter, sizeof(counter)); + + // Finalize the hash with a temporary state + EVP_MD_CTX_copy_ex(state_finalize, state_counter); + unsigned int TEMP; // We already know the digest length! + EVP_DigestFinal_ex(state_finalize, hash.data(), &TEMP); + + // Assemble the serialized input: + // bytes 0..31: x coordinate + // byte 32: even/odd + // byte 33: zero (this point is not infinity) + unsigned char candidate_bytes[GROUP_ENCODING]; + memcpy(candidate_bytes, hash.data(), 33); + memcpy(candidate_bytes + 33, &ZERO, 1); + GroupElement candidate; + try { + candidate.deserialize(candidate_bytes); + + // Deserialization can succeed even with an invalid result + if (!candidate.isMember()) { + counter++; + continue; + } + + EVP_MD_CTX_free(ctx); + EVP_MD_CTX_free(state_counter); + EVP_MD_CTX_free(state_finalize); + + return candidate; + } catch (...) { + counter++; + } + } +} + +// Derive an AES key for diversifier encryption/decryption +std::vector SparkUtils::kdf_diversifier(const Scalar& s1) { + KDF kdf(LABEL_KDF_DIVERSIFIER); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << s1; + kdf.include(stream); + + return kdf.finalize(AES256_KEYSIZE); +} + +// Derive a ChaCha20 key for AEAD operations +std::vector SparkUtils::kdf_aead(const GroupElement& K_der) { + KDF kdf(LABEL_KDF_AEAD); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << K_der; + kdf.include(stream); + + return kdf.finalize(AEAD_KEY_SIZE); +} + +// Hash-to-group function H_div +GroupElement SparkUtils::hash_div(const std::vector& d) { + Hash hash(LABEL_HASH_DIV); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << d; + hash.include(stream); + + return hash.finalize_group(); +} + +// Hash-to-scalar function H_Q2 +Scalar SparkUtils::hash_Q2(const Scalar& s1, const Scalar& i) { + Hash hash(LABEL_HASH_Q2); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << s1; + stream << i; + hash.include(stream); + + return hash.finalize_scalar(); +} + +// Hash-to-scalar function H_k +Scalar SparkUtils::hash_k(const Scalar& k) { + Hash hash(LABEL_HASH_K); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << k; + hash.include(stream); + + return hash.finalize_scalar(); +} + +// Hash-to-scalar function H_ser +Scalar SparkUtils::hash_ser(const Scalar& k) { + Hash hash(LABEL_HASH_SER); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << k; + hash.include(stream); + + return hash.finalize_scalar(); +} + +// Hash-to-scalar function H_val +Scalar SparkUtils::hash_val(const Scalar& k) { + Hash hash(LABEL_HASH_VAL); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << k; + hash.include(stream); + + return hash.finalize_scalar(); +} + +// Hash-to-scalar function H_ser1 +Scalar SparkUtils::hash_ser1(const Scalar& s, const GroupElement& D) { + Hash hash(LABEL_HASH_SER1); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << s; + stream << D; + hash.include(stream); + + return hash.finalize_scalar(); +} + +// Hash-to-scalar function H_val1 +Scalar SparkUtils::hash_val1(const Scalar& s, const GroupElement& D) { + Hash hash(LABEL_HASH_VAL1); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << s; + stream << D; + hash.include(stream); + + return hash.finalize_scalar(); +} + +} diff --git a/src/libspark/util.h b/src/libspark/util.h new file mode 100644 index 0000000000..ff37e8de56 --- /dev/null +++ b/src/libspark/util.h @@ -0,0 +1,84 @@ +#ifndef FIRO_SPARK_UTIL_H +#define FIRO_SPARK_UTIL_H +#include +#include +#include "../../crypto/aes.h" +#include "../streams.h" +#include "../version.h" +#include "../util.h" +#include "kdf.h" +#include "hash.h" +#include "grootle_proof.h" +#include "schnorr_proof.h" + +namespace spark { + +using namespace secp_primitives; + +// Useful serialization constant +const std::size_t SCALAR_ENCODING = 32; + +// Base protocol separator +const std::string LABEL_PROTOCOL = "SPARK"; + +// All hash operations have a mode flag to separate their use cases +const unsigned char HASH_MODE_TRANSCRIPT = 0; // a Fiat-Shamir transcript +const unsigned char HASH_MODE_GROUP_GENERATOR = 1; // a prime-order group generator derived from a label +const unsigned char HASH_MODE_FUNCTION = 2; // a hash function derived from a label +const unsigned char HASH_MODE_KDF = 3; // a key derivation function derived from a label + +// Generator labels +const std::string LABEL_GENERATOR_F = "F"; +const std::string LABEL_GENERATOR_G = "G"; +const std::string LABEL_GENERATOR_H = "H"; +const std::string LABEL_GENERATOR_U = "U"; +const std::string LABEL_GENERATOR_G_RANGE = "G_RANGE"; +const std::string LABEL_GENERATOR_H_RANGE = "H_RANGE"; +const std::string LABEL_GENERATOR_G_GROOTLE = "G_GROOTLE"; +const std::string LABEL_GENERATOR_H_GROOTLE = "H_GROOTLE"; + +// Hash function labels +const std::string LABEL_HASH_DIV = "DIV"; +const std::string LABEL_HASH_Q2 = "Q2"; +const std::string LABEL_HASH_K = "K"; +const std::string LABEL_HASH_SER = "SER"; +const std::string LABEL_HASH_VAL = "VAL"; +const std::string LABEL_HASH_SER1 = "SER1"; +const std::string LABEL_HASH_VAL1 = "VAL1"; +const std::string LABEL_HASH_BIND = "BIND"; + +// KDF labels +const std::string LABEL_KDF_DIVERSIFIER = "DIVERSIFIER"; +const std::string LABEL_KDF_AEAD = "AEAD"; + +// AEAD constants +const int AEAD_IV_SIZE = 12; // byte length of the IV +const int AEAD_KEY_SIZE = 32; // byte length of the key +const int AEAD_TAG_SIZE = 16; // byte length of the tag + +class SparkUtils { +public: + // Protocol-level hash functions + static GroupElement hash_generator(const std::string label); + + // Hash functions + static GroupElement hash_div(const std::vector& d); + static Scalar hash_Q2(const Scalar& s1, const Scalar& i); + static Scalar hash_k(const Scalar& k); + static Scalar hash_ser(const Scalar& k); + static Scalar hash_val(const Scalar& k); + static Scalar hash_ser1(const Scalar& s, const GroupElement& D); + static Scalar hash_val1(const Scalar& s, const GroupElement& D); + + // Key derivation functions + static std::vector kdf_diversifier(const Scalar& s1); + static std::vector kdf_aead(const GroupElement& K_der); + + // Diversifier encryption/decryption + static std::vector diversifier_encrypt(const std::vector& key, const uint64_t i); + static uint64_t diversifier_decrypt(const std::vector& key, const std::vector& d); +}; + +} + +#endif From 3dee12a5022528f1206655dcb82a4eff3616a9a9 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Mon, 14 Feb 2022 13:22:05 -0600 Subject: [PATCH 002/197] Replace coin cover sets with Merkle roots for transcript purposes --- src/libspark/grootle.cpp | 16 +++++++---- src/libspark/grootle.h | 3 +++ src/libspark/spend_transaction.cpp | 20 +++++++++----- src/libspark/spend_transaction.h | 4 ++- src/libspark/test/grootle_test.cpp | 28 +++++++++++++++++--- src/libspark/test/spend_transaction_test.cpp | 9 +++++++ 6 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/libspark/grootle.cpp b/src/libspark/grootle.cpp index 9d1ebdb7d9..eca3b7b468 100644 --- a/src/libspark/grootle.cpp +++ b/src/libspark/grootle.cpp @@ -146,6 +146,7 @@ void Grootle::prove( const Scalar& v, const std::vector& V, const GroupElement& V1, + const std::vector& root, GrootleProof& proof) { // Check statement validity std::size_t N = (std::size_t) pow(n, m); // padded input size @@ -173,9 +174,8 @@ void Grootle::prove( transcript.add("Hi", Hi); transcript.add("n", Scalar(n)); transcript.add("m", Scalar(m)); - transcript.add("S", S); + transcript.add("root", root); transcript.add("S1", S1); - transcript.add("V", V); transcript.add("V1", V1); // Compute A @@ -348,14 +348,16 @@ bool Grootle::verify( const GroupElement& S1, const std::vector& V, const GroupElement& V1, + const std::vector& root, const std::size_t size, const GrootleProof& proof) { std::vector S1_batch = {S1}; std::vector V1_batch = {V1}; std::vector size_batch = {size}; + std::vector> root_batch = {root}; std::vector proof_batch = {proof}; - return verify(S, S1_batch, V, V1_batch, size_batch, proof_batch); + return verify(S, S1_batch, V, V1_batch, root_batch, size_batch, proof_batch); } // Verify a batch of proofs @@ -364,6 +366,7 @@ bool Grootle::verify( const std::vector& S1, const std::vector& V, const std::vector& V1, + const std::vector>& roots, const std::vector& sizes, const std::vector& proofs) { // Sanity checks @@ -394,6 +397,10 @@ bool Grootle::verify( LogPrintf("Invalid set size vector size"); return false; } + if (roots.size() != M) { + LogPrintf("Invalid root vector size"); + return false; + } // Check proof semantics for (std::size_t t = 0; t < M; t++) { @@ -462,9 +469,8 @@ bool Grootle::verify( transcript.add("Hi", Hi); transcript.add("n", Scalar(n)); transcript.add("m", Scalar(m)); - transcript.add("S", std::vector(S.begin() + S.size() - sizes[t], S.end())); + transcript.add("root", roots[t]); transcript.add("S1", S1[t]); - transcript.add("V", std::vector(V.begin() + V.size() - sizes[t], V.end())); transcript.add("V1", V1[t]); transcript.add("A", proof.A); transcript.add("B", proof.B); diff --git a/src/libspark/grootle.h b/src/libspark/grootle.h index d8b03d7d2d..f0d5dd4b27 100644 --- a/src/libspark/grootle.h +++ b/src/libspark/grootle.h @@ -26,17 +26,20 @@ class Grootle { const Scalar& v, const std::vector& V, const GroupElement& V1, + const std::vector& root, GrootleProof& proof); bool verify(const std::vector& S, const GroupElement& S1, const std::vector& V, const GroupElement& V1, + const std::vector& root, const std::size_t size, const GrootleProof& proof); // single proof bool verify(const std::vector& S, const std::vector& S1, const std::vector& V, const std::vector& V1, + const std::vector>& roots, const std::vector& sizes, const std::vector& proofs); // batch of proofs diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index dc7585b4ac..0da600c2ce 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -7,6 +7,7 @@ SpendTransaction::SpendTransaction( const FullViewKey& full_view_key, const SpendKey& spend_key, const std::vector& in_coins, + const std::vector>& roots, const std::vector& inputs, const uint64_t f, const std::vector& outputs @@ -18,6 +19,12 @@ SpendTransaction::SpendTransaction( const std::size_t t = outputs.size(); // number of generated coins const std::size_t N = in_coins.size(); // size of cover set + // Ensure we have enough Merkle roots + if (roots.size() != w) { + throw std::invalid_argument("Bad number of roots for spend transaction"); + } + this->roots = roots; + // Prepare input-related vectors this->in_coins = in_coins; // input cover set this->S1.reserve(w); // serial commitment offsets @@ -79,6 +86,7 @@ SpendTransaction::SpendTransaction( SparkUtils::hash_val(inputs[u].k) - SparkUtils::hash_val1(inputs[u].s, full_view_key.get_D()), C, this->C1.back(), + this->roots[u], this->grootle_proofs.back() ); @@ -150,7 +158,7 @@ SpendTransaction::SpendTransaction( // Compute the binding hash Scalar mu = hash_bind( - this->in_coins, + this->roots, this->out_coins, this->f, this->S1, @@ -186,7 +194,7 @@ bool SpendTransaction::verify() { const std::size_t N = this->in_coins.size(); // Semantics - if (this->S1.size() != w || this->C1.size() != w || this->T.size() != w) { + if (this->S1.size() != w || this->C1.size() != w || this->T.size() != w || this->roots.size() != w) { throw std::invalid_argument("Bad spend transaction semantics"); } if (N > (std::size_t)pow(this->params->get_n_grootle(), this->params->get_m_grootle())) { @@ -223,13 +231,13 @@ bool SpendTransaction::verify() { for (std::size_t u = 0; u < w; u++) { sizes.emplace_back(N); } - if (!grootle.verify(S, this->S1, C, this->C1, sizes, this->grootle_proofs)) { + if (!grootle.verify(S, this->S1, C, this->C1, this->roots, sizes, this->grootle_proofs)) { return false; } // Compute the binding hash Scalar mu = hash_bind( - this->in_coins, + this->roots, this->out_coins, this->f, this->S1, @@ -285,7 +293,7 @@ bool SpendTransaction::verify() { // Hash-to-scalar function H_bind Scalar SpendTransaction::hash_bind( - const std::vector& in_coins, + const std::vector>& roots, const std::vector& out_coins, const uint64_t f, const std::vector& S1, @@ -300,7 +308,7 @@ Scalar SpendTransaction::hash_bind( CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); // Perform the serialization and hashing - stream << in_coins; + stream << roots, stream << out_coins; stream << f; stream << S1; diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h index 7110058eda..fd515576d0 100644 --- a/src/libspark/spend_transaction.h +++ b/src/libspark/spend_transaction.h @@ -33,6 +33,7 @@ class SpendTransaction { const FullViewKey& full_view_key, const SpendKey& spend_key, const std::vector& in_coins, + const std::vector>& roots, const std::vector& inputs, const uint64_t f, const std::vector& outputs @@ -40,7 +41,7 @@ class SpendTransaction { bool verify(); static Scalar hash_bind( - const std::vector& in_coins, + const std::vector>& roots, const std::vector& out_coins, const uint64_t f, const std::vector& S1, @@ -54,6 +55,7 @@ class SpendTransaction { private: const Params* params; std::vector in_coins; + std::vector> roots; std::vector out_coins; uint64_t f; std::vector S1, C1, T; diff --git a/src/libspark/test/grootle_test.cpp b/src/libspark/test/grootle_test.cpp index 7ac9bfbafd..57d82635de 100644 --- a/src/libspark/test/grootle_test.cpp +++ b/src/libspark/test/grootle_test.cpp @@ -38,6 +38,7 @@ BOOST_AUTO_TEST_CASE(batch) std::vector indexes = { 0, 1, 3, 59 }; std::vector sizes = { 60, 60, 59, 16 }; std::vector S1, V1; + std::vector> roots; std::vector s, v; for (std::size_t index : indexes) { Scalar s_, v_; @@ -51,6 +52,14 @@ BOOST_AUTO_TEST_CASE(batch) S[index] += H*s_; V[index] += H*v_; + + // Prepare random data in place of Merkle root + Scalar temp; + temp.randomize(); + std::vector root; + root.reserve(SCALAR_ENCODING); + temp.serialize(root.data()); + roots.emplace_back(root); } // Prepare proving system @@ -69,14 +78,15 @@ BOOST_AUTO_TEST_CASE(batch) v[i], V_, V1[i], + roots[i], proofs.back() ); // Verify single proof - BOOST_CHECK(grootle.verify(S, S1[i], V, V1[i], sizes[i], proofs.back())); + BOOST_CHECK(grootle.verify(S, S1[i], V, V1[i], roots[i], sizes[i], proofs.back())); } - BOOST_CHECK(grootle.verify(S, S1, V, V1, sizes, proofs)); + BOOST_CHECK(grootle.verify(S, S1, V, V1, roots, sizes, proofs)); } BOOST_AUTO_TEST_CASE(invalid_batch) @@ -101,6 +111,7 @@ BOOST_AUTO_TEST_CASE(invalid_batch) std::vector indexes = { 0, 1, 3, 59 }; std::vector sizes = { 60, 60, 59, 16 }; std::vector S1, V1; + std::vector> roots; std::vector s, v; for (std::size_t index : indexes) { Scalar s_, v_; @@ -114,6 +125,14 @@ BOOST_AUTO_TEST_CASE(invalid_batch) S[index] += H*s_; V[index] += H*v_; + + // Prepare random data in place of Merkle root + Scalar temp; + temp.randomize(); + std::vector root; + root.reserve(SCALAR_ENCODING); + temp.serialize(root.data()); + roots.emplace_back(root); } // Prepare proving system @@ -132,11 +151,12 @@ BOOST_AUTO_TEST_CASE(invalid_batch) v[i], V_, V1[i], + roots[i], proofs.back() ); } - BOOST_CHECK(grootle.verify(S, S1, V, V1, sizes, proofs)); + BOOST_CHECK(grootle.verify(S, S1, V, V1, roots, sizes, proofs)); // Add an invalid proof proofs.emplace_back(proofs.back()); @@ -145,7 +165,7 @@ BOOST_AUTO_TEST_CASE(invalid_batch) S1.back().randomize(); sizes.emplace_back(sizes.back()); - BOOST_CHECK(!grootle.verify(S, S1, V, V1, sizes, proofs)); + BOOST_CHECK(!grootle.verify(S, S1, V, V1, roots, sizes, proofs)); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/libspark/test/spend_transaction_test.cpp b/src/libspark/test/spend_transaction_test.cpp index a8cda7fc20..b911155b6f 100644 --- a/src/libspark/test/spend_transaction_test.cpp +++ b/src/libspark/test/spend_transaction_test.cpp @@ -51,11 +51,19 @@ BOOST_AUTO_TEST_CASE(generate_verify) // Choose coins to spend, recover them, and prepare them for spending std::vector spend_indices = { 1, 3, 5 }; std::vector spend_coin_data; + std::vector> roots; const std::size_t w = spend_indices.size(); for (std::size_t u = 0; u < w; u++) { IdentifiedCoinData identified_coin_data = in_coins[spend_indices[u]].identify(incoming_view_key); RecoveredCoinData recovered_coin_data = in_coins[spend_indices[u]].recover(full_view_key, identified_coin_data); + Scalar temp; + temp.randomize(); + std::vector root; + root.resize(SCALAR_ENCODING); + temp.serialize(root.data()); + roots.emplace_back(root); + spend_coin_data.emplace_back(); spend_coin_data.back().index = spend_indices[u]; spend_coin_data.back().k = identified_coin_data.k; @@ -96,6 +104,7 @@ BOOST_AUTO_TEST_CASE(generate_verify) full_view_key, spend_key, in_coins, + roots, spend_coin_data, f, out_coin_data From 1516c8cf4e8ade5a76f62ae5a02d30e9482fc5b5 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Tue, 22 Feb 2022 11:58:07 -0500 Subject: [PATCH 003/197] Support multiple output coins in mint transactions --- src/libspark/mint_transaction.cpp | 72 +++++++++++++-------- src/libspark/mint_transaction.h | 14 ++-- src/libspark/test/mint_transaction_test.cpp | 26 ++++---- 3 files changed, 69 insertions(+), 43 deletions(-) diff --git a/src/libspark/mint_transaction.cpp b/src/libspark/mint_transaction.cpp index 7c99e6f498..0841c76403 100644 --- a/src/libspark/mint_transaction.cpp +++ b/src/libspark/mint_transaction.cpp @@ -4,40 +4,58 @@ namespace spark { MintTransaction::MintTransaction( const Params* params, - const Address& address, - uint64_t v, - const std::string memo + const std::vector& outputs ) { - this->params = params; + // Important note: This construction assumes that the public coin values are correct according to higher-level consensus rules! - // Generate the coin - Scalar k; - k.randomize(); - this->coin = Coin( - this->params, - COIN_TYPE_MINT, - k, - address, - v, - memo - ); - - // Generate the balance proof + this->params = params; Schnorr schnorr(this->params->get_H()); - schnorr.prove( - SparkUtils::hash_val(k), - this->coin.C + this->params->get_G().inverse()*Scalar(v), - this->balance_proof - ); + + for (std::size_t j = 0; j < outputs.size(); j++) { + MintedCoinData output = outputs[j]; + + // Generate the coin + Scalar k; + k.randomize(); + this->coins.emplace_back(Coin( + this->params, + COIN_TYPE_MINT, + k, + output.address, + output.v, + output.memo + )); + + // Generate the value proof + this->value_proofs.emplace_back(); + schnorr.prove( + SparkUtils::hash_val(k), + this->coins[j].C + this->params->get_G().inverse()*Scalar(this->coins[j].v), + this->value_proofs.back() + ); + } + } bool MintTransaction::verify() { - // Verify the balance proof + // Size check + if (this->coins.size() != this->value_proofs.size()) { + throw std::invalid_argument("Bad mint transaction semantics"); + } + + // Verify the value proofs Schnorr schnorr(this->params->get_H()); - return schnorr.verify( - this->coin.C + this->params->get_G().inverse()*Scalar(this->coin.v), - this->balance_proof - ); + + for (std::size_t j = 0; j < this->coins.size(); j++) { + if (!schnorr.verify( + this->coins[j].C + this->params->get_G().inverse()*Scalar(this->coins[j].v), + this->value_proofs[j] + )) { + return false; + } + } + + return true; } } diff --git a/src/libspark/mint_transaction.h b/src/libspark/mint_transaction.h index 7e14d45138..60456da463 100644 --- a/src/libspark/mint_transaction.h +++ b/src/libspark/mint_transaction.h @@ -9,20 +9,24 @@ namespace spark { using namespace secp_primitives; +struct MintedCoinData { + Address address; + uint64_t v; + std::string memo; +}; + class MintTransaction { public: MintTransaction( const Params* params, - const Address& address, - uint64_t v, - const std::string memo + const std::vector& outputs ); bool verify(); private: const Params* params; - Coin coin; - SchnorrProof balance_proof; + std::vector coins; + std::vector value_proofs; }; } diff --git a/src/libspark/test/mint_transaction_test.cpp b/src/libspark/test/mint_transaction_test.cpp index 911d4d9e59..8119e27baa 100644 --- a/src/libspark/test/mint_transaction_test.cpp +++ b/src/libspark/test/mint_transaction_test.cpp @@ -14,29 +14,33 @@ BOOST_AUTO_TEST_CASE(generate_verify) // Parameters const Params* params; params = Params::get_default(); + const std::size_t t = 3; // number of coins to generate - const uint64_t i = 12345; - const uint64_t v = 86; - const std::string memo = "Spam and eggs"; - // Generate keys SpendKey spend_key(params); FullViewKey full_view_key(spend_key); IncomingViewKey incoming_view_key(full_view_key); - // Generate address - Address address(incoming_view_key, i); + std::vector outputs; + + // Generate addresses and coins + for (std::size_t j = 0; j < t; j++) { + MintedCoinData output; + output.address = Address(incoming_view_key, 12345 + j); + output.v = 678 + j; + output.memo = "Spam and eggs"; + + outputs.emplace_back(output); + } // Generate mint transaction - MintTransaction t( + MintTransaction mint( params, - address, - v, - memo + outputs ); // Verify - BOOST_CHECK(t.verify()); + BOOST_CHECK(mint.verify()); } BOOST_AUTO_TEST_SUITE_END() From 1aa39156672e56d7157eed0735293341085999d8 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Tue, 22 Feb 2022 12:51:35 -0500 Subject: [PATCH 004/197] Use aggregate Schnorr proofs for multiple-output mint transactions --- src/libspark/mint_transaction.cpp | 32 ++++++---------- src/libspark/mint_transaction.h | 2 +- src/libspark/schnorr.cpp | 61 ++++++++++++++++++++++++++---- src/libspark/schnorr.h | 7 +++- src/libspark/schnorr_proof.h | 6 +-- src/libspark/test/schnorr_test.cpp | 31 +++++++++++++-- 6 files changed, 102 insertions(+), 37 deletions(-) diff --git a/src/libspark/mint_transaction.cpp b/src/libspark/mint_transaction.cpp index 0841c76403..53a7a29401 100644 --- a/src/libspark/mint_transaction.cpp +++ b/src/libspark/mint_transaction.cpp @@ -11,6 +11,9 @@ MintTransaction::MintTransaction( this->params = params; Schnorr schnorr(this->params->get_H()); + std::vector value_statement; + std::vector value_witness; + for (std::size_t j = 0; j < outputs.size(); j++) { MintedCoinData output = outputs[j]; @@ -26,36 +29,25 @@ MintTransaction::MintTransaction( output.memo )); - // Generate the value proof - this->value_proofs.emplace_back(); - schnorr.prove( - SparkUtils::hash_val(k), - this->coins[j].C + this->params->get_G().inverse()*Scalar(this->coins[j].v), - this->value_proofs.back() - ); + // Prepare the value proof + value_statement.emplace_back(this->coins[j].C + this->params->get_G().inverse()*Scalar(this->coins[j].v)); + value_witness.emplace_back(SparkUtils::hash_val(k)); } + // Complete the value proof + schnorr.prove(value_witness, value_statement, this->value_proof); } bool MintTransaction::verify() { - // Size check - if (this->coins.size() != this->value_proofs.size()) { - throw std::invalid_argument("Bad mint transaction semantics"); - } - - // Verify the value proofs + // Verify the value proof Schnorr schnorr(this->params->get_H()); + std::vector value_statement; for (std::size_t j = 0; j < this->coins.size(); j++) { - if (!schnorr.verify( - this->coins[j].C + this->params->get_G().inverse()*Scalar(this->coins[j].v), - this->value_proofs[j] - )) { - return false; - } + value_statement.emplace_back(this->coins[j].C + this->params->get_G().inverse()*Scalar(this->coins[j].v)); } - return true; + return schnorr.verify(value_statement, this->value_proof); } } diff --git a/src/libspark/mint_transaction.h b/src/libspark/mint_transaction.h index 60456da463..e1bc8f1e17 100644 --- a/src/libspark/mint_transaction.h +++ b/src/libspark/mint_transaction.h @@ -26,7 +26,7 @@ class MintTransaction { private: const Params* params; std::vector coins; - std::vector value_proofs; + SchnorrProof value_proof; }; } diff --git a/src/libspark/schnorr.cpp b/src/libspark/schnorr.cpp index d7ffce9b10..309938f122 100644 --- a/src/libspark/schnorr.cpp +++ b/src/libspark/schnorr.cpp @@ -8,7 +8,7 @@ Schnorr::Schnorr(const GroupElement& G_): } Scalar Schnorr::challenge( - const GroupElement& Y, + const std::vector& Y, const GroupElement& A) { Transcript transcript("SPARK_SCHNORR"); transcript.add("G", G); @@ -19,22 +19,67 @@ Scalar Schnorr::challenge( } void Schnorr::prove(const Scalar& y, const GroupElement& Y, SchnorrProof& proof) { + const std::vector y_vector = { y }; + const std::vector Y_vector = { Y }; + prove(y_vector, Y_vector, proof); +} + +void Schnorr::prove(const std::vector& y, const std::vector& Y, SchnorrProof& proof) { + const std::size_t n = y.size(); + // Check statement validity - if (!(G*y == Y)) { + if (y.size() != Y.size()) { throw std::invalid_argument("Bad Schnorr statement!"); } + for (std::size_t i = 0; i < n; i++) { + if (G*y[i] != Y[i]) { + throw std::invalid_argument("Bad Schnorr statement!"); + } + } + Scalar r; r.randomize(); - GroupElement A = G*r; - proof.c = challenge(Y, A); - proof.t = r + proof.c*y; + proof.A = G*r; + + const Scalar c = challenge(Y, proof.A); + Scalar c_power(c); + + proof.t = r; + for (std::size_t i = 0; i < n; i++) { + proof.t += y[i].negate()*c_power; + c_power *= c; + } +} + +bool Schnorr::verify(const GroupElement& Y, const SchnorrProof& proof) { + const std::vector Y_vector = { Y }; + return verify(Y_vector, proof); } -bool Schnorr::verify(const GroupElement& Y, SchnorrProof& proof) { - Scalar c = challenge(Y, G*proof.t + Y.inverse()*proof.c); +bool Schnorr::verify(const std::vector& Y, const SchnorrProof& proof) { + const std::size_t n = Y.size(); + + std::vector points; + points.reserve(n + 2); + std::vector scalars; + scalars.reserve(n + 2); + + points.emplace_back(G); + scalars.emplace_back(proof.t); + points.emplace_back(proof.A); + scalars.emplace_back(Scalar(uint64_t(1)).negate()); + + const Scalar c = challenge(Y, proof.A); + Scalar c_power(c); + for (std::size_t i = 0; i < n; i++) { + points.emplace_back(Y[i]); + scalars.emplace_back(c_power); + c_power *= c; + } - return c == proof.c; + MultiExponent result(points, scalars); + return result.get_multiple().isInfinity(); } } diff --git a/src/libspark/schnorr.h b/src/libspark/schnorr.h index 7cc40b0df1..a760e5a56b 100644 --- a/src/libspark/schnorr.h +++ b/src/libspark/schnorr.h @@ -2,6 +2,7 @@ #define FIRO_LIBSPARK_SCHNORR_H #include "schnorr_proof.h" +#include namespace spark { @@ -10,10 +11,12 @@ class Schnorr { Schnorr(const GroupElement& G); void prove(const Scalar& y, const GroupElement& Y, SchnorrProof& proof); - bool verify(const GroupElement& Y, SchnorrProof& proof); + void prove(const std::vector& y, const std::vector& Y, SchnorrProof& proof); + bool verify(const GroupElement& Y, const SchnorrProof& proof); + bool verify(const std::vector& Y, const SchnorrProof& proof); private: - Scalar challenge(const GroupElement& Y, const GroupElement& A); + Scalar challenge(const std::vector& Y, const GroupElement& A); const GroupElement& G; }; diff --git a/src/libspark/schnorr_proof.h b/src/libspark/schnorr_proof.h index c5fa96c776..a3fe067d01 100644 --- a/src/libspark/schnorr_proof.h +++ b/src/libspark/schnorr_proof.h @@ -8,18 +8,18 @@ namespace spark { class SchnorrProof{ public: inline std::size_t memoryRequired() const { - return 2*Scalar::memoryRequired(); + return Scalar::memoryRequired() + GroupElement::memoryRequired(); } ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(c); + READWRITE(A); READWRITE(t); } public: - Scalar c; + GroupElement A; Scalar t; }; } diff --git a/src/libspark/test/schnorr_test.cpp b/src/libspark/test/schnorr_test.cpp index ea350ccaaf..261ed383ba 100644 --- a/src/libspark/test/schnorr_test.cpp +++ b/src/libspark/test/schnorr_test.cpp @@ -29,7 +29,7 @@ BOOST_AUTO_TEST_CASE(serialization) SchnorrProof deserialized; serialized >> deserialized; - BOOST_CHECK(proof.c == deserialized.c); + BOOST_CHECK(proof.A == deserialized.A); BOOST_CHECK(proof.t == deserialized.t); } @@ -50,6 +50,31 @@ BOOST_AUTO_TEST_CASE(completeness) BOOST_CHECK(schnorr.verify(Y, proof)); } +BOOST_AUTO_TEST_CASE(completeness_aggregate) +{ + const std::size_t n = 3; + + GroupElement G; + G.randomize(); + + std::vector y; + std::vector Y; + + for (std::size_t i = 0; i < n; i++) { + y.emplace_back(); + y.back().randomize(); + + Y.emplace_back(G*y.back()); + } + + SchnorrProof proof; + + Schnorr schnorr(G); + schnorr.prove(y, Y, proof); + + BOOST_CHECK(schnorr.verify(Y, proof)); +} + BOOST_AUTO_TEST_CASE(bad_proofs) { GroupElement G; @@ -69,9 +94,9 @@ BOOST_AUTO_TEST_CASE(bad_proofs) evil_Y.randomize(); BOOST_CHECK(!(schnorr.verify(evil_Y, proof))); - // Bad c + // Bad A SchnorrProof evil_proof = proof; - evil_proof.c.randomize(); + evil_proof.A.randomize(); BOOST_CHECK(!(schnorr.verify(Y, evil_proof))); // Bad t From 59c80135638be3c021984dda541826221fc1e863 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Fri, 11 Mar 2022 12:04:51 -0600 Subject: [PATCH 005/197] Add versioning to proving system transcript labels --- src/libspark/bpplus.cpp | 4 ++-- src/libspark/chaum.cpp | 2 +- src/libspark/grootle.cpp | 4 ++-- src/libspark/schnorr.cpp | 2 +- src/libspark/util.h | 8 ++++++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/libspark/bpplus.cpp b/src/libspark/bpplus.cpp index 224b7a57ef..958021a705 100644 --- a/src/libspark/bpplus.cpp +++ b/src/libspark/bpplus.cpp @@ -66,7 +66,7 @@ void BPPlus::prove( } // Set up transcript - Transcript transcript("SPARK_BPPLUS"); + Transcript transcript(LABEL_TRANSCRIPT_BPPLUS); transcript.add("G", G); transcript.add("H", H); transcript.add("Gi", Gi); @@ -327,7 +327,7 @@ bool BPPlus::verify(const std::vector>& C, const std:: } // Set up transcript - Transcript transcript("SPARK_BPPLUS"); + Transcript transcript(LABEL_TRANSCRIPT_BPPLUS); transcript.add("G", G); transcript.add("H", H); transcript.add("Gi", Gi); diff --git a/src/libspark/chaum.cpp b/src/libspark/chaum.cpp index e130ef2539..4fb219fa64 100644 --- a/src/libspark/chaum.cpp +++ b/src/libspark/chaum.cpp @@ -14,7 +14,7 @@ Scalar Chaum::challenge( const GroupElement& A1, const std::vector& A2 ) { - Transcript transcript("SPARK_CHAUM"); + Transcript transcript(LABEL_TRANSCRIPT_CHAUM); transcript.add("F", F); transcript.add("G", G); transcript.add("H", H); diff --git a/src/libspark/grootle.cpp b/src/libspark/grootle.cpp index eca3b7b468..f27c180194 100644 --- a/src/libspark/grootle.cpp +++ b/src/libspark/grootle.cpp @@ -168,7 +168,7 @@ void Grootle::prove( } // Set up transcript - Transcript transcript("SPARK_GROOTLE"); + Transcript transcript(LABEL_TRANSCRIPT_GROOTLE); transcript.add("H", H); transcript.add("Gi", Gi); transcript.add("Hi", Hi); @@ -463,7 +463,7 @@ bool Grootle::verify( GrootleProof proof = proofs[t]; // Reconstruct the challenge - Transcript transcript("SPARK_GROOTLE"); + Transcript transcript(LABEL_TRANSCRIPT_GROOTLE); transcript.add("H", H); transcript.add("Gi", Gi); transcript.add("Hi", Hi); diff --git a/src/libspark/schnorr.cpp b/src/libspark/schnorr.cpp index 309938f122..353bfbc88c 100644 --- a/src/libspark/schnorr.cpp +++ b/src/libspark/schnorr.cpp @@ -10,7 +10,7 @@ Schnorr::Schnorr(const GroupElement& G_): Scalar Schnorr::challenge( const std::vector& Y, const GroupElement& A) { - Transcript transcript("SPARK_SCHNORR"); + Transcript transcript(LABEL_TRANSCRIPT_SCHNORR); transcript.add("G", G); transcript.add("Y", Y); transcript.add("A", A); diff --git a/src/libspark/util.h b/src/libspark/util.h index ff37e8de56..6fea39d555 100644 --- a/src/libspark/util.h +++ b/src/libspark/util.h @@ -8,8 +8,6 @@ #include "../util.h" #include "kdf.h" #include "hash.h" -#include "grootle_proof.h" -#include "schnorr_proof.h" namespace spark { @@ -27,6 +25,12 @@ const unsigned char HASH_MODE_GROUP_GENERATOR = 1; // a prime-order group genera const unsigned char HASH_MODE_FUNCTION = 2; // a hash function derived from a label const unsigned char HASH_MODE_KDF = 3; // a key derivation function derived from a label +// Transcript labels +const std::string LABEL_TRANSCRIPT_BPPLUS = "BULLETPROOF_PLUS_V1"; +const std::string LABEL_TRANSCRIPT_CHAUM = "CHAUM_V1"; +const std::string LABEL_TRANSCRIPT_GROOTLE = "GROOTLE_V1"; +const std::string LABEL_TRANSCRIPT_SCHNORR = "SCHNORR_V1"; + // Generator labels const std::string LABEL_GENERATOR_F = "F"; const std::string LABEL_GENERATOR_G = "G"; From 7d5097ad0c32ec60ddc2aa4521810df46a62cc76 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 27 Aug 2021 03:43:26 +0400 Subject: [PATCH 006/197] Refactoring mobile api for Lelantus --- src/rpc/misc.cpp | 65 +++++++++++++++------------------------------- src/rpc/server.cpp | 2 +- src/rpc/server.h | 2 +- 3 files changed, 23 insertions(+), 46 deletions(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 555733bd35..ce95d84c40 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -923,13 +923,12 @@ UniValue getaddressbalance(const JSONRPCRequest& request) UniValue getanonymityset(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 2) + if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "getanonymityset\n" "\nReturns the anonymity set and latest block hash.\n" "\nArguments:\n" "{\n" - " \"denomination\" (int64_t) int denomination\n" " \"coinGroupId\" (int)\n" "}\n" "\nResult:\n" @@ -942,41 +941,37 @@ UniValue getanonymityset(const JSONRPCRequest& request) ); - int64_t intDenom; int coinGroupId; try { - intDenom = std::stol(request.params[0].get_str()); coinGroupId = std::stol(request.params[1].get_str()); } catch (std::logic_error const & e) { throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); } - sigma::CoinDenomination denomination; - sigma::IntegerToDenomination(intDenom, denomination); - uint256 blockHash; - std::vector coins; - + std::vector coins; + std::vector setHash; { LOCK(cs_main); - sigma::CSigmaState* sigmaState = sigma::CSigmaState::GetState(); - sigmaState->GetCoinSetForSpend( + lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); + lelantusState->GetCoinSetForSpend( &chainActive, chainActive.Height() - (ZC_MINT_CONFIRMATIONS - 1), - denomination, coinGroupId, blockHash, - coins); + coins, + setHash); } UniValue serializedCoins(UniValue::VARR); - for(sigma::PublicCoin const & coin : coins) { + for(lelantus::PublicCoin const & coin : coins) { std::vector vch = coin.getValue().getvch(); serializedCoins.push_back(HexStr(vch.begin(), vch.end())); } UniValue ret(UniValue::VOBJ); ret.push_back(Pair("blockHash", blockHash.GetHex())); + ret.push_back(Pair("setHash", UniValue(HexStr(setHash.begin(), setHash.end())))); ret.push_back(Pair("serializedCoins", serializedCoins)); return ret; @@ -992,7 +987,6 @@ UniValue getmintmetadata(const JSONRPCRequest& request) " \"mints\"\n" " [\n" " {\n" - " \"denom\" (int) The mint denomination\n" " \"pubcoin\" (string) The PubCoin value\n" " }\n" " ,...\n" @@ -1009,7 +1003,7 @@ UniValue getmintmetadata(const JSONRPCRequest& request) if (!mintValues.isArray()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "mints is expected to be an array"); } - sigma::CSigmaState* sigmaState = sigma::CSigmaState::GetState(); + lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); UniValue ret(UniValue::VARR); for(UniValue const & mintData : mintValues.getValues()){ std::vector serializedCoin = ParseHex(find_value(mintData, "pubcoin").get_str().c_str()); @@ -1017,14 +1011,10 @@ UniValue getmintmetadata(const JSONRPCRequest& request) secp_primitives::GroupElement pubCoin; pubCoin.deserialize(serializedCoin.data()); - int64_t intDenom = find_value(mintData, "denom").get_int64(); - sigma::CoinDenomination denomination; - sigma::IntegerToDenomination(intDenom, denomination); - std::pair coinHeightAndId; { LOCK(cs_main); - coinHeightAndId = sigmaState->GetMintedCoinHeightAndId(sigma::PublicCoin(pubCoin, denomination)); + coinHeightAndId = lelantusState->GetMintedCoinHeightAndId(lelantus::PublicCoin(pubCoin)); } UniValue metaData(UniValue::VOBJ); metaData.pushKV(std::to_string(coinHeightAndId.first), coinHeightAndId.second); @@ -1045,11 +1035,11 @@ UniValue getusedcoinserials(const JSONRPCRequest& request) "}\n" ); - sigma::CSigmaState* sigmaState = sigma::CSigmaState::GetState(); - sigma::spend_info_container serials; + lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); + std::unordered_map serials; { LOCK(cs_main); - serials = sigmaState->GetSpends(); + serials = lelantusState->GetSpends(); } UniValue serializedSerials(UniValue::VARR); @@ -1062,17 +1052,16 @@ UniValue getusedcoinserials(const JSONRPCRequest& request) return ret; } -UniValue getlatestcoinids(const JSONRPCRequest& request) +UniValue getlatestcoinid(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( - "getlatestcoinids\n" + "getlatestcoinid\n" "\nReturns the set of used coin serial.\n" "\nResult:\n" "{\n" " [\n" " {\n" - " \"denom\" (int64_t) The mint denomination\n" " \"coinGroupId\" (int) The latest group id\n" " }\n" " ,...\n" @@ -1080,26 +1069,14 @@ UniValue getlatestcoinids(const JSONRPCRequest& request) "}\n" ); - sigma::CSigmaState* sigmaState = sigma::CSigmaState::GetState(); - std::unordered_map latestCoinIds; + lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); + int latestCoinId; { LOCK(cs_main); - latestCoinIds = sigmaState->GetLatestCoinIds(); + latestCoinId = lelantusState->GetLatestCoinID(); } - UniValue ret(UniValue::VARR); - for (const auto& it : latestCoinIds ) { - int64_t denom; - sigma::DenominationToInteger(it.first, denom); - - UniValue denomandid(UniValue::VOBJ); - denomandid.push_back(Pair("denom", denom)); - denomandid.push_back(Pair("id", it.second)); - - ret.push_back(denomandid); - } - - return ret; + return UniValue(latestCoinId); } UniValue getaddresstxids(const JSONRPCRequest& request) @@ -1388,7 +1365,7 @@ static const CRPCCommand commands[] = { "mobile", "getanonymityset", &getanonymityset, true }, { "mobile", "getmintmetadata", &getmintmetadata, true }, { "mobile", "getusedcoinserials", &getusedcoinserials, true }, - { "mobile", "getlatestcoinids", &getlatestcoinids, true }, + { "mobile", "getlatestcoinid", &getlatestcoinid, true }, { "hidden", "setmocktime", &setmocktime, true, {"timestamp"}}, { "hidden", "echo", &echo, true, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index ed1813cade..3c2819f6af 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -335,7 +335,7 @@ static const CRPCCommand vRPCCommands[] = { "mobile", "getanonymityset", &getanonymityset, true }, { "mobile", "getmintmetadata", &getmintmetadata, true }, { "mobile", "getusedcoinserials", &getusedcoinserials, true }, - { "mobile", "getlatestcoinids", &getlatestcoinids, true }, + { "mobile", "getlatestcoinid", &getlatestcoinid, true }, }; CRPCTable::CRPCTable() diff --git a/src/rpc/server.h b/src/rpc/server.h index 2bcd9049fb..2e3ddc0ad8 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -210,7 +210,7 @@ extern UniValue getaddressbalance(const JSONRPCRequest &request); extern UniValue getanonymityset(const JSONRPCRequest& params); extern UniValue getmintmetadata(const JSONRPCRequest& params); extern UniValue getusedcoinserials(const JSONRPCRequest& params); -extern UniValue getlatestcoinids(const JSONRPCRequest& params); +extern UniValue getlatestcoinid(const JSONRPCRequest& params); extern UniValue znode(const JSONRPCRequest &request); extern UniValue znodelist(const JSONRPCRequest &request); From 1ef76de98ba71531d5c9ffeed9581b88157700ba Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 30 Aug 2021 00:34:26 +0400 Subject: [PATCH 007/197] Typo fixed --- src/rpc/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index ce95d84c40..dec327c4e8 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -943,7 +943,7 @@ UniValue getanonymityset(const JSONRPCRequest& request) int coinGroupId; try { - coinGroupId = std::stol(request.params[1].get_str()); + coinGroupId = std::stol(request.params[0].get_str()); } catch (std::logic_error const & e) { throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); } From 3aecf3653c8824be0891fcc5b15e84105b3e68f5 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 1 Nov 2021 05:44:51 +0400 Subject: [PATCH 008/197] Updating getanonymityset rpc to get part of the set --- src/lelantus.cpp | 5 +++-- src/lelantus.h | 3 ++- src/rpc/misc.cpp | 10 +++++++--- src/rpc/server.cpp | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/lelantus.cpp b/src/lelantus.cpp index 78819bdbdd..a13f741cf8 100644 --- a/src/lelantus.cpp +++ b/src/lelantus.cpp @@ -1403,7 +1403,8 @@ int CLelantusState::GetCoinSetForSpend( int coinGroupID, uint256& blockHash_out, std::vector& coins_out, - std::vector& setHash_out) { + std::vector& setHash_out, + std::string start_block_hash) { coins_out.clear(); @@ -1451,7 +1452,7 @@ int CLelantusState::GetCoinSetForSpend( } } - if (block == coinGroup.firstBlock) { + if (block == coinGroup.firstBlock || block->GetBlockHash().GetHex() == start_block_hash) { break ; } } diff --git a/src/lelantus.h b/src/lelantus.h index 8e0da50596..0ef48810d8 100644 --- a/src/lelantus.h +++ b/src/lelantus.h @@ -189,7 +189,8 @@ friend bool BuildLelantusStateFromIndex(CChain *, std::set &); int id, uint256& blockHash_out, std::vector& coins_out, - std::vector& setHash_out); + std::vector& setHash_out, + std::string start_block_hash = ""); void GetAnonymitySet( int coinGroupID, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index dec327c4e8..56efe99007 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -923,13 +923,14 @@ UniValue getaddressbalance(const JSONRPCRequest& request) UniValue getanonymityset(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 1) + if (request.fHelp || request.params.size() != 2) throw std::runtime_error( "getanonymityset\n" "\nReturns the anonymity set and latest block hash.\n" "\nArguments:\n" "{\n" " \"coinGroupId\" (int)\n" + " \"startBlockHash\" (string)\n" "}\n" "\nResult:\n" "{\n" @@ -942,8 +943,10 @@ UniValue getanonymityset(const JSONRPCRequest& request) int coinGroupId; + std::string startBlockHash; try { coinGroupId = std::stol(request.params[0].get_str()); + startBlockHash = std::stol(request.params[1].get_str()); } catch (std::logic_error const & e) { throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); } @@ -960,7 +963,8 @@ UniValue getanonymityset(const JSONRPCRequest& request) coinGroupId, blockHash, coins, - setHash); + setHash, + startBlockHash); } UniValue serializedCoins(UniValue::VARR); @@ -1362,7 +1366,7 @@ static const CRPCCommand commands[] = { "addressindex", "gettotalsupply", &gettotalsupply, false }, /* Mobile related */ - { "mobile", "getanonymityset", &getanonymityset, true }, + { "mobile", "getanonymityset", &getanonymityset, false }, { "mobile", "getmintmetadata", &getmintmetadata, true }, { "mobile", "getusedcoinserials", &getusedcoinserials, true }, { "mobile", "getlatestcoinid", &getlatestcoinid, true }, diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 3c2819f6af..57eeb7e582 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -332,7 +332,7 @@ static const CRPCCommand vRPCCommands[] = { "addressindex", "getaddresstxids", &getaddresstxids, false }, { "addressindex", "getaddressbalance", &getaddressbalance, false }, /* Mobile related */ - { "mobile", "getanonymityset", &getanonymityset, true }, + { "mobile", "getanonymityset", &getanonymityset, false }, { "mobile", "getmintmetadata", &getmintmetadata, true }, { "mobile", "getusedcoinserials", &getusedcoinserials, true }, { "mobile", "getlatestcoinid", &getlatestcoinid, true }, From 82864b33a230b6f2f8a01c069daa22068e13c2a9 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 1 Nov 2021 05:54:43 +0400 Subject: [PATCH 009/197] Adding getfeerate rpc --- src/rpc/misc.cpp | 19 +++++++++++++++++++ src/rpc/server.cpp | 1 + src/rpc/server.h | 1 + 3 files changed, 21 insertions(+) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 56efe99007..f098e14f27 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1056,6 +1056,24 @@ UniValue getusedcoinserials(const JSONRPCRequest& request) return ret; } +UniValue getfeerate(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 0) + throw std::runtime_error( + "getfeerate\n" + "\nReturns the fee rate.\n" + "\nResult:\n" + "{\n" + " \"rate\" (int) Fee rate\n" + "}\n" + ); + + UniValue ret(UniValue::VOBJ); + ret.push_back(Pair("serials", ::minRelayTxFee.GetFeePerK())); + + return ret; +} + UniValue getlatestcoinid(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) @@ -1369,6 +1387,7 @@ static const CRPCCommand commands[] = { "mobile", "getanonymityset", &getanonymityset, false }, { "mobile", "getmintmetadata", &getmintmetadata, true }, { "mobile", "getusedcoinserials", &getusedcoinserials, true }, + { "mobile", "getfeerate", &getfeerate, true }, { "mobile", "getlatestcoinid", &getlatestcoinid, true }, { "hidden", "setmocktime", &setmocktime, true, {"timestamp"}}, diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 57eeb7e582..c6b9100ac6 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -335,6 +335,7 @@ static const CRPCCommand vRPCCommands[] = { "mobile", "getanonymityset", &getanonymityset, false }, { "mobile", "getmintmetadata", &getmintmetadata, true }, { "mobile", "getusedcoinserials", &getusedcoinserials, true }, + { "mobile", "getfeerate", &getfeerate, true }, { "mobile", "getlatestcoinid", &getlatestcoinid, true }, }; diff --git a/src/rpc/server.h b/src/rpc/server.h index 2e3ddc0ad8..8b87c8efb3 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -210,6 +210,7 @@ extern UniValue getaddressbalance(const JSONRPCRequest &request); extern UniValue getanonymityset(const JSONRPCRequest& params); extern UniValue getmintmetadata(const JSONRPCRequest& params); extern UniValue getusedcoinserials(const JSONRPCRequest& params); +extern UniValue getfeerate(const JSONRPCRequest& params); extern UniValue getlatestcoinid(const JSONRPCRequest& params); extern UniValue znode(const JSONRPCRequest &request); From 81a8c66f77e2fb099248bf1cf464a5d39e9b6944 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Tue, 9 Nov 2021 12:53:36 +0400 Subject: [PATCH 010/197] Parsing issue fixed --- src/rpc/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index f098e14f27..38fd040526 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -946,7 +946,7 @@ UniValue getanonymityset(const JSONRPCRequest& request) std::string startBlockHash; try { coinGroupId = std::stol(request.params[0].get_str()); - startBlockHash = std::stol(request.params[1].get_str()); + startBlockHash = request.params[1].get_str(); } catch (std::logic_error const & e) { throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); } From 48f76d240cacf668f836b8140b059838827bb985 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Wed, 17 Nov 2021 01:14:45 +0400 Subject: [PATCH 011/197] Fixing typo in getfeerate rpc --- src/rpc/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 38fd040526..4da5e2ad73 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1069,7 +1069,7 @@ UniValue getfeerate(const JSONRPCRequest& request) ); UniValue ret(UniValue::VOBJ); - ret.push_back(Pair("serials", ::minRelayTxFee.GetFeePerK())); + ret.push_back(Pair("rate", ::minRelayTxFee.GetFeePerK())); return ret; } From 5f0b4c57f5d82f4b690cf646fc15ba536ea8a301 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 22 Nov 2021 18:07:40 +0400 Subject: [PATCH 012/197] Adding getcoinsforrecovery rpc --- src/chain.h | 4 +-- src/coin_containers.h | 17 +++++++++++++ src/lelantus.cpp | 57 +++++++++++++++++++++++++++++++++++++++---- src/lelantus.h | 6 +++++ src/rpc/client.cpp | 1 + src/rpc/misc.cpp | 46 ++++++++++++++++++++++++++++++++++ src/rpc/server.cpp | 1 + src/rpc/server.h | 1 + 8 files changed, 126 insertions(+), 7 deletions(-) diff --git a/src/chain.h b/src/chain.h index 4218e03c16..1c88d441cb 100644 --- a/src/chain.h +++ b/src/chain.h @@ -243,7 +243,7 @@ class CBlockIndex //! Maps to vector of public coins std::map, std::vector> sigmaMintedPubCoins; //! Map id to - std::map>> lelantusMintedPubCoins; + std::map>>> lelantusMintedPubCoins; //! Map id to std::map> anonymitySetHash; @@ -524,7 +524,7 @@ class CDiskBlockIndex : public CBlockIndex for(auto& itr : lelantusPubCoins) { if(!itr.second.empty()) { for(auto& coin : itr.second) - lelantusMintedPubCoins[itr.first].push_back(std::make_pair(coin, uint256())); + lelantusMintedPubCoins[itr.first].push_back(std::make_pair(coin, std::make_pair(lelantus::MintValueData(), uint256()))); } } } else diff --git a/src/coin_containers.h b/src/coin_containers.h index cfe5f7355c..a9ea16d630 100644 --- a/src/coin_containers.h +++ b/src/coin_containers.h @@ -59,6 +59,23 @@ using spend_info_container = std::unordered_map encryptedValue; + + ADD_SERIALIZE_METHODS; + template + void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(isJMint); + READWRITE(amount); + READWRITE(encryptedValue); + } +}; + // Custom hash for the public coin. struct CPublicCoinHash { std::size_t operator()(const lelantus::PublicCoin& coin) const noexcept; diff --git a/src/lelantus.cpp b/src/lelantus.cpp index a13f741cf8..a4d905c786 100644 --- a/src/lelantus.cpp +++ b/src/lelantus.cpp @@ -328,6 +328,7 @@ bool CheckLelantusJMintTransaction( // Update public coin list in the info lelantusTxInfo->mints.push_back(std::make_pair(pubCoin, std::make_pair(amount, mintTag))); + lelantusTxInfo->encryptedJmintValues.insert(std::make_pair(pubCoin, encryptedValue)); lelantusTxInfo->zcTransactions.insert(hashTx); } @@ -1196,9 +1197,16 @@ void CLelantusState::AddMintsToStateAndBlockIndex( CBlockIndex *index, const CBlock* pblock) { - std::vector> blockMints; + std::vector>> blockMints; for (const auto& mint : pblock->lelantusTxInfo->mints) { - blockMints.push_back(std::make_pair(mint.first, mint.second.second)); + lelantus::MintValueData mintdata; + mintdata.amount = mint.second.first; + if (pblock->lelantusTxInfo->encryptedJmintValues.count(mint.first) > 0) { + mintdata.isJMint = true; + mintdata.encryptedValue = pblock->lelantusTxInfo->encryptedJmintValues[mint.first]; + } + + blockMints.push_back(std::make_pair(mint.first, std::make_pair(mintdata, mint.second.second))); } latestCoinId = std::max(1, latestCoinId); @@ -1233,7 +1241,7 @@ void CLelantusState::AddMintsToStateAndBlockIndex( } for (const auto& mint : blockMints) { - containers.AddMint(mint.first, CMintedCoinInfo::make(latestCoinId, index->nHeight), mint.second); + containers.AddMint(mint.first, CMintedCoinInfo::make(latestCoinId, index->nHeight), mint.second.second); LogPrintf("AddMintsToStateAndBlockIndex: Lelantus mint added id=%d\n", latestCoinId); index->lelantusMintedPubCoins[latestCoinId].push_back(mint); @@ -1268,7 +1276,7 @@ void CLelantusState::AddBlock(CBlockIndex *index) { latestCoinId = pubCoins.first; for (auto const &coin : pubCoins.second) { - containers.AddMint(coin.first, CMintedCoinInfo::make(pubCoins.first, index->nHeight), coin.second); + containers.AddMint(coin.first, CMintedCoinInfo::make(pubCoins.first, index->nHeight), coin.second.second); } } @@ -1422,6 +1430,10 @@ int CLelantusState::GetCoinSetForSpend( continue; } + if (block->GetBlockHash().GetHex() == start_block_hash) { + break ; + } + // check coins in group coinGroupID - 1 in the case that using coins from prev group. int id = 0; if (CountCoinInBlock(block, coinGroupID)) { @@ -1452,7 +1464,7 @@ int CLelantusState::GetCoinSetForSpend( } } - if (block == coinGroup.firstBlock || block->GetBlockHash().GetHex() == start_block_hash) { + if (block == coinGroup.firstBlock) { break ; } } @@ -1460,6 +1472,41 @@ int CLelantusState::GetCoinSetForSpend( return numberOfCoins; } +void CLelantusState::GetCoinsForRecovery( + int coinGroupID, + std::vector>>& coins) { + coins.clear(); + + if (coinGroups.count(coinGroupID) == 0) { + return; + } + + LelantusCoinGroupInfo &coinGroup = coinGroups[coinGroupID]; + + for (CBlockIndex *block = coinGroup.lastBlock;; block = block->pprev) { + // check coins in group coinGroupID - 1 in the case that using coins from prev group. + int id = 0; + if (CountCoinInBlock(block, coinGroupID)) { + id = coinGroupID; + } else if (CountCoinInBlock(block, coinGroupID - 1)) { + id = coinGroupID - 1; + } + + if (id) { + if (block->lelantusMintedPubCoins.count(id) > 0) { + for (const auto &coin : block->lelantusMintedPubCoins[id]) { + coins.push_back(coin); + } + } + } + + if (block == coinGroup.firstBlock) { + break ; + } + } + +} + void CLelantusState::GetAnonymitySet( int coinGroupID, bool fStartLelantusBlacklist, diff --git a/src/lelantus.h b/src/lelantus.h index 0ef48810d8..262a64d81d 100644 --- a/src/lelantus.h +++ b/src/lelantus.h @@ -27,6 +27,8 @@ class CLelantusTxInfo { // Vector of > for all the mints. std::vector>> mints; + std::unordered_map, lelantus::CPublicCoinHash> encryptedJmintValues; + // serial for every spend (map from serial to coin group id) std::unordered_map spentSerials; @@ -197,6 +199,10 @@ friend bool BuildLelantusStateFromIndex(CChain *, std::set &); bool fStartLelantusBlacklist, std::vector& coins_out); + void GetCoinsForRecovery( + int coinGroupID, + std::vector>>& coins); + // Return height of mint transaction and id of minted coin std::pair GetMintedCoinHeightAndId(const lelantus::PublicCoin& pubCoin); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 8f9cece7aa..79a0db42ba 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -192,6 +192,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getmintmetadata", 0 }, { "getusedcoinserials", 0 }, { "getlatestcoinids", 0 }, + { "getcoinsforrecovery", 0 }, /* Elysium - data retrieval calls */ { "elysium_gettradehistoryforaddress", 1 }, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 4da5e2ad73..9c002036bd 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1101,6 +1101,50 @@ UniValue getlatestcoinid(const JSONRPCRequest& request) return UniValue(latestCoinId); } +UniValue getcoinsforrecovery(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 0) + throw std::runtime_error( + "getcoinsforrecovery\n" + "\nResult:\n" + "{\n" + " \"setID\" (int) Set id\n" + " \"mints\" (Pair>) Public coins paired with mint tag and mint value\n" + " \"jmints\"(Pair>) Public coins paired with mint tag and encrypted mint value \n" + "}\n" + ); + + lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); + int latestCoinId = lelantusState->GetLatestCoinID(); + + UniValue ret(UniValue::VOBJ); + while (latestCoinId) { + ret.push_back(Pair("setID", latestCoinId)); + std::vector>> coins; + lelantusState->GetCoinsForRecovery(latestCoinId, coins); + UniValue mints(UniValue::VARR); + UniValue jmints(UniValue::VARR); + for (const auto& coin : coins) { + if (coin.second.first.isJMint) { + std::vector vch = coin.first.getValue().getvch(); + UniValue data(UniValue::VARR); + data.push_back(Pair(coin.second.second.GetHex(), HexStr(coin.second.first.encryptedValue.begin(), coin.second.first.encryptedValue.end()))); + jmints.push_back(Pair(HexStr(vch.begin(), vch.end()), data)); + } else { + std::vector vch = coin.first.getValue().getvch(); + UniValue data(UniValue::VARR); + data.push_back(Pair(coin.second.second.GetHex(), coin.second.first.amount)); + mints.push_back(Pair(HexStr(vch.begin(), vch.end()), data)); + } + } + ret.push_back(Pair("mints", mints)); + ret.push_back(Pair("jmints", jmints)); + latestCoinId--; + } + + return ret; +} + UniValue getaddresstxids(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) @@ -1389,6 +1433,8 @@ static const CRPCCommand commands[] = { "mobile", "getusedcoinserials", &getusedcoinserials, true }, { "mobile", "getfeerate", &getfeerate, true }, { "mobile", "getlatestcoinid", &getlatestcoinid, true }, + { "mobile", "getcoinsforrecovery", &getcoinsforrecovery, true }, + { "hidden", "setmocktime", &setmocktime, true, {"timestamp"}}, { "hidden", "echo", &echo, true, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index c6b9100ac6..447ca784f4 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -337,6 +337,7 @@ static const CRPCCommand vRPCCommands[] = { "mobile", "getusedcoinserials", &getusedcoinserials, true }, { "mobile", "getfeerate", &getfeerate, true }, { "mobile", "getlatestcoinid", &getlatestcoinid, true }, + { "mobile", "getcoinsforrecovery", &getcoinsforrecovery, true }, }; CRPCTable::CRPCTable() diff --git a/src/rpc/server.h b/src/rpc/server.h index 8b87c8efb3..f1dd848844 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -212,6 +212,7 @@ extern UniValue getmintmetadata(const JSONRPCRequest& params); extern UniValue getusedcoinserials(const JSONRPCRequest& params); extern UniValue getfeerate(const JSONRPCRequest& params); extern UniValue getlatestcoinid(const JSONRPCRequest& params); +extern UniValue getcoinsforrecovery(const JSONRPCRequest& params); extern UniValue znode(const JSONRPCRequest &request); extern UniValue znodelist(const JSONRPCRequest &request); From a6e8a60ea647bc5d1a110f7b79e70a470191db1e Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 26 Nov 2021 02:17:33 +0400 Subject: [PATCH 013/197] Including block hash into recovery rpc --- src/lelantus.cpp | 20 ++++++++++++++++++-- src/lelantus.h | 5 ++++- src/rpc/misc.cpp | 13 ++++++++++--- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/lelantus.cpp b/src/lelantus.cpp index a4d905c786..5988aedf6c 100644 --- a/src/lelantus.cpp +++ b/src/lelantus.cpp @@ -974,6 +974,19 @@ bool GetOutPointFromBlock(COutPoint& outPoint, const GroupElement &pubCoinValue, return false; } +uint256 GetBlockHashFromPubcoin(const lelantus::PublicCoin& pubCoin) { + lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); + auto mintedCoinHeightAndId = lelantusState->GetMintedCoinHeightAndId(pubCoin); + int mintHeight = mintedCoinHeightAndId.first; + int coinId = mintedCoinHeightAndId.second; + + if(mintHeight==-1 && coinId==-1) + return uint256(); + + // get hash of theblock containing mint + return chainActive[mintHeight]->GetBlockHash(); +} + bool GetOutPoint(COutPoint& outPoint, const lelantus::PublicCoin &pubCoin) { lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); @@ -1474,9 +1487,11 @@ int CLelantusState::GetCoinSetForSpend( void CLelantusState::GetCoinsForRecovery( int coinGroupID, - std::vector>>& coins) { - coins.clear(); + std::vector>>& coins, + std::vector& blockHashes) { + coins.clear(); + blockHashes.clear(); if (coinGroups.count(coinGroupID) == 0) { return; } @@ -1496,6 +1511,7 @@ void CLelantusState::GetCoinsForRecovery( if (block->lelantusMintedPubCoins.count(id) > 0) { for (const auto &coin : block->lelantusMintedPubCoins[id]) { coins.push_back(coin); + blockHashes.push_back(GetBlockHashFromPubcoin(coin.first)); } } } diff --git a/src/lelantus.h b/src/lelantus.h index 262a64d81d..130a9373e8 100644 --- a/src/lelantus.h +++ b/src/lelantus.h @@ -80,6 +80,8 @@ bool ConnectBlockLelantus( const CBlock *pblock, bool fJustCheck=false); +uint256 GetBlockHashFromPubcoin(const lelantus::PublicCoin& pubCoin); + /* * Get COutPoint(txHash, index) from the chain using pubcoin value alone. */ @@ -201,7 +203,8 @@ friend bool BuildLelantusStateFromIndex(CChain *, std::set &); void GetCoinsForRecovery( int coinGroupID, - std::vector>>& coins); + std::vector>>& coins, + std::vector& blockHashes); // Return height of mint transaction and id of minted coin std::pair GetMintedCoinHeightAndId(const lelantus::PublicCoin& pubCoin); diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 9c002036bd..0f3519c034 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1109,8 +1109,8 @@ UniValue getcoinsforrecovery(const JSONRPCRequest& request) "\nResult:\n" "{\n" " \"setID\" (int) Set id\n" - " \"mints\" (Pair>) Public coins paired with mint tag and mint value\n" - " \"jmints\"(Pair>) Public coins paired with mint tag and encrypted mint value \n" + " \"mints\" (Pair>) Public coins paired with block hash which is paired with mint tag and mint value\n" + " \"jmints\"(Pair>>) Public coins paired with block hash which is paired with mint tag and encrypted mint value \n" "}\n" ); @@ -1121,21 +1121,28 @@ UniValue getcoinsforrecovery(const JSONRPCRequest& request) while (latestCoinId) { ret.push_back(Pair("setID", latestCoinId)); std::vector>> coins; - lelantusState->GetCoinsForRecovery(latestCoinId, coins); + std::vector blockHashes; + lelantusState->GetCoinsForRecovery(latestCoinId, coins, blockHashes); UniValue mints(UniValue::VARR); UniValue jmints(UniValue::VARR); + int i = 0; for (const auto& coin : coins) { if (coin.second.first.isJMint) { std::vector vch = coin.first.getValue().getvch(); UniValue data(UniValue::VARR); data.push_back(Pair(coin.second.second.GetHex(), HexStr(coin.second.first.encryptedValue.begin(), coin.second.first.encryptedValue.end()))); + UniValue dataAndBlockHash(UniValue::VARR); + dataAndBlockHash.push_back(Pair(blockHashes[i].GetHex(), data)); jmints.push_back(Pair(HexStr(vch.begin(), vch.end()), data)); } else { std::vector vch = coin.first.getValue().getvch(); UniValue data(UniValue::VARR); data.push_back(Pair(coin.second.second.GetHex(), coin.second.first.amount)); + UniValue dataAndBlockHash(UniValue::VARR); + dataAndBlockHash.push_back(Pair(blockHashes[i].GetHex(), data)); mints.push_back(Pair(HexStr(vch.begin(), vch.end()), data)); } + i++; } ret.push_back(Pair("mints", mints)); ret.push_back(Pair("jmints", jmints)); From aa03340e9f0650945dd79b9b25f73ed7b6b75ab4 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 28 Nov 2021 02:44:31 +0400 Subject: [PATCH 014/197] Fixed json creation in getcoinsforrecovery rpc --- src/rpc/misc.cpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 0f3519c034..16acc9f3e7 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1129,18 +1129,24 @@ UniValue getcoinsforrecovery(const JSONRPCRequest& request) for (const auto& coin : coins) { if (coin.second.first.isJMint) { std::vector vch = coin.first.getValue().getvch(); - UniValue data(UniValue::VARR); - data.push_back(Pair(coin.second.second.GetHex(), HexStr(coin.second.first.encryptedValue.begin(), coin.second.first.encryptedValue.end()))); - UniValue dataAndBlockHash(UniValue::VARR); - dataAndBlockHash.push_back(Pair(blockHashes[i].GetHex(), data)); - jmints.push_back(Pair(HexStr(vch.begin(), vch.end()), data)); + std::vector data; + data.push_back(HexStr(vch.begin(), vch.end())); + data.push_back(coin.second.second.GetHex()); + data.push_back(HexStr(coin.second.first.encryptedValue.begin(), coin.second.first.encryptedValue.end())); + data.push_back(blockHashes[i].GetHex()); + UniValue entity(UniValue::VARR); + entity.push_backV(data); + jmints.push_back(entity); } else { std::vector vch = coin.first.getValue().getvch(); - UniValue data(UniValue::VARR); - data.push_back(Pair(coin.second.second.GetHex(), coin.second.first.amount)); - UniValue dataAndBlockHash(UniValue::VARR); - dataAndBlockHash.push_back(Pair(blockHashes[i].GetHex(), data)); - mints.push_back(Pair(HexStr(vch.begin(), vch.end()), data)); + std::vector data; + data.push_back(HexStr(vch.begin(), vch.end())); + data.push_back(coin.second.second.GetHex()); + data.push_back(coin.second.first.amount); + data.push_back(blockHashes[i].GetHex()); + UniValue entity(UniValue::VARR); + entity.push_backV(data); + mints.push_back(entity); } i++; } From f7b243664cfa15835e1bdbaaac7c8055dda93c29 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 6 Dec 2021 00:27:17 +0400 Subject: [PATCH 015/197] Putting tx hash instead of block hash into coin list for recovery --- src/lelantus.cpp | 21 +++++++-------------- src/lelantus.h | 4 ++-- src/rpc/misc.cpp | 12 ++++++------ 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/lelantus.cpp b/src/lelantus.cpp index 5988aedf6c..77df6582c6 100644 --- a/src/lelantus.cpp +++ b/src/lelantus.cpp @@ -974,17 +974,10 @@ bool GetOutPointFromBlock(COutPoint& outPoint, const GroupElement &pubCoinValue, return false; } -uint256 GetBlockHashFromPubcoin(const lelantus::PublicCoin& pubCoin) { - lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); - auto mintedCoinHeightAndId = lelantusState->GetMintedCoinHeightAndId(pubCoin); - int mintHeight = mintedCoinHeightAndId.first; - int coinId = mintedCoinHeightAndId.second; - - if(mintHeight==-1 && coinId==-1) - return uint256(); - - // get hash of theblock containing mint - return chainActive[mintHeight]->GetBlockHash(); +uint256 GetTxHashFromPubcoin(const lelantus::PublicCoin& pubCoin) { + COutPoint outPoint; + GetOutPoint(outPoint, pubCoin.getValue()); + return outPoint.hash; } bool GetOutPoint(COutPoint& outPoint, const lelantus::PublicCoin &pubCoin) { @@ -1488,10 +1481,10 @@ int CLelantusState::GetCoinSetForSpend( void CLelantusState::GetCoinsForRecovery( int coinGroupID, std::vector>>& coins, - std::vector& blockHashes) { + std::vector& txHashes) { coins.clear(); - blockHashes.clear(); + txHashes.clear(); if (coinGroups.count(coinGroupID) == 0) { return; } @@ -1511,7 +1504,7 @@ void CLelantusState::GetCoinsForRecovery( if (block->lelantusMintedPubCoins.count(id) > 0) { for (const auto &coin : block->lelantusMintedPubCoins[id]) { coins.push_back(coin); - blockHashes.push_back(GetBlockHashFromPubcoin(coin.first)); + txHashes.push_back(GetTxHashFromPubcoin(coin.first)); } } } diff --git a/src/lelantus.h b/src/lelantus.h index 130a9373e8..671f853b4a 100644 --- a/src/lelantus.h +++ b/src/lelantus.h @@ -80,7 +80,7 @@ bool ConnectBlockLelantus( const CBlock *pblock, bool fJustCheck=false); -uint256 GetBlockHashFromPubcoin(const lelantus::PublicCoin& pubCoin); +uint256 GetTxHashFromPubcoin(const lelantus::PublicCoin& pubCoin); /* * Get COutPoint(txHash, index) from the chain using pubcoin value alone. @@ -204,7 +204,7 @@ friend bool BuildLelantusStateFromIndex(CChain *, std::set &); void GetCoinsForRecovery( int coinGroupID, std::vector>>& coins, - std::vector& blockHashes); + std::vector& txHashes); // Return height of mint transaction and id of minted coin std::pair GetMintedCoinHeightAndId(const lelantus::PublicCoin& pubCoin); diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 16acc9f3e7..0e2e4d5cde 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1109,8 +1109,8 @@ UniValue getcoinsforrecovery(const JSONRPCRequest& request) "\nResult:\n" "{\n" " \"setID\" (int) Set id\n" - " \"mints\" (Pair>) Public coins paired with block hash which is paired with mint tag and mint value\n" - " \"jmints\"(Pair>>) Public coins paired with block hash which is paired with mint tag and encrypted mint value \n" + " \"mints\" (Pair>) Public coins paired with txhash which is paired with mint tag and mint value\n" + " \"jmints\"(Pair>>) Public coins paired with txhash which is paired with mint tag and encrypted mint value \n" "}\n" ); @@ -1121,8 +1121,8 @@ UniValue getcoinsforrecovery(const JSONRPCRequest& request) while (latestCoinId) { ret.push_back(Pair("setID", latestCoinId)); std::vector>> coins; - std::vector blockHashes; - lelantusState->GetCoinsForRecovery(latestCoinId, coins, blockHashes); + std::vector txHashes; + lelantusState->GetCoinsForRecovery(latestCoinId, coins, txHashes); UniValue mints(UniValue::VARR); UniValue jmints(UniValue::VARR); int i = 0; @@ -1133,7 +1133,7 @@ UniValue getcoinsforrecovery(const JSONRPCRequest& request) data.push_back(HexStr(vch.begin(), vch.end())); data.push_back(coin.second.second.GetHex()); data.push_back(HexStr(coin.second.first.encryptedValue.begin(), coin.second.first.encryptedValue.end())); - data.push_back(blockHashes[i].GetHex()); + data.push_back(txHashes[i].GetHex()); UniValue entity(UniValue::VARR); entity.push_backV(data); jmints.push_back(entity); @@ -1143,7 +1143,7 @@ UniValue getcoinsforrecovery(const JSONRPCRequest& request) data.push_back(HexStr(vch.begin(), vch.end())); data.push_back(coin.second.second.GetHex()); data.push_back(coin.second.first.amount); - data.push_back(blockHashes[i].GetHex()); + data.push_back(txHashes[i].GetHex()); UniValue entity(UniValue::VARR); entity.push_backV(data); mints.push_back(entity); From 50cfa23cb47be4a831dbd0886214cf9eb6fd24b5 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Tue, 7 Dec 2021 14:45:05 +0400 Subject: [PATCH 016/197] Pushing sets as vector of json files --- src/rpc/misc.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 0e2e4d5cde..d1e7894cc9 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1117,9 +1117,10 @@ UniValue getcoinsforrecovery(const JSONRPCRequest& request) lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); int latestCoinId = lelantusState->GetLatestCoinID(); - UniValue ret(UniValue::VOBJ); + std::vector vSet; while (latestCoinId) { - ret.push_back(Pair("setID", latestCoinId)); + UniValue setEntity(UniValue::VOBJ); + setEntity.push_back(Pair("setID", latestCoinId)); std::vector>> coins; std::vector txHashes; lelantusState->GetCoinsForRecovery(latestCoinId, coins, txHashes); @@ -1150,11 +1151,13 @@ UniValue getcoinsforrecovery(const JSONRPCRequest& request) } i++; } - ret.push_back(Pair("mints", mints)); - ret.push_back(Pair("jmints", jmints)); + setEntity.push_back(Pair("mints", mints)); + setEntity.push_back(Pair("jmints", jmints)); latestCoinId--; + vSet.push_back(setEntity); } - + UniValue ret(UniValue::VOBJ); + ret.push_backV(vSet); return ret; } From 3e346f02e72fe4772fc1e4e0c7023dce35c532fa Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 9 Dec 2021 03:01:43 +0400 Subject: [PATCH 017/197] Fixed json creation --- src/rpc/misc.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index d1e7894cc9..83734f1fa8 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1116,8 +1116,7 @@ UniValue getcoinsforrecovery(const JSONRPCRequest& request) lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); int latestCoinId = lelantusState->GetLatestCoinID(); - - std::vector vSet; + UniValue ret(UniValue::VOBJ); while (latestCoinId) { UniValue setEntity(UniValue::VOBJ); setEntity.push_back(Pair("setID", latestCoinId)); @@ -1153,11 +1152,10 @@ UniValue getcoinsforrecovery(const JSONRPCRequest& request) } setEntity.push_back(Pair("mints", mints)); setEntity.push_back(Pair("jmints", jmints)); + ret.push_back(Pair(std::to_string(latestCoinId), setEntity)); latestCoinId--; - vSet.push_back(setEntity); } - UniValue ret(UniValue::VOBJ); - ret.push_backV(vSet); + return ret; } From 7f364e65076cb02d9cb09c0941911814f9fbb2a9 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 13 Dec 2021 17:26:27 +0400 Subject: [PATCH 018/197] Returning sets separately during restoration --- src/rpc/misc.cpp | 83 +++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 83734f1fa8..90908f288a 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1103,58 +1103,63 @@ UniValue getlatestcoinid(const JSONRPCRequest& request) UniValue getcoinsforrecovery(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) + if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "getcoinsforrecovery\n" + "\nArguments:\n" + "{\n" + " \"coinGroupId\" (int)\n" + "}\n" "\nResult:\n" "{\n" - " \"setID\" (int) Set id\n" " \"mints\" (Pair>) Public coins paired with txhash which is paired with mint tag and mint value\n" " \"jmints\"(Pair>>) Public coins paired with txhash which is paired with mint tag and encrypted mint value \n" "}\n" ); + int coinGroupId; + try { + coinGroupId = request.params[0].get_int(); + } catch (std::logic_error const & e) { + throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); + } + + lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); - int latestCoinId = lelantusState->GetLatestCoinID(); UniValue ret(UniValue::VOBJ); - while (latestCoinId) { - UniValue setEntity(UniValue::VOBJ); - setEntity.push_back(Pair("setID", latestCoinId)); - std::vector>> coins; - std::vector txHashes; - lelantusState->GetCoinsForRecovery(latestCoinId, coins, txHashes); - UniValue mints(UniValue::VARR); - UniValue jmints(UniValue::VARR); - int i = 0; - for (const auto& coin : coins) { - if (coin.second.first.isJMint) { - std::vector vch = coin.first.getValue().getvch(); - std::vector data; - data.push_back(HexStr(vch.begin(), vch.end())); - data.push_back(coin.second.second.GetHex()); - data.push_back(HexStr(coin.second.first.encryptedValue.begin(), coin.second.first.encryptedValue.end())); - data.push_back(txHashes[i].GetHex()); - UniValue entity(UniValue::VARR); - entity.push_backV(data); - jmints.push_back(entity); - } else { - std::vector vch = coin.first.getValue().getvch(); - std::vector data; - data.push_back(HexStr(vch.begin(), vch.end())); - data.push_back(coin.second.second.GetHex()); - data.push_back(coin.second.first.amount); - data.push_back(txHashes[i].GetHex()); - UniValue entity(UniValue::VARR); - entity.push_backV(data); - mints.push_back(entity); - } - i++; + + std::vector>> coins; + std::vector txHashes; + lelantusState->GetCoinsForRecovery(coinGroupId, coins, txHashes); + UniValue mints(UniValue::VARR); + UniValue jmints(UniValue::VARR); + int i = 0; + for (const auto& coin : coins) { + if (coin.second.first.isJMint) { + std::vector vch = coin.first.getValue().getvch(); + std::vector data; + data.push_back(HexStr(vch.begin(), vch.end())); + data.push_back(coin.second.second.GetHex()); + data.push_back(HexStr(coin.second.first.encryptedValue.begin(), coin.second.first.encryptedValue.end())); + data.push_back(txHashes[i].GetHex()); + UniValue entity(UniValue::VARR); + entity.push_backV(data); + jmints.push_back(entity); + } else { + std::vector vch = coin.first.getValue().getvch(); + std::vector data; + data.push_back(HexStr(vch.begin(), vch.end())); + data.push_back(coin.second.second.GetHex()); + data.push_back(coin.second.first.amount); + data.push_back(txHashes[i].GetHex()); + UniValue entity(UniValue::VARR); + entity.push_backV(data); + mints.push_back(entity); } - setEntity.push_back(Pair("mints", mints)); - setEntity.push_back(Pair("jmints", jmints)); - ret.push_back(Pair(std::to_string(latestCoinId), setEntity)); - latestCoinId--; + i++; } + ret.push_back(Pair("mints", mints)); + ret.push_back(Pair("jmints", jmints)); return ret; } From 62345ffbfde779118206eb9e896fb8c1b01651ed Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sat, 19 Feb 2022 05:10:55 +0400 Subject: [PATCH 019/197] getanonymityset and getusedcoinserials rpc calls refactored --- src/lelantus.cpp | 31 ++++++++++++ src/lelantus.h | 11 +++-- src/rpc/client.cpp | 1 - src/rpc/misc.cpp | 116 ++++++++++++++++----------------------------- src/rpc/server.cpp | 3 +- src/rpc/server.h | 1 - 6 files changed, 80 insertions(+), 83 deletions(-) diff --git a/src/lelantus.cpp b/src/lelantus.cpp index b4ef2a27f3..905fa98468 100644 --- a/src/lelantus.cpp +++ b/src/lelantus.cpp @@ -1492,8 +1492,13 @@ int CLelantusState::GetCoinSetForSpend( } void CLelantusState::GetCoinsForRecovery( + CChain *chain, + int maxHeight, int coinGroupID, + std::string start_block_hash, + uint256& blockHash_out, std::vector>>& coins, + std::vector& setHash_out, std::vector& txHashes) { coins.clear(); @@ -1504,7 +1509,17 @@ void CLelantusState::GetCoinsForRecovery( LelantusCoinGroupInfo &coinGroup = coinGroups[coinGroupID]; + int numberOfCoins = 0; for (CBlockIndex *block = coinGroup.lastBlock;; block = block->pprev) { + // ignore block heigher than max height + if (block->nHeight > maxHeight) { + continue; + } + + if (block->GetBlockHash().GetHex() == start_block_hash) { + break; + } + // check coins in group coinGroupID - 1 in the case that using coins from prev group. int id = 0; if (CountCoinInBlock(block, coinGroupID)) { @@ -1514,8 +1529,24 @@ void CLelantusState::GetCoinsForRecovery( } if (id) { + if (numberOfCoins == 0) { + // latest block satisfying given conditions + // remember block hash and set hash + blockHash_out = block->GetBlockHash(); + setHash_out = GetAnonymitySetHash(block, id); + } + + numberOfCoins += block->lelantusMintedPubCoins[id].size(); if (block->lelantusMintedPubCoins.count(id) > 0) { for (const auto &coin : block->lelantusMintedPubCoins[id]) { + LOCK(cs_main); + // skip mints from blacklist if nLelantusFixesStartBlock is passed + if (chainActive.Height() >= ::Params().GetConsensus().nLelantusFixesStartBlock) { + if (::Params().GetConsensus().lelantusBlacklist.count(coin.first.getValue()) > 0) { + continue; + } + } + coins.push_back(coin); txHashes.push_back(GetTxHashFromPubcoin(coin.first)); } diff --git a/src/lelantus.h b/src/lelantus.h index 671f853b4a..a74e4e3518 100644 --- a/src/lelantus.h +++ b/src/lelantus.h @@ -202,9 +202,14 @@ friend bool BuildLelantusStateFromIndex(CChain *, std::set &); std::vector& coins_out); void GetCoinsForRecovery( - int coinGroupID, - std::vector>>& coins, - std::vector& txHashes); + CChain *chain, + int maxHeight, + int coinGroupID, + std::string start_block_hash, + uint256& blockHash_out, + std::vector>>& coins, + std::vector& setHash_out, + std::vector& txHashes); // Return height of mint transaction and id of minted coin std::pair GetMintedCoinHeightAndId(const lelantus::PublicCoin& pubCoin); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index e9002879a2..0908d28ace 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -193,7 +193,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getmintmetadata", 0 }, { "getusedcoinserials", 0 }, { "getlatestcoinids", 0 }, - { "getcoinsforrecovery", 0 }, /* Elysium - data retrieval calls */ { "elysium_gettradehistoryforaddress", 1 }, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 90908f288a..dd240ed7df 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -935,7 +935,8 @@ UniValue getanonymityset(const JSONRPCRequest& request) "\nResult:\n" "{\n" " \"blockHash\" (string) Latest block hash for anonymity set\n" - " \"anonymityset\"(std::string[]) array of Serialized GroupElements\n" + " \"setHash\" (string) Anonymity set hash\n" + " \"mints\" (Pair>) Serialized GroupElements paired with txhash which is paired with mint tag and mint value\n" "}\n" + HelpExampleCli("getanonymityset", "100000000 1") + HelpExampleRpc("getanonymityset", "\"100000000\", \"1\"") @@ -952,31 +953,47 @@ UniValue getanonymityset(const JSONRPCRequest& request) } uint256 blockHash; - std::vector coins; + std::vector>> coins; std::vector setHash; + std::vector txHashes; { LOCK(cs_main); lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); - lelantusState->GetCoinSetForSpend( + lelantusState->GetCoinsForRecovery( &chainActive, chainActive.Height() - (ZC_MINT_CONFIRMATIONS - 1), coinGroupId, + startBlockHash, blockHash, coins, setHash, - startBlockHash); + txHashes); } - UniValue serializedCoins(UniValue::VARR); - for(lelantus::PublicCoin const & coin : coins) { - std::vector vch = coin.getValue().getvch(); - serializedCoins.push_back(HexStr(vch.begin(), vch.end())); + UniValue ret(UniValue::VOBJ); + UniValue mints(UniValue::VARR); + + int i = 0; + for (const auto& coin : coins) { + std::vector vch = coin.first.getValue().getvch(); + std::vector data; + data.push_back(HexStr(vch.begin(), vch.end())); + data.push_back(coin.second.second.GetHex()); + if (coin.second.first.isJMint) { + data.push_back(HexStr(coin.second.first.encryptedValue.begin(), coin.second.first.encryptedValue.end())); + } else { + data.push_back(coin.second.first.amount); + } + data.push_back(txHashes[i].GetHex()); + UniValue entity(UniValue::VARR); + entity.push_backV(data); + mints.push_back(entity); + i++; } - UniValue ret(UniValue::VOBJ); ret.push_back(Pair("blockHash", blockHash.GetHex())); ret.push_back(Pair("setHash", UniValue(HexStr(setHash.begin(), setHash.end())))); - ret.push_back(Pair("serializedCoins", serializedCoins)); + ret.push_back(Pair("coins", mints)); return ret; } @@ -1033,12 +1050,23 @@ UniValue getusedcoinserials(const JSONRPCRequest& request) throw std::runtime_error( "getusedcoinserials\n" "\nReturns the set of used coin serial.\n" + "\nArguments:\n" + "{\n" + " \"startNumber \" (int) Number of elements already existing on user side\n" + "}\n" "\nResult:\n" "{\n" " \"serials\" (std::string[]) array of Serialized Scalars\n" "}\n" ); + int startNumber; + try { + startNumber = std::stol(request.params[0].get_str()); + } catch (std::logic_error const & e) { + throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); + } + lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); std::unordered_map serials; { @@ -1047,7 +1075,7 @@ UniValue getusedcoinserials(const JSONRPCRequest& request) } UniValue serializedSerials(UniValue::VARR); - for ( auto it = serials.begin(); it != serials.end(); ++it ) + for ( auto it = serials.begin() + startNumber; it != serials.end(); ++it ) serializedSerials.push_back(it->first.GetHex()); UniValue ret(UniValue::VOBJ); @@ -1101,69 +1129,6 @@ UniValue getlatestcoinid(const JSONRPCRequest& request) return UniValue(latestCoinId); } -UniValue getcoinsforrecovery(const JSONRPCRequest& request) -{ - if (request.fHelp || request.params.size() != 1) - throw std::runtime_error( - "getcoinsforrecovery\n" - "\nArguments:\n" - "{\n" - " \"coinGroupId\" (int)\n" - "}\n" - "\nResult:\n" - "{\n" - " \"mints\" (Pair>) Public coins paired with txhash which is paired with mint tag and mint value\n" - " \"jmints\"(Pair>>) Public coins paired with txhash which is paired with mint tag and encrypted mint value \n" - "}\n" - ); - - int coinGroupId; - try { - coinGroupId = request.params[0].get_int(); - } catch (std::logic_error const & e) { - throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); - } - - - lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); - UniValue ret(UniValue::VOBJ); - - std::vector>> coins; - std::vector txHashes; - lelantusState->GetCoinsForRecovery(coinGroupId, coins, txHashes); - UniValue mints(UniValue::VARR); - UniValue jmints(UniValue::VARR); - int i = 0; - for (const auto& coin : coins) { - if (coin.second.first.isJMint) { - std::vector vch = coin.first.getValue().getvch(); - std::vector data; - data.push_back(HexStr(vch.begin(), vch.end())); - data.push_back(coin.second.second.GetHex()); - data.push_back(HexStr(coin.second.first.encryptedValue.begin(), coin.second.first.encryptedValue.end())); - data.push_back(txHashes[i].GetHex()); - UniValue entity(UniValue::VARR); - entity.push_backV(data); - jmints.push_back(entity); - } else { - std::vector vch = coin.first.getValue().getvch(); - std::vector data; - data.push_back(HexStr(vch.begin(), vch.end())); - data.push_back(coin.second.second.GetHex()); - data.push_back(coin.second.first.amount); - data.push_back(txHashes[i].GetHex()); - UniValue entity(UniValue::VARR); - entity.push_backV(data); - mints.push_back(entity); - } - i++; - } - ret.push_back(Pair("mints", mints)); - ret.push_back(Pair("jmints", jmints)); - - return ret; -} - UniValue getaddresstxids(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) @@ -1449,10 +1414,9 @@ static const CRPCCommand commands[] = /* Mobile related */ { "mobile", "getanonymityset", &getanonymityset, false }, { "mobile", "getmintmetadata", &getmintmetadata, true }, - { "mobile", "getusedcoinserials", &getusedcoinserials, true }, + { "mobile", "getusedcoinserials", &getusedcoinserials, false }, { "mobile", "getfeerate", &getfeerate, true }, { "mobile", "getlatestcoinid", &getlatestcoinid, true }, - { "mobile", "getcoinsforrecovery", &getcoinsforrecovery, true }, { "hidden", "setmocktime", &setmocktime, true, {"timestamp"}}, diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 447ca784f4..7908baa564 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -334,10 +334,9 @@ static const CRPCCommand vRPCCommands[] = /* Mobile related */ { "mobile", "getanonymityset", &getanonymityset, false }, { "mobile", "getmintmetadata", &getmintmetadata, true }, - { "mobile", "getusedcoinserials", &getusedcoinserials, true }, + { "mobile", "getusedcoinserials", &getusedcoinserials, false }, { "mobile", "getfeerate", &getfeerate, true }, { "mobile", "getlatestcoinid", &getlatestcoinid, true }, - { "mobile", "getcoinsforrecovery", &getcoinsforrecovery, true }, }; CRPCTable::CRPCTable() diff --git a/src/rpc/server.h b/src/rpc/server.h index f1dd848844..8b87c8efb3 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -212,7 +212,6 @@ extern UniValue getmintmetadata(const JSONRPCRequest& params); extern UniValue getusedcoinserials(const JSONRPCRequest& params); extern UniValue getfeerate(const JSONRPCRequest& params); extern UniValue getlatestcoinid(const JSONRPCRequest& params); -extern UniValue getcoinsforrecovery(const JSONRPCRequest& params); extern UniValue znode(const JSONRPCRequest &request); extern UniValue znodelist(const JSONRPCRequest &request); From a9fc2752d272dbe647c1144e3d9de11fd2396faa Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 20 Feb 2022 01:40:15 +0400 Subject: [PATCH 020/197] Compilation fixed --- src/rpc/misc.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index dd240ed7df..f44dda5118 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1075,8 +1075,12 @@ UniValue getusedcoinserials(const JSONRPCRequest& request) } UniValue serializedSerials(UniValue::VARR); - for ( auto it = serials.begin() + startNumber; it != serials.end(); ++it ) + int i = 0; + for ( auto it = serials.begin(); it != serials.end(); ++it, ++i) { + if (i < startNumber) + continue; serializedSerials.push_back(it->first.GetHex()); + } UniValue ret(UniValue::VOBJ); ret.push_back(Pair("serials", serializedSerials)); From ba96c1bc0c49808cb42b0a67bcdfab5cfbdd81c0 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 21 Feb 2022 02:24:44 +0400 Subject: [PATCH 021/197] Fixed getusedcoinserials argument number bug --- src/rpc/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index f44dda5118..c67111626f 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1046,7 +1046,7 @@ UniValue getmintmetadata(const JSONRPCRequest& request) UniValue getusedcoinserials(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() != 0) + if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "getusedcoinserials\n" "\nReturns the set of used coin serial.\n" From 2b987158f776e950320aa80f346b45cbb4fdf2a8 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Wed, 1 Jun 2022 01:51:29 +0400 Subject: [PATCH 022/197] Spark address/key serialization functions implemented --- src/libspark/keys.cpp | 62 +++++++++++++++++++++++++++++++++++++++++++ src/libspark/keys.h | 11 ++++++++ 2 files changed, 73 insertions(+) diff --git a/src/libspark/keys.cpp b/src/libspark/keys.cpp index 58047c4cbe..69c70027b3 100644 --- a/src/libspark/keys.cpp +++ b/src/libspark/keys.cpp @@ -1,4 +1,5 @@ #include "keys.h" +#include "../hash.h" namespace spark { @@ -12,6 +13,19 @@ SpendKey::SpendKey(const Params* params) { this->r.randomize(); } +SpendKey::SpendKey(const Params* params, const Scalar& r_) { + this->params = params; + this->r = r_; + std::vector data; + r.serialize(data.data()); + std::vector result(CSHA512().OUTPUT_SIZE); + CHash512 hash512; + hash512.Write(data.data(), data.size()).Finalize(&result[0]); + + this->s1.memberFromSeed(&result[0]); + this->s2.memberFromSeed(&result[32]); +} + const Params* SpendKey::get_params() const { return this->params; } @@ -58,6 +72,11 @@ const GroupElement& FullViewKey::get_P2() const { } IncomingViewKey::IncomingViewKey() {} + +IncomingViewKey::IncomingViewKey(const Params* params) { + this->params = params; +} + IncomingViewKey::IncomingViewKey(const FullViewKey& full_view_key) { this->params = full_view_key.get_params(); this->s1 = full_view_key.get_s1(); @@ -115,4 +134,47 @@ const GroupElement& Address::get_Q2() const { return this->Q2; } +std::string Address::GetHex() const { + const std::size_t size = 2* GroupElement::serialize_size + AES_BLOCKSIZE; + std::vector buffer; + buffer.reserve(size); + buffer.resize(2* GroupElement::serialize_size); + unsigned char* ptr = buffer.data(); + ptr = Q1.serialize(ptr); + Q2.serialize(ptr); + buffer.insert(buffer.end(), d.begin(), d.end()); + + std::stringstream ss; + ss << std::hex; + for (const auto b : buffer) { + ss << (b >> 4); + ss << (b & 0xF); + } + + return ss.str(); +} + +void Address::SetHex(const std::string& str) { + const std::size_t size = 2 * GroupElement::serialize_size + AES_BLOCKSIZE; + if (str.size() != size * 2) { + throw "Address: SetHex failed, invalid length"; + } + + std::array buffer; + + for (std::size_t i = 0; i < buffer.size(); i++) { + auto hexs = str.substr(2 * i, 2); + + if (::isxdigit(hexs[0]) && ::isxdigit(hexs[1])) { + buffer[i] = strtol(hexs.c_str(), NULL, 16); + } else { + throw "Address: SetHex failed, invalid hex"; + } + } + + const unsigned char* ptr = Q1.deserialize(buffer.data()); + Q2.deserialize(ptr); + d.insert(d.end(), buffer.begin() + 2 * GroupElement::serialize_size, buffer.end()); +} + } diff --git a/src/libspark/keys.h b/src/libspark/keys.h index 1f34c80bc9..80aba7c7e0 100644 --- a/src/libspark/keys.h +++ b/src/libspark/keys.h @@ -11,6 +11,7 @@ class SpendKey { public: SpendKey(); SpendKey(const Params* params); + SpendKey(const Params* params, const Scalar& r_); const Params* get_params() const; const Scalar& get_s1() const; const Scalar& get_s2() const; @@ -40,12 +41,20 @@ class FullViewKey { class IncomingViewKey { public: IncomingViewKey(); + IncomingViewKey(const Params* params); IncomingViewKey(const FullViewKey& full_view_key); const Params* get_params() const; const Scalar& get_s1() const; const GroupElement& get_P2() const; uint64_t get_diversifier(const std::vector& d) const; + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(s1); + READWRITE(P2); + } + private: const Params* params; Scalar s1; @@ -60,6 +69,8 @@ class Address { const std::vector& get_d() const; const GroupElement& get_Q1() const; const GroupElement& get_Q2() const; + std::string GetHex() const; + void SetHex(const std::string& str); private: const Params* params; From 41a100dafa7039a37c240d6ed537ae716b5617d3 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Wed, 1 Jun 2022 01:57:18 +0400 Subject: [PATCH 023/197] CSparkWallet and mint db functions added --- src/Makefile.am | 5 + src/Makefile.qt.include | 2 +- src/primitives/mint_spend.cpp | 6 + src/primitives/mint_spend.h | 34 ++++++ src/spark.cpp | 9 ++ src/spark.h | 17 +++ src/wallet/spark_wallet.cpp | 219 ++++++++++++++++++++++++++++++++++ src/wallet/spark_wallet.h | 69 +++++++++++ src/wallet/walletdb.cpp | 81 +++++++++++++ src/wallet/walletdb.h | 12 ++ 10 files changed, 453 insertions(+), 1 deletion(-) create mode 100644 src/spark.cpp create mode 100644 src/spark.h create mode 100644 src/wallet/spark_wallet.cpp create mode 100644 src/wallet/spark_wallet.h diff --git a/src/Makefile.am b/src/Makefile.am index c4572dd53e..3741ab0a7d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -218,6 +218,7 @@ BITCOIN_CORE_H = \ wallet/sigmaspendbuilder.h \ wallet/txbuilder.h \ wallet/lelantusjoinsplitbuilder.h \ + wallet/spark_wallet.h \ wallet/wallet.h \ wallet/walletexcept.h \ wallet/walletdb.h \ @@ -241,6 +242,7 @@ BITCOIN_CORE_H = \ bip47/secretpoint.h \ sigma.h \ lelantus.h \ + spark.h \ blacklists.h \ coin_containers.h \ firo_params.h \ @@ -403,6 +405,7 @@ libbitcoin_server_a_SOURCES = \ versionbits.cpp \ sigma.cpp \ lelantus.cpp \ + spark.cpp \ coin_containers.cpp \ mtpstate.cpp \ $(BITCOIN_CORE_H) @@ -429,6 +432,7 @@ libbitcoin_wallet_a_SOURCES = \ hdmint/wallet.cpp \ sigma.cpp \ lelantus.cpp \ + spark.cpp \ wallet/crypter.cpp \ wallet/bip39.cpp \ wallet/mnemoniccontainer.cpp \ @@ -440,6 +444,7 @@ libbitcoin_wallet_a_SOURCES = \ wallet/lelantusjoinsplitbuilder.cpp \ wallet/walletexcept.cpp \ wallet/wallet.cpp \ + wallet/spark_wallet.cpp \ wallet/walletdb.cpp \ wallet/authhelper.cpp \ hdmint/tracker.cpp \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index d9228a0155..40cde59f74 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -584,7 +584,7 @@ endif qt_firo_qt_LDADD += -ltor qt_firo_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) \ - $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBFIRO_SIGMA) $(LIBLELANTUS) \ + $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBFIRO_SIGMA) $(LIBLELANTUS) $(LIBSPARK)\ $(LIBUNIVALUE) $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BACKTRACE_LIB) $(BOOST_LIBS) $(QT_LIBS) \ $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) \ $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) $(LIBBLSSIG_LIBS) $(LIBBLSSIG_DEPENDS) \ diff --git a/src/primitives/mint_spend.cpp b/src/primitives/mint_spend.cpp index 486ca75996..82e57d4e21 100644 --- a/src/primitives/mint_spend.cpp +++ b/src/primitives/mint_spend.cpp @@ -20,6 +20,12 @@ uint256 MintMeta::GetPubCoinValueHash() const { return *pubCoinValueHash; } +uint256 CSparkMintMeta::GetNonceHash() const { + if(!nonceHash) + nonceHash.reset(primitives::GetSerialHash(k)); + return *nonceHash; +} + namespace primitives { uint256 GetSerialHash(const secp_primitives::Scalar& bnSerial) { diff --git a/src/primitives/mint_spend.h b/src/primitives/mint_spend.h index 016db788da..81a7f90f26 100644 --- a/src/primitives/mint_spend.h +++ b/src/primitives/mint_spend.h @@ -13,6 +13,40 @@ #include "sigma/coin.h" #include "serialize.h" #include "firo_params.h" +#include "libspark/coin.h" + +struct CSparkMintMeta +{ + int nHeight; + int nId; + bool isUsed; + uint256 txid; + uint64_t i; // diversifier + std::vector d; // encrypted diversifier + uint64_t v; // value + Scalar k; // nonce + std::string memo; // memo + mutable boost::optional nonceHash; + + uint256 GetNonceHash() const; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(nHeight); + READWRITE(nId); + READWRITE(isUsed); + READWRITE(txid); + READWRITE(i); + READWRITE(d); + READWRITE(v); + READWRITE(k); + READWRITE(memo); + }; +}; + //struct that is safe to store essential mint data, without holding any information that allows for actual spending (serial, randomness, private key) struct MintMeta diff --git a/src/spark.cpp b/src/spark.cpp new file mode 100644 index 0000000000..5f5150e7de --- /dev/null +++ b/src/spark.cpp @@ -0,0 +1,9 @@ +#include "spark.h" + +namespace spark { + +bool GetOutPoint(COutPoint& outPoint, const spark::Coin coin) { + // TODO levon, implement this function after state implementation +} + +} // namespace spark \ No newline at end of file diff --git a/src/spark.h b/src/spark.h new file mode 100644 index 0000000000..43adc28ace --- /dev/null +++ b/src/spark.h @@ -0,0 +1,17 @@ +// Copyright (c) 2022 The Firo Core Developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef _MAIN_SPARK_H__ +#define _MAIN_SPARK_H__ + +#include "libspark/coin.h" +#include "chain.h" + +namespace spark { + +bool GetOutPoint(COutPoint& outPoint, const spark::Coin coin); + +} // namespace spark + +#endif //_MAIN_SPARK_H__ diff --git a/src/wallet/spark_wallet.cpp b/src/wallet/spark_wallet.cpp new file mode 100644 index 0000000000..dc53908f0c --- /dev/null +++ b/src/wallet/spark_wallet.cpp @@ -0,0 +1,219 @@ +#include "spark_wallet.h" +#include "wallet.h" +#include "walletdb.h" +#include "../hash.h" + +const uint32_t DEFAULT_SPARK_NCOUNT = 1; + +CSparkWallet::CSparkWallet(const std::string& strWalletFile) { + + CWalletDB walletdb(strWalletFile); + + const spark::Params* params = spark::Params::get_default(); + viewKey = spark::IncomingViewKey(params); + + // try to get incoming view key from db, if it fails, that means it is first start + if (!walletdb.readIncomingViewKey(viewKey)) { + if (pwalletMain->IsLocked()) { + LogPrintf("Spark wallet creation FAILED, wallet is locked\n"); + return; + } + // Generating spark key set first time + spark::SpendKey spendKey = generateSpendKey(); + spark::FullViewKey fullViewKey = generateFullViewKey(spendKey); + viewKey = generateIncomingViewKey(fullViewKey); + + // Write incoming view key into db, it is safe to be kept in db, it is used to identify incoming coins belonging to the wallet + walletdb.writeIncomingViewKey(viewKey); + lastDiversifier = -1; + // generate one initial address for wallet + addresses[lastDiversifier] = generateNextAddress(); + // set 0 as last diversifier into db, we will update it later, in case coin comes, or user manually generates new address + walletdb.writeDiversifier(lastDiversifier); + } else { + int32_t diversifierInDB = 0; + // read diversifier from db + walletdb.readDiversifier(diversifierInDB); + lastDiversifier = -1; + + // generate all used addresses + while (lastDiversifier < diversifierInDB) { + addresses[lastDiversifier] = generateNextAddress(); + } + + // get the list of coin metadata from db + std::list listMints = walletdb.ListSparkMints(); + for (const auto& itr : listMints) { + coinMeta[itr.GetNonceHash()] = itr; + } + } +} + +void CSparkWallet::resetDiversifierFromDB(CWalletDB& walletdb) { + walletdb.writeDiversifier(lastDiversifier); +} + +void CSparkWallet::updatetDiversifierInDB(CWalletDB& walletdb) { + walletdb.readDiversifier(lastDiversifier); +} + +CAmount CSparkWallet::getFullBalance() { + return getAvailableBalance() + getUnconfirmedBalance(); +} + +CAmount CSparkWallet::getAvailableBalance() { + CAmount result = 0; + for (auto& it : coinMeta) { + CSparkMintMeta mint = it.second; + + if (mint.isUsed) + continue; + + // Not confirmed + if (!mint.nHeight) + continue; + + result += mint.v; + } + return result; +} + +CAmount CSparkWallet::getUnconfirmedBalance() { + CAmount result = 0; + + for (auto& it : coinMeta) { + CSparkMintMeta mint = it.second; + if (mint.isUsed) + continue; + + // Continue if confirmed + if (mint.nHeight) + continue; + + result += mint.v; + } + + return result; +} + +spark::Address CSparkWallet::generateNextAddress() { + lastDiversifier++; + return spark::Address(viewKey, lastDiversifier); +} + +spark::SpendKey CSparkWallet::generateSpendKey() { + if (pwalletMain->IsLocked()) { + LogPrintf("Spark spend key generation FAILED, wallet is locked\n"); + return spark::SpendKey(); + } + + CKey secret; + uint32_t nCount; + { + LOCK(pwalletMain->cs_wallet); + nCount = GetArg("-sparkncount", DEFAULT_SPARK_NCOUNT); + pwalletMain->GetKeyFromKeypath(BIP44_SPARK_INDEX, nCount, secret); + } + + std::string nCountStr = std::to_string(nCount); + CHash256 hasher; + hasher.Write(secret.begin(), secret.size()); + hasher.Write(reinterpret_cast(nCountStr.c_str()), nCountStr.size()); + unsigned char hash[CSHA256::OUTPUT_SIZE]; + hasher.Finalize(hash); + + secp_primitives::Scalar r; + r.memberFromSeed(hash); + const spark::Params* params = spark::Params::get_default(); + spark::SpendKey key(params, r); + return key; +} + +spark::FullViewKey CSparkWallet::generateFullViewKey(const spark::SpendKey& spend_key) { + return spark::FullViewKey(spend_key); +} + +spark::IncomingViewKey CSparkWallet::generateIncomingViewKey(const spark::FullViewKey& full_view_key) { + viewKey = spark::IncomingViewKey(full_view_key); + return viewKey; +} + +std::unordered_map CSparkWallet::getAllAddresses() { + return addresses; +} + +spark::Address CSparkWallet::getAddress(const int32_t& i) { + if (lastDiversifier < i || addresses.count(i) == 0) + return spark::Address(viewKey, lastDiversifier); + + return addresses[i]; +} + +std::vector CSparkWallet::ListSparkMints(bool fUnusedOnly, bool fMatureOnly) { + std::vector setMints; + + for (auto& it : coinMeta) { + CSparkMintMeta mint = it.second; + if (fUnusedOnly && mint.isUsed) + continue; + + // Not confirmed + if (fMatureOnly && !mint.nHeight) + continue; + + setMints.push_back(mint); + } + + return setMints; +} + +spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta) { + const spark::Params* params = spark::Params::get_default(); + spark::Address address = getAddress(meta.i); + // type we are passing 0; as we don't care about type now + char type = 0; + return spark::Coin(params, type, meta.k, address, meta.v, meta.memo); +} + +void CSparkWallet::clearAllMints(CWalletDB& walletdb) { + + for (auto& itr : coinMeta) { + walletdb.EraseSparkMint(itr.first); + } + + coinMeta.clear(); + lastDiversifier = 0; + walletdb.writeDiversifier(lastDiversifier); +} + +void CSparkWallet::eraseMint(const uint256& hash, CWalletDB& walletdb) { + walletdb.EraseSparkMint(hash); + coinMeta.erase(hash); +} +void CSparkWallet::addOrUpdate(const CSparkMintMeta& mint, CWalletDB& walletdb) { + if (mint.i > lastDiversifier) { + lastDiversifier = mint.i; + walletdb.writeDiversifier(lastDiversifier); + } + coinMeta[mint.GetNonceHash()] = mint; + walletdb.WriteSparkMint(mint.GetNonceHash(), mint); +} + +CSparkMintMeta CSparkWallet::getMintMeta(const uint256& hash) { + if (coinMeta.count(hash)) + return coinMeta[hash]; + return CSparkMintMeta(); +} + +std::vector CSparkWallet::listAddressCoins(const int32_t& i, bool fUnusedOnly) { + std::vector listMints; + + for (auto& itr : coinMeta) { + if (itr.second.i == i) { + if (fUnusedOnly && itr.second.isUsed) + continue; + listMints.push_back(itr.second); + } + } + return listMints; +} \ No newline at end of file diff --git a/src/wallet/spark_wallet.h b/src/wallet/spark_wallet.h new file mode 100644 index 0000000000..e00ccf81e7 --- /dev/null +++ b/src/wallet/spark_wallet.h @@ -0,0 +1,69 @@ +// Copyright (c) 2022 The Firo Core Developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef FIRO_SPARK_WALLET_H +#define FIRO_SPARK_WALLET_H + +#include "../libspark/keys.h" +#include "../coin_containers.h" +#include "../primitives/mint_spend.h" +#include "walletdb.h" + +class CSparkWallet { +public: + CSparkWallet(const std::string& strWalletFile); + + // increment diversifier and generate address for that + spark::Address generateNextAddress(); + // assign difersifier to the value from db + void resetDiversifierFromDB(CWalletDB& walletdb); + // assign diversifier in to to current value + void updatetDiversifierInDB(CWalletDB& walletdb); + + // functions for key set generation + spark::SpendKey generateSpendKey(); + spark::FullViewKey generateFullViewKey(const spark::SpendKey& spend_key); + spark::IncomingViewKey generateIncomingViewKey(const spark::FullViewKey& full_view_key); + + // get map diversifier to Address + std::unordered_map getAllAddresses(); + // get address for a diversifier + spark::Address getAddress(const int32_t& i); + // list spark mint, mint metadata in memory and in db should be the same at this moment, so get from memory + std::vector ListSparkMints(bool fUnusedOnly = false, bool fMatureOnly = false); + // generate spark Coin from meta data + spark::Coin getCoinFromMeta(const CSparkMintMeta& meta); + + // functions to get spark balance + CAmount getFullBalance(); + CAmount getAvailableBalance(); + CAmount getUnconfirmedBalance(); + + // function to be used for zap wallet + void clearAllMints(CWalletDB& walletdb); + // erase mint meta data from memory and from db + void eraseMint(const uint256& hash, CWalletDB& walletdb); + // add mint meta data to memory and to db + void addOrUpdate(const CSparkMintMeta& mint, CWalletDB& walletdb); + CSparkMintMeta getMintMeta(const uint256& hash); + + // get the vector of mint metadata for a single address + std::vector listAddressCoins(const int32_t& i, bool fUnusedOnly = false); + +private: + // this is latest used diversifier + int32_t lastDiversifier; + + // this is incoming view key, which is saved into db and is used to identify our coins + spark::IncomingViewKey viewKey; + + // map diversifier to address. + std::unordered_map addresses; + + // map nonceHash to coin meta + std::unordered_map coinMeta; +}; + + +#endif //FIRO_SPARK_WALLET_H diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index f3508d2ee0..1f4d136762 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -13,6 +13,7 @@ #include "util.h" #include "utiltime.h" #include "wallet/wallet.h" + #include "bip47/account.h" #include @@ -1438,6 +1439,27 @@ bool CWalletDB::WriteMintSeedCount(const int32_t& nCount) return Write(std::string("dzsc"), nCount); } +bool CWalletDB::readDiversifier(int32_t& diversifier) +{ + return Read(std::string("div"), diversifier); + +} + +bool CWalletDB::writeDiversifier(const int32_t& diversifier) +{ + return Write(std::string("div"), diversifier); +} + +bool CWalletDB::readIncomingViewKey(spark::IncomingViewKey& viewKey) +{ + return Read(std::string("viewkey"), viewKey); +} + +bool CWalletDB::writeIncomingViewKey(const spark::IncomingViewKey& viewKey) +{ + return Write(std::string("viewkey"), viewKey); +} + bool CWalletDB::WritePubcoin(const uint256& hashSerial, const GroupElement& pubcoin) { return Write(std::make_pair(std::string("pubcoin"), hashSerial), pubcoin); @@ -1681,6 +1703,65 @@ unsigned int CWalletDB::GetUpdateCounter() return nWalletDBUpdateCounter; } +std::list CWalletDB::ListSparkMints() +{ + std::list listMints; + Dbc* pcursor = GetCursor(); + if (!pcursor) + throw std::runtime_error(std::string(__func__)+" : cannot create DB cursor"); + std::string mintName = "sparkMint"; + bool setRange = true; + for (;;) + { + // Read next record + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + if (setRange) + ssKey << std::make_pair(mintName, ArithToUint256(arith_uint256(0))); + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + int ret = ReadAtCursor(pcursor, ssKey, ssValue, setRange); + setRange = false; + if (ret == DB_NOTFOUND) + break; + else if (ret != 0) + { + pcursor->close(); + throw std::runtime_error(std::string(__func__)+" : error scanning DB"); + } + + // Unserialize + std::string strType; + ssKey >> strType; + if (strType != mintName) + break; + + uint256 nonceHash; + ssKey >> nonceHash; + + CSparkMintMeta mint; + ssValue >> mint; + + listMints.emplace_back(mint); + } + + pcursor->close(); + return listMints; +} + +bool CWalletDB::WriteSparkMint(const uint256& nonceHash, const CSparkMintMeta& mint) +{ + return Write(std::make_pair(std::string("sparkMint"), nonceHash), mint); +} + +bool CWalletDB::ReadSparkMint(const uint256& nonceHash, CSparkMintMeta& mint) +{ + return Read(std::make_pair(std::string("sparkMint"), nonceHash), mint); +} + +bool CWalletDB::EraseSparkMint(const uint256& nonceHash) +{ + return Erase(std::make_pair(std::string("sparkMint"), nonceHash)); +} + /******************************************************************************/ // BIP47 diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 585be7e41b..3ebf6addc8 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -18,6 +18,7 @@ #include "hdmint/mintpool.h" #include "../secp256k1/include/GroupElement.h" #include "../secp256k1/include/Scalar.h" +#include "../libspark/keys.h" #include #include @@ -272,6 +273,12 @@ class CWalletDB : public CDB bool ReadMintSeedCount(int32_t& nCount); bool WriteMintSeedCount(const int32_t& nCount); + bool readDiversifier(int32_t& nCount); + bool writeDiversifier(const int32_t& nCount); + + bool readIncomingViewKey(spark::IncomingViewKey& viewKey); + bool writeIncomingViewKey(const spark::IncomingViewKey& viewKey); + bool ArchiveDeterministicOrphan(const CHDMint& dMint); bool UnarchiveSigmaMint(const uint256& hashPubcoin, CSigmaEntry& sigma); bool UnarchiveHDMint(const uint256& hashPubcoin, bool isLelantus, CHDMint& dMint); @@ -295,6 +302,11 @@ class CWalletDB : public CDB bool ReadMintPoolPair(const uint256& hashPubcoin, uint160& hashSeedMaster, CKeyID& seedId, int32_t& nCount); std::vector> ListMintPool(); + std::list ListSparkMints(); + bool WriteSparkMint(const uint256& nonceHash, const CSparkMintMeta& mint); + bool ReadSparkMint(const uint256& nonceHash, CSparkMintMeta& mint); + bool EraseSparkMint(const uint256& nonceHash); + //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain); bool WriteMnemonic(const MnemonicContainer& mnContainer); From d1799e5ddda6437aff9692be2cbf747f24e790e2 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Wed, 1 Jun 2022 01:59:47 +0400 Subject: [PATCH 024/197] Spark mint tx creation, GetAvailableSparkCoins() implemented --- src/libspark/mint_transaction.cpp | 65 ++-- src/libspark/mint_transaction.h | 8 +- src/script/script.cpp | 5 + src/script/script.h | 8 +- src/wallet/wallet.cpp | 570 ++++++++++++++++++++++++++++++ src/wallet/wallet.h | 26 ++ 6 files changed, 660 insertions(+), 22 deletions(-) diff --git a/src/libspark/mint_transaction.cpp b/src/libspark/mint_transaction.cpp index 53a7a29401..a94af2dbbb 100644 --- a/src/libspark/mint_transaction.cpp +++ b/src/libspark/mint_transaction.cpp @@ -4,10 +4,10 @@ namespace spark { MintTransaction::MintTransaction( const Params* params, - const std::vector& outputs + const std::vector& outputs, + bool generate ) { // Important note: This construction assumes that the public coin values are correct according to higher-level consensus rules! - this->params = params; Schnorr schnorr(this->params->get_H()); @@ -15,27 +15,32 @@ MintTransaction::MintTransaction( std::vector value_witness; for (std::size_t j = 0; j < outputs.size(); j++) { - MintedCoinData output = outputs[j]; - - // Generate the coin - Scalar k; - k.randomize(); - this->coins.emplace_back(Coin( - this->params, - COIN_TYPE_MINT, - k, - output.address, - output.v, - output.memo - )); - - // Prepare the value proof - value_statement.emplace_back(this->coins[j].C + this->params->get_G().inverse()*Scalar(this->coins[j].v)); - value_witness.emplace_back(SparkUtils::hash_val(k)); + if (generate) { + MintedCoinData output = outputs[j]; + + // Generate the coin + Scalar k; + k.randomize(); + this->coins.emplace_back(Coin( + this->params, + COIN_TYPE_MINT, + k, + output.address, + output.v, + output.memo + )); + + // Prepare the value proof + value_statement.emplace_back(this->coins[j].C + this->params->get_G().inverse()*Scalar(this->coins[j].v)); + value_witness.emplace_back(SparkUtils::hash_val(k)); + } else { + this->coins.emplace_back(Coin()); + } } // Complete the value proof - schnorr.prove(value_witness, value_statement, this->value_proof); + if (generate) + schnorr.prove(value_witness, value_statement, this->value_proof); } bool MintTransaction::verify() { @@ -50,4 +55,24 @@ bool MintTransaction::verify() { return schnorr.verify(value_statement, this->value_proof); } +std::vector MintTransaction::getMintedCoinsSerialized() { + std::vector serializedCoins; + bool first = true; + for (const auto& coin : coins) { + CDataStream serializedCoin(SER_NETWORK, 0); + serializedCoin << coin; + if (first) { + serializedCoin << value_proof; + first = false; + } + serializedCoins.push_back(serializedCoin); + } + return serializedCoins; } + +void MintTransaction::getCoins(std::vector& coins_) { + coins_.insert(coins_.end(), coins.begin(), coins.end()); +} + + +} // namespace spark diff --git a/src/libspark/mint_transaction.h b/src/libspark/mint_transaction.h index e1bc8f1e17..a0c7bc98b9 100644 --- a/src/libspark/mint_transaction.h +++ b/src/libspark/mint_transaction.h @@ -19,10 +19,16 @@ class MintTransaction { public: MintTransaction( const Params* params, - const std::vector& outputs + const std::vector& outputs, + bool generate = true ); bool verify(); + // returns the vector of serialized coins, with first one it puts also the chnorr proof; + std::vector getMintedCoinsSerialized(); + + void getCoins(std::vector& coins_); + private: const Params* params; std::vector coins; diff --git a/src/script/script.cpp b/src/script/script.cpp index d56ad30c53..c463d93595 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -326,6 +326,11 @@ bool CScript::IsLelantusJoinSplit() const { ((*this)[0] == OP_LELANTUSJOINSPLIT || (*this)[0] == OP_LELANTUSJOINSPLITPAYLOAD)); } +bool CScript::IsSparkMint() const { + return (this->size() > 0 && + (*this)[0] == OP_SPARKMINT); +} + bool CScript::IsMint() const { return IsZerocoinMint() || IsSigmaMint() || IsZerocoinRemint() || IsLelantusMint() || IsLelantusJMint(); } diff --git a/src/script/script.h b/src/script/script.h index a6c6d70fdf..3c5170a9b3 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -201,7 +201,10 @@ enum opcodetype OP_LELANTUSJOINSPLITPAYLOAD = 0xc9, // input for reminting zerocoin to sigma (v3) - OP_ZEROCOINTOSIGMAREMINT = 0xc8 + OP_ZEROCOINTOSIGMAREMINT = 0xc8, + + // spark params + OP_SPARKMINT = 0xd1, }; const char* GetOpName(opcodetype opcode); @@ -672,6 +675,9 @@ class CScript : public CScriptBase bool IsLelantusJMint() const; bool IsLelantusJoinSplit() const; + // Spark + bool IsSparkMint() const; + bool IsZerocoinRemint() const; bool IsMint() const; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ad848688e0..fc4a0e0220 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -22,6 +22,7 @@ #include "../sigma/spend_metadata.h" #include "../sigma/coin.h" #include "lelantus.h" +#include "spark.h" #include "llmq/quorums_instantsend.h" #include "llmq/quorums_chainlocks.h" #include "net.h" @@ -114,6 +115,13 @@ static void EnsureMintWalletAvailable() } } +static void EnsureSparkWalletAvailable() +{ + if (!pwalletMain || !pwalletMain->sparkWallet) { + throw std::logic_error("Spark feature requires HD wallet"); + } +} + std::string COutput::ToString() const { return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); } @@ -2804,6 +2812,90 @@ CRecipient CWallet::CreateLelantusMintRecipient( } } +std::list> CWallet::GetAvailableSparkCoins(const CCoinControl *coinControl) const { + EnsureSparkWalletAvailable(); + + LOCK2(cs_main, cs_wallet); + CWalletDB walletdb(strWalletFile); + std::list> coins; + // get all unsued coins from spark wallet + std::vector vecMints = sparkWallet->ListSparkMints(true, true); + for (const auto& mint : vecMints) { + if(mint.v == 0) // ignore 0 mints which where created to increase privacy + continue; + // get actual spark coin + spark::Coin coin = sparkWallet->getCoinFromMeta(mint); + coins.push_back(std::make_pair(coin, mint)); + } + + std::set lockedCoins = setLockedCoins; + + // Filter out coins that have not been selected from CoinControl should that be used + coins.remove_if([lockedCoins, coinControl](const std::pair& coin) { + COutPoint outPoint; + + // ignore if the coin is not actually on chain + if (!spark::GetOutPoint(outPoint, coin.first)) { + return true; + } + + // ignore if coin is locked + if(lockedCoins.count(outPoint) > 0){ + return true; + } + + // if we are using coincontrol, filter out unselected coins + if(coinControl != NULL){ + if(coinControl->HasSelected()){ + if(!coinControl->IsSelected(outPoint)){ + return true; + } + } + } + + return false; + }); + + return coins; +} + +std::vector CWallet::CreateSparkMintRecipients( + const std::vector& outputs, + bool generate) +{ + const spark::Params* params = spark::Params::get_default(); + + // create spark mints, if generate is false, skip actual math operations + spark::MintTransaction sparkMint(params, outputs, generate); + + // verify if the mint is valid + if (generate && !sparkMint.verify()) { + throw std::runtime_error("Unable to validate spark mint."); + } + + // get serialized coins, also a schnorr proof with first coin, + std::vector serializedCoins = sparkMint.getMintedCoinsSerialized(); + + if (outputs.size() != serializedCoins.size()) + throw std::runtime_error("Spark mit output number should be equal to required number."); + + std::vector results; + results.reserve(outputs.size()); + + // serialize coins and put into scripts + for (size_t i = 0; i < outputs.size(); i++) { + // Create script for a coin + CScript script; + // opcode is inserted as 1 byte according to file script/script.h + script << OP_SPARKMINT; + script.insert(script.end(), serializedCoins[i].begin(), serializedCoins[i].end()); + CRecipient recipient = {script, CAmount(outputs[i].v), false}; + results.emplace_back(recipient); + } + + return results; +} + // coinsIn has to be sorted in descending order. int CWallet::GetRequiredCoinCountForAmount( const CAmount& required, @@ -5127,6 +5219,429 @@ bool CWallet::CreateLelantusMintTransactions( return true; } +bool CWallet::CreateSparkMintTransactions( + const std::vector& outputs, + std::vector>& wtxAndFee, + CAmount& nAllFeeRet, + std::list& reservekeys, + int& nChangePosInOut, + std::string& strFailReason, + const CCoinControl *coinControl, + bool autoMintAll) +{ + + int nChangePosRequest = nChangePosInOut; + + // Create transaction template + CWalletTx wtxNew; + wtxNew.fTimeReceivedIsTxTime = true; + wtxNew.BindWallet(this); + + CMutableTransaction txNew; + txNew.nLockTime = chainActive.Height(); + + assert(txNew.nLockTime <= (unsigned int) chainActive.Height()); + assert(txNew.nLockTime < LOCKTIME_THRESHOLD); + std::vector outputs_ = outputs; + CAmount valueToMint = 0; + + for (auto& output : outputs_) + valueToMint += output.v; + + { + LOCK2(cs_main, cs_wallet); + { + std::list cacheWtxs; + // vector pairs for each transparent address + std::vector>> valueAndUTXO; + AvailableCoinsForLMint(valueAndUTXO, coinControl); + + Shuffle(valueAndUTXO.begin(), valueAndUTXO.end(), FastRandomContext()); + + while (!valueAndUTXO.empty()) { + + // initialize + CWalletTx wtx = wtxNew; + CMutableTransaction tx = txNew; + + reservekeys.emplace_back(this); + auto &reservekey = reservekeys.back(); + + if (GetRandInt(10) == 0) + tx.nLockTime = std::max(0, (int) tx.nLockTime - GetRandInt(100)); + + auto nFeeRet = 0; + LogPrintf("nFeeRet=%s\n", nFeeRet); + + auto itr = valueAndUTXO.begin(); + + // TODO(levon) do we need mint limit? if yes, define new MaxValue Mint for spark + CAmount valueToMintInTx = std::min( + ::Params().GetConsensus().nMaxValueLelantusMint, + itr->first); + + if (!autoMintAll) { + valueToMintInTx = std::min(valueToMintInTx, valueToMint); + } + + CAmount nValueToSelect, mintedValue; + + std::set> setCoins; + bool skipCoin = false; + // Start with no fee and loop until there is enough fee + while (true) { + mintedValue = valueToMintInTx; + nValueToSelect = mintedValue + nFeeRet; + + // if have no enough coins in this group then subtract fee from mint + if (nValueToSelect > itr->first) { + mintedValue -= nFeeRet; + nValueToSelect = mintedValue + nFeeRet; + } + + if (!MoneyRange(mintedValue) || mintedValue == 0) { + valueAndUTXO.erase(itr); + skipCoin = true; + break; + } + + nChangePosInOut = nChangePosRequest; + tx.vin.clear(); + tx.vout.clear(); + wtx.fFromMe = true; + wtx.changes.clear(); + setCoins.clear(); + std::vector remainingOutputs = outputs_; + std::vector singleTxOutputs; + if (autoMintAll) { + CWalletDB walletdb(strWalletFile); + sparkWallet->resetDiversifierFromDB(walletdb); + spark::MintedCoinData mintedCoinData; + mintedCoinData.v = mintedValue; + mintedCoinData.memo = ""; + mintedCoinData.address = sparkWallet->generateNextAddress(); + singleTxOutputs.push_back(mintedCoinData); + } else { + uint64_t remainingMintValue = mintedValue; + while (remainingMintValue > 0){ + // Create the mint data and push into vector + uint64_t singleMintValue = std::min(remainingMintValue, remainingOutputs.begin()->v); + spark::MintedCoinData mintedCoinData; + mintedCoinData.v = singleMintValue; + mintedCoinData.address = remainingOutputs.begin()->address; + mintedCoinData.memo = remainingOutputs.begin()->memo; + singleTxOutputs.push_back(mintedCoinData); + + // subtract minted amount from remaining value + remainingMintValue -= singleMintValue; + remainingOutputs.begin()->v -= singleMintValue; + + if (remainingOutputs.begin()->v == 0) + remainingOutputs.erase(remainingOutputs.begin()); + } + } + + // Generate dummy mint coins to save time + std::vector recipients = CWallet::CreateSparkMintRecipients(singleTxOutputs, false); + for (auto& recipient : recipients) { + // vout to create mint + CTxOut txout(recipient.nAmount, recipient.scriptPubKey); + + if (txout.IsDust(::minRelayTxFee)) { + strFailReason = _("Transaction amount too small"); + return false; + } + + tx.vout.push_back(txout); + } + // Choose coins to use + CAmount nValueIn = 0; + if (!SelectCoins(itr->second, nValueToSelect, setCoins, nValueIn, coinControl)) { + + if (nValueIn < nValueToSelect) { + strFailReason = _("Insufficient funds"); + } + return false; + } + + double dPriority = 0; + for (auto const &pcoin : setCoins) { + CAmount nCredit = pcoin.first->tx->vout[pcoin.second].nValue; + //The coin age after the next block (depth+1) is used instead of the current, + //reflecting an assumption the user would accept a bit more delay for + //a chance at a free transaction. + //But mempool inputs might still be in the mempool, so their age stays 0 + int age = pcoin.first->GetDepthInMainChain(); + assert(age >= 0); + if (age != 0) + age += 1; + dPriority += (double) nCredit * age; + } + + CAmount nChange = nValueIn - nValueToSelect; + + if (nChange > 0) { + // Fill a vout to ourself + // TODO: pass in scriptChange instead of reservekey so + // change transaction isn't always pay-to-bitcoin-address + CScript scriptChange; + + // coin control: send change to custom address + if (coinControl && !boost::get(&coinControl->destChange)) + scriptChange = GetScriptForDestination(coinControl->destChange); + + // send change to one of the specified change addresses + else if (IsArgSet("-change") && mapMultiArgs.at("-change").size() > 0) { + CBitcoinAddress address( + mapMultiArgs.at("change")[GetRandInt(mapMultiArgs.at("-change").size())]); + CKeyID keyID; + if (!address.GetKeyID(keyID)) { + strFailReason = _("Bad change address"); + return false; + } + scriptChange = GetScriptForDestination(keyID); + } + + // no coin control: send change to newly generated address + else { + // Note: We use a new key here to keep it from being obvious which side is the change. + // The drawback is that by not reusing a previous key, the change may be lost if a + // backup is restored, if the backup doesn't have the new private key for the change. + // If we reused the old key, it would be possible to add code to look for and + // rediscover unknown transactions that were written with keys of ours to recover + // post-backup change. + + // Reserve a new key pair from key pool + CPubKey vchPubKey; + bool ret; + ret = reservekey.GetReservedKey(vchPubKey); + if (!ret) { + strFailReason = _("Keypool ran out, please call keypoolrefill first"); + return false; + } + + scriptChange = GetScriptForDestination(vchPubKey.GetID()); + } + + CTxOut newTxOut(nChange, scriptChange); + + // Never create dust outputs; if we would, just + // add the dust to the fee. + if (newTxOut.IsDust(::minRelayTxFee)) { + nChangePosInOut = -1; + nFeeRet += nChange; + reservekey.ReturnKey(); + } else { + + if (nChangePosInOut == -1) { + + // Insert change txn at random position: + nChangePosInOut = GetRandInt(tx.vout.size() + 1); + } else if ((unsigned int) nChangePosInOut > tx.vout.size()) { + + strFailReason = _("Change index out of range"); + return false; + } + + std::vector::iterator position = tx.vout.begin() + nChangePosInOut; + tx.vout.insert(position, newTxOut); + wtx.changes.insert(static_cast(nChangePosInOut)); + } + } else { + reservekey.ReturnKey(); + } + + // Fill vin + // + // Note how the sequence number is set to max()-1 so that the + // nLockTime set above actually works. + for (const auto &coin : setCoins) { + tx.vin.push_back(CTxIn( + coin.first->GetHash(), + coin.second, + CScript(), + std::numeric_limits::max() - 1)); + } + + // Fill in dummy signatures for fee calculation. + if (!DummySignTx(tx, setCoins)) { + strFailReason = _("Signing transaction failed"); + return false; + } + + unsigned int nBytes = GetVirtualTransactionSize(tx); + + // Limit size + CTransaction txConst(tx); + if (GetTransactionWeight(txConst) >= MAX_STANDARD_TX_WEIGHT) { + strFailReason = _("Transaction too large"); + return false; + } + dPriority = txConst.ComputePriority(dPriority, nBytes); + + // Remove scriptSigs to eliminate the fee calculation dummy signatures + for (auto &vin : tx.vin) { + vin.scriptSig = CScript(); + vin.scriptWitness.SetNull(); + } + + // Can we complete this as a free transaction? + if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE) { + // Not enough fee: enough priority? + double dPriorityNeeded = mempool.estimateSmartPriority(nTxConfirmTarget); + // Require at least hard-coded AllowFree. + if (dPriority >= dPriorityNeeded && AllowFree(dPriority)) + break; + } + CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool); + + if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) { + nFeeNeeded = coinControl->nMinimumTotalFee; + } + + if (coinControl && coinControl->fOverrideFeeRate) + nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes); + + // If we made it here and we aren't even able to meet the relay fee on the next pass, give up + // because we must be at the maximum allowed fee. + if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) { + strFailReason = _("Transaction too large for fee policy"); + return false; + } + + if (nFeeRet >= nFeeNeeded) { + for (auto &usedCoin : setCoins) { + for (auto coin = itr->second.begin(); coin != itr->second.end(); coin++) { + if (usedCoin.first == coin->tx && usedCoin.second == coin->i) { + itr->first -= coin->tx->tx->vout[coin->i].nValue; + itr->second.erase(coin); + break; + } + } + } + + if (itr->second.empty()) { + valueAndUTXO.erase(itr); + } + + // Generate real mint coins + recipients = CWallet::CreateSparkMintRecipients(singleTxOutputs, true); + size_t i = 0; + for (auto& recipient : recipients) { + CTxOut txout(recipient.nAmount, recipient.scriptPubKey); + LogPrintf("txout: %s\n", txout.ToString()); + while (i < tx.vout.size() - 1) { + if (tx.vout[i].scriptPubKey.IsSparkMint()) { + tx.vout[i] = txout; + break; + } + ++i; + } + ++i; + } + + //remove output from outputs_ vector if it got all requested value + outputs_ = remainingOutputs; + + if (autoMintAll) { + CWalletDB walletdb(strWalletFile); + sparkWallet->updatetDiversifierInDB(walletdb); + } + + break; // Done, enough fee included. + } + + // Include more fee and try again. + nFeeRet = nFeeNeeded; + continue; + } + + if(skipCoin) + continue; + + if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { + // Lastly, ensure this tx will pass the mempool's chain limits + LockPoints lp; + CTxMemPoolEntry entry(MakeTransactionRef(tx), 0, 0, 0, 0, false, 0, lp); + CTxMemPool::setEntries setAncestors; + size_t nLimitAncestors = GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); + size_t nLimitAncestorSize = GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; + size_t nLimitDescendants = GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); + size_t nLimitDescendantSize = + GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; + std::string errString; + if (!mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, + nLimitDescendants, nLimitDescendantSize, errString)) { + strFailReason = _("Transaction has too long of a mempool chain"); + return false; + } + } + + // Sign + int nIn = 0; + CTransaction txNewConst(tx); + for (const auto &coin : setCoins) { + bool signSuccess = false; + const CScript &scriptPubKey = coin.first->tx->vout[coin.second].scriptPubKey; + SignatureData sigdata; + signSuccess = ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, + coin.first->tx->vout[coin.second].nValue, + SIGHASH_ALL), scriptPubKey, sigdata); + + if (!signSuccess) { + strFailReason = _("Signing transaction failed"); + return false; + } else { + UpdateTransaction(tx, nIn, sigdata); + } + nIn++; + } + + wtx.SetTx(MakeTransactionRef(std::move(tx))); + + wtxAndFee.push_back(std::make_pair(wtx, nFeeRet)); + + if (nChangePosInOut >= 0) { + // Cache wtx to somewhere because COutput use pointer of it. + cacheWtxs.push_back(wtx); + auto &wtx = cacheWtxs.back(); + + COutput out(&wtx, nChangePosInOut, wtx.GetDepthInMainChain(false), true, true); + auto val = wtx.tx->vout[nChangePosInOut].nValue; + + bool added = false; + for (auto &utxos : valueAndUTXO) { + auto const &o = utxos.second.front(); + if (o.tx->tx->vout[o.i].scriptPubKey == wtx.tx->vout[nChangePosInOut].scriptPubKey) { + utxos.first += val; + utxos.second.push_back(out); + + added = true; + } + } + + if (!added) { + valueAndUTXO.push_back({val, {out}}); + } + } + + nAllFeeRet += nFeeRet; + if(!autoMintAll) { + valueToMint -= mintedValue; + if (valueToMint == 0) + break; + } + } + } + } + + if (!autoMintAll && valueToMint > 0) { + return false; + } + + return true; +} + bool CWallet::CreateMintTransaction(CScript pubCoin, int64_t nValue, CWalletTx &wtxNew, CReserveKey &reservekey, int64_t &nFeeRet, std::string &strFailReason, @@ -5303,6 +5818,58 @@ std::string CWallet::MintAndStoreLelantus(const CAmount& value, return ""; } +std::string CWallet::MintAndStoreSpark( + const std::vector& outputs, + std::vector>& wtxAndFee, + bool autoMintAll, + bool fAskFee, + const CCoinControl *coinControl) { + std::string strError; + + EnsureSparkWalletAvailable(); + + if (IsLocked()) { + strError = _("Error: Wallet locked, unable to create transaction!"); + LogPrintf("MintLelantus() : %s", strError); + return strError; + } + + uint64_t value = 0; + for (auto& output : outputs) + value += output.v; + + if ((value + payTxFee.GetFeePerK()) > GetBalance()) + return _("Insufficient funds"); + + LogPrintf("payTxFee.GetFeePerK()=%s\n", payTxFee.GetFeePerK()); + int64_t nFeeRequired = 0; + + int nChangePosRet = -1; + + std::list reservekeys; + if (!CreateSparkMintTransactions(outputs, wtxAndFee, nFeeRequired, reservekeys, nChangePosRet, strError, coinControl, autoMintAll)) { + return strError; + } + + if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired)){ + LogPrintf("MintLelantus: returning aborted..\n"); + return "ABORTED"; + } + + CValidationState state; + auto reservekey = reservekeys.begin(); + for(size_t i = 0; i < wtxAndFee.size(); i++) { + if (!CommitTransaction(wtxAndFee[i].first, *reservekey++, g_connman.get(), state)) { + return _( + "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); + } else { + LogPrintf("CommitTransaction success!\n"); + } + } + + return ""; +} + std::vector CWallet::SpendSigma(const std::vector& recipients, CWalletTx& result) { CAmount fee; @@ -6689,6 +7256,9 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart); if (pwalletMain->IsHDSeedAvailable()) { walletInstance->zwallet = std::make_unique(pwalletMain->strWalletFile); + + // if it is first run, we need to generate the full key set for spark, if not we are loading spark wallet from db + walletInstance->sparkWallet = std::make_unique(pwalletMain->strWalletFile); } walletInstance->bip47wallet = std::make_shared(walletInstance->vchDefaultKey.GetHash()); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 8d1a6284ea..1b9e4f8d71 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -9,6 +9,7 @@ #include "amount.h" #include "../sigma/coin.h" #include "../liblelantus/coin.h" +#include "../libspark/mint_transaction.h" #include "streams.h" #include "tinyformat.h" #include "ui_interface.h" @@ -20,6 +21,7 @@ #include "wallet/walletdb.h" #include "wallet/rpcwallet.h" #include "wallet/mnemoniccontainer.h" +#include "wallet/spark_wallet.h" #include "../base58.h" #include "firo_params.h" #include "univalue.h" @@ -105,6 +107,8 @@ const uint32_t BIP44_ELYSIUM_MINT_INDEX_V1 = 0x4; #endif const uint32_t BIP44_MINT_VALUE_INDEX = 0x5; +const uint32_t BIP44_SPARK_INDEX = 0x6; + class CBlockIndex; class CCoinControl; class COutput; @@ -753,6 +757,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::unique_ptr zwallet; + std::unique_ptr sparkWallet; + CWallet() { SetNull(); @@ -931,6 +937,11 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CHDMint& vDMint, bool generate = true); + // generate recipient data for mint transaction, + static std::vector CreateSparkMintRecipients( + const std::vector& outputs, + bool generate); + static int GetRequiredCoinCountForAmount( const CAmount& required, const std::vector& denominations); @@ -951,6 +962,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::list GetAvailableLelantusCoins(const CCoinControl *coinControl = NULL, bool includeUnsafe = false, bool forEstimation = false) const; + // Returns the list of pairs of coins and meta data for that coin, + std::list> GetAvailableSparkCoins(const CCoinControl *coinControl = NULL) const; + std::vector EncryptMintAmount(uint64_t amount, const secp_primitives::GroupElement& pubcoin) const; bool DecryptMintAmount(const std::vector& encryptedValue, const secp_primitives::GroupElement& pubcoin, uint64_t& amount) const; @@ -1009,6 +1023,11 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::list& reservekeys, int& nChangePosInOut, std::string& strFailReason, const CCoinControl *coinControl, bool autoMintAll = false, bool sign = true); + bool CreateSparkMintTransactions(const std::vector& outputs, std::vector>& wtxAndFee, + CAmount& nAllFeeRet, + std::list& reservekeys, int& nChangePosInOut, + std::string& strFailReason, const CCoinControl *coinControl, bool autoMintAll = false); + CWalletTx CreateSigmaSpendTransaction( const std::vector& recipients, CAmount& fee, @@ -1048,6 +1067,13 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool fAskFee = false, const CCoinControl *coinControl = NULL); + std::string MintAndStoreSpark( + const std::vector& outputs, + std::vector>& wtxAndFee, + bool autoMintAll = false, + bool fAskFee = false, + const CCoinControl *coinControl = NULL); + std::vector SpendSigma(const std::vector& recipients, CWalletTx& result); std::vector SpendSigma(const std::vector& recipients, CWalletTx& result, CAmount& fee); From 9ba7eb903a34a41a1b2b025b76e75a0942084cbe Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Wed, 1 Jun 2022 19:40:48 -0500 Subject: [PATCH 025/197] Bind serial commitments to chain context --- src/libspark/coin.cpp | 10 ++++++---- src/libspark/coin.h | 4 +++- src/libspark/mint_transaction.cpp | 6 +++++- src/libspark/mint_transaction.h | 3 ++- src/libspark/spend_transaction.cpp | 8 +++++++- src/libspark/test/coin_test.cpp | 21 ++++++++++++++++---- src/libspark/test/mint_transaction_test.cpp | 14 ++++++++++++- src/libspark/test/spend_transaction_test.cpp | 21 +++++++++++++------- src/libspark/util.cpp | 3 ++- src/libspark/util.h | 2 +- 10 files changed, 70 insertions(+), 22 deletions(-) diff --git a/src/libspark/coin.cpp b/src/libspark/coin.cpp index 66644808ee..764e9a379c 100644 --- a/src/libspark/coin.cpp +++ b/src/libspark/coin.cpp @@ -12,9 +12,11 @@ Coin::Coin( const Scalar& k, const Address& address, const uint64_t v, - const std::string memo + const std::string memo, + const std::vector serial_context ) { this->params = params; + this->serial_context = serial_context; // Validate the type if (type != COIN_TYPE_MINT && type != COIN_TYPE_SPEND) { @@ -31,7 +33,7 @@ Coin::Coin( this->K = SparkUtils::hash_div(address.get_d())*SparkUtils::hash_k(k); // Construct the serial commitment - this->S = this->params->get_F()*SparkUtils::hash_ser(k) + address.get_Q2(); + this->S = this->params->get_F()*SparkUtils::hash_ser(k, serial_context) + address.get_Q2(); // Construct the value commitment this->C = this->params->get_G()*Scalar(v) + this->params->get_H()*SparkUtils::hash_val(k); @@ -90,7 +92,7 @@ bool Coin::validate( // Check serial commitment data.i = incoming_view_key.get_diversifier(data.d); - if (this->params->get_F()*(SparkUtils::hash_ser(data.k) + SparkUtils::hash_Q2(incoming_view_key.get_s1(), data.i)) + incoming_view_key.get_P2() != this->S) { + if (this->params->get_F()*(SparkUtils::hash_ser(data.k, this->serial_context) + SparkUtils::hash_Q2(incoming_view_key.get_s1(), data.i)) + incoming_view_key.get_P2() != this->S) { return false; } @@ -100,7 +102,7 @@ bool Coin::validate( // Recover a coin RecoveredCoinData Coin::recover(const FullViewKey& full_view_key, const IdentifiedCoinData& data) { RecoveredCoinData recovered_data; - recovered_data.s = SparkUtils::hash_ser(data.k) + SparkUtils::hash_Q2(full_view_key.get_s1(), data.i) + full_view_key.get_s2(); + recovered_data.s = SparkUtils::hash_ser(data.k, this->serial_context) + SparkUtils::hash_Q2(full_view_key.get_s1(), data.i) + full_view_key.get_s2(); recovered_data.T = (this->params->get_U() + full_view_key.get_D().inverse())*recovered_data.s.inverse(); return recovered_data; diff --git a/src/libspark/coin.h b/src/libspark/coin.h index 1d935b14a0..800159c40a 100644 --- a/src/libspark/coin.h +++ b/src/libspark/coin.h @@ -71,7 +71,8 @@ class Coin { const Scalar& k, const Address& address, const uint64_t v, - const std::string memo + const std::string memo, + const std::vector serial_context ); // Given an incoming view key, extract the coin's nonce, diversifier, value, and memo @@ -89,6 +90,7 @@ class Coin { GroupElement S, K, C; // serial commitment, recovery key, value commitment AEADEncryptedData r_; // encrypted recipient data uint64_t v; // value + std::vector serial_context; // context to which the serial commitment should be bound (not serialized, but inferred) // Serialization depends on the coin type ADD_SERIALIZE_METHODS; diff --git a/src/libspark/mint_transaction.cpp b/src/libspark/mint_transaction.cpp index a94af2dbbb..ee492aa75b 100644 --- a/src/libspark/mint_transaction.cpp +++ b/src/libspark/mint_transaction.cpp @@ -5,9 +5,12 @@ namespace spark { MintTransaction::MintTransaction( const Params* params, const std::vector& outputs, + const std::vector& serial_context, bool generate ) { // Important note: This construction assumes that the public coin values are correct according to higher-level consensus rules! + // Important note: For pool transition transactions, the serial context should contain unique references to all base-layer spent assets, in order to ensure the resulting serial commitment is bound to this transaction + this->params = params; Schnorr schnorr(this->params->get_H()); @@ -27,7 +30,8 @@ MintTransaction::MintTransaction( k, output.address, output.v, - output.memo + output.memo, + serial_context )); // Prepare the value proof diff --git a/src/libspark/mint_transaction.h b/src/libspark/mint_transaction.h index a0c7bc98b9..6f3af9cd20 100644 --- a/src/libspark/mint_transaction.h +++ b/src/libspark/mint_transaction.h @@ -20,7 +20,8 @@ class MintTransaction { MintTransaction( const Params* params, const std::vector& outputs, - bool generate = true + const std::vector& serial_context, + bool generate = true ); bool verify(); diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index 0da600c2ce..6d8fde1066 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -100,6 +100,11 @@ SpendTransaction::SpendTransaction( std::vector range_v; std::vector range_r; std::vector range_C; + + // Serial context for all outputs is the set of linking tags for this transaction, which must always be in a fixed order + CDataStream serial_context(SER_NETWORK, PROTOCOL_VERSION); + serial_context << this->T; + for (std::size_t j = 0; j < t; j++) { // Nonce k.emplace_back(); @@ -113,7 +118,8 @@ SpendTransaction::SpendTransaction( k.back(), outputs[j].address, outputs[j].v, - outputs[j].memo + outputs[j].memo, + std::vector(serial_context.begin(), serial_context.end()) ); // Range data diff --git a/src/libspark/test/coin_test.cpp b/src/libspark/test/coin_test.cpp index 567069769b..c162ecdc9c 100644 --- a/src/libspark/test/coin_test.cpp +++ b/src/libspark/test/coin_test.cpp @@ -7,6 +7,17 @@ namespace spark { using namespace secp_primitives; +// Generate a random char vector from a random scalar +static std::vector random_char_vector() { + Scalar temp; + temp.randomize(); + std::vector result; + result.resize(SCALAR_ENCODING); + temp.serialize(result.data()); + + return result; +} + BOOST_FIXTURE_TEST_SUITE(spark_coin_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(mint_identify_recover) @@ -36,7 +47,8 @@ BOOST_AUTO_TEST_CASE(mint_identify_recover) k, address, v, - memo + memo, + random_char_vector() ); // Identify coin @@ -50,7 +62,7 @@ BOOST_AUTO_TEST_CASE(mint_identify_recover) // Recover coin RecoveredCoinData r_data = coin.recover(full_view_key, i_data); BOOST_CHECK_EQUAL( - params->get_F()*(SparkUtils::hash_ser(k) + SparkUtils::hash_Q2(incoming_view_key.get_s1(), i) + full_view_key.get_s2()) + full_view_key.get_D(), + params->get_F()*(SparkUtils::hash_ser(k, coin.serial_context) + SparkUtils::hash_Q2(incoming_view_key.get_s1(), i) + full_view_key.get_s2()) + full_view_key.get_D(), params->get_F()*r_data.s + full_view_key.get_D() ); BOOST_CHECK_EQUAL(r_data.T*r_data.s + full_view_key.get_D(), params->get_U()); @@ -83,7 +95,8 @@ BOOST_AUTO_TEST_CASE(spend_identify_recover) k, address, v, - memo + memo, + random_char_vector() ); // Identify coin @@ -97,7 +110,7 @@ BOOST_AUTO_TEST_CASE(spend_identify_recover) // Recover coin RecoveredCoinData r_data = coin.recover(full_view_key, i_data); BOOST_CHECK_EQUAL( - params->get_F()*(SparkUtils::hash_ser(k) + SparkUtils::hash_Q2(incoming_view_key.get_s1(), i) + full_view_key.get_s2()) + full_view_key.get_D(), + params->get_F()*(SparkUtils::hash_ser(k, coin.serial_context) + SparkUtils::hash_Q2(incoming_view_key.get_s1(), i) + full_view_key.get_s2()) + full_view_key.get_D(), params->get_F()*r_data.s + full_view_key.get_D() ); BOOST_CHECK_EQUAL(r_data.T*r_data.s + full_view_key.get_D(), params->get_U()); diff --git a/src/libspark/test/mint_transaction_test.cpp b/src/libspark/test/mint_transaction_test.cpp index 8119e27baa..cbf562415f 100644 --- a/src/libspark/test/mint_transaction_test.cpp +++ b/src/libspark/test/mint_transaction_test.cpp @@ -7,6 +7,17 @@ namespace spark { using namespace secp_primitives; +// Generate a random char vector from a random scalar +static std::vector random_char_vector() { + Scalar temp; + temp.randomize(); + std::vector result; + result.resize(SCALAR_ENCODING); + temp.serialize(result.data()); + + return result; +} + BOOST_FIXTURE_TEST_SUITE(spark_mint_transaction_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(generate_verify) @@ -36,7 +47,8 @@ BOOST_AUTO_TEST_CASE(generate_verify) // Generate mint transaction MintTransaction mint( params, - outputs + outputs, + random_char_vector() ); // Verify diff --git a/src/libspark/test/spend_transaction_test.cpp b/src/libspark/test/spend_transaction_test.cpp index b911155b6f..319c83791c 100644 --- a/src/libspark/test/spend_transaction_test.cpp +++ b/src/libspark/test/spend_transaction_test.cpp @@ -7,6 +7,17 @@ namespace spark { using namespace secp_primitives; +// Generate a random char vector from a random scalar +static std::vector random_char_vector() { + Scalar temp; + temp.randomize(); + std::vector result; + result.resize(SCALAR_ENCODING); + temp.serialize(result.data()); + + return result; +} + BOOST_FIXTURE_TEST_SUITE(spark_spend_transaction_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(generate_verify) @@ -41,7 +52,8 @@ BOOST_AUTO_TEST_CASE(generate_verify) k, address, v, - memo + memo, + random_char_vector() )); } @@ -57,12 +69,7 @@ BOOST_AUTO_TEST_CASE(generate_verify) IdentifiedCoinData identified_coin_data = in_coins[spend_indices[u]].identify(incoming_view_key); RecoveredCoinData recovered_coin_data = in_coins[spend_indices[u]].recover(full_view_key, identified_coin_data); - Scalar temp; - temp.randomize(); - std::vector root; - root.resize(SCALAR_ENCODING); - temp.serialize(root.data()); - roots.emplace_back(root); + roots.emplace_back(random_char_vector()); spend_coin_data.emplace_back(); spend_coin_data.back().index = spend_indices[u]; diff --git a/src/libspark/util.cpp b/src/libspark/util.cpp index 1f58f97e69..d6c35b61de 100644 --- a/src/libspark/util.cpp +++ b/src/libspark/util.cpp @@ -184,11 +184,12 @@ Scalar SparkUtils::hash_k(const Scalar& k) { } // Hash-to-scalar function H_ser -Scalar SparkUtils::hash_ser(const Scalar& k) { +Scalar SparkUtils::hash_ser(const Scalar& k, const std::vector& serial_context) { Hash hash(LABEL_HASH_SER); CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << k; + stream << serial_context; hash.include(stream); return hash.finalize_scalar(); diff --git a/src/libspark/util.h b/src/libspark/util.h index 6fea39d555..ddcd0785a4 100644 --- a/src/libspark/util.h +++ b/src/libspark/util.h @@ -69,7 +69,7 @@ class SparkUtils { static GroupElement hash_div(const std::vector& d); static Scalar hash_Q2(const Scalar& s1, const Scalar& i); static Scalar hash_k(const Scalar& k); - static Scalar hash_ser(const Scalar& k); + static Scalar hash_ser(const Scalar& k, const std::vector& serial_context); static Scalar hash_val(const Scalar& k); static Scalar hash_ser1(const Scalar& s, const GroupElement& D); static Scalar hash_val1(const Scalar& s, const GroupElement& D); From 54e704ec5633d2b3019d7a695586e9dda7e39e71 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Thu, 2 Jun 2022 14:00:05 -0500 Subject: [PATCH 026/197] Use default curve generator for spend component --- src/libspark/params.cpp | 2 +- src/libspark/util.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libspark/params.cpp b/src/libspark/params.cpp index a3ddb8392b..3f7fb10c1e 100644 --- a/src/libspark/params.cpp +++ b/src/libspark/params.cpp @@ -56,7 +56,7 @@ Params::Params( { // Global generators this->F = SparkUtils::hash_generator(LABEL_GENERATOR_F); - this->G = SparkUtils::hash_generator(LABEL_GENERATOR_G); + this->G.set_base_g(); this->H = SparkUtils::hash_generator(LABEL_GENERATOR_H); this->U = SparkUtils::hash_generator(LABEL_GENERATOR_U); diff --git a/src/libspark/util.h b/src/libspark/util.h index ddcd0785a4..9d3d790f91 100644 --- a/src/libspark/util.h +++ b/src/libspark/util.h @@ -33,7 +33,6 @@ const std::string LABEL_TRANSCRIPT_SCHNORR = "SCHNORR_V1"; // Generator labels const std::string LABEL_GENERATOR_F = "F"; -const std::string LABEL_GENERATOR_G = "G"; const std::string LABEL_GENERATOR_H = "H"; const std::string LABEL_GENERATOR_U = "U"; const std::string LABEL_GENERATOR_G_RANGE = "G_RANGE"; From 508ab8662e7eb49018b9474a4c803d746c26f8a5 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 5 Jun 2022 03:41:55 +0400 Subject: [PATCH 027/197] Completed merge --- src/wallet/spark_wallet.cpp | 4 ++-- src/wallet/spark_wallet.h | 2 +- src/wallet/wallet.cpp | 18 ++++++++++++++---- src/wallet/wallet.h | 1 + 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/wallet/spark_wallet.cpp b/src/wallet/spark_wallet.cpp index dc53908f0c..bf5c4402f4 100644 --- a/src/wallet/spark_wallet.cpp +++ b/src/wallet/spark_wallet.cpp @@ -167,12 +167,12 @@ std::vector CSparkWallet::ListSparkMints(bool fUnusedOnly, bool return setMints; } -spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta) { +spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta, const std::vector& serial_context) { const spark::Params* params = spark::Params::get_default(); spark::Address address = getAddress(meta.i); // type we are passing 0; as we don't care about type now char type = 0; - return spark::Coin(params, type, meta.k, address, meta.v, meta.memo); + return spark::Coin(params, type, meta.k, address, meta.v, meta.memo, serial_context); } void CSparkWallet::clearAllMints(CWalletDB& walletdb) { diff --git a/src/wallet/spark_wallet.h b/src/wallet/spark_wallet.h index e00ccf81e7..ca83a8dc90 100644 --- a/src/wallet/spark_wallet.h +++ b/src/wallet/spark_wallet.h @@ -33,7 +33,7 @@ class CSparkWallet { // list spark mint, mint metadata in memory and in db should be the same at this moment, so get from memory std::vector ListSparkMints(bool fUnusedOnly = false, bool fMatureOnly = false); // generate spark Coin from meta data - spark::Coin getCoinFromMeta(const CSparkMintMeta& meta); + spark::Coin getCoinFromMeta(const CSparkMintMeta& meta, const std::vector& serial_context); // functions to get spark balance CAmount getFullBalance(); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index fc4a0e0220..fb6f7bc388 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2824,7 +2824,9 @@ std::list> CWallet::GetAvailableSparkCoin if(mint.v == 0) // ignore 0 mints which where created to increase privacy continue; // get actual spark coin - spark::Coin coin = sparkWallet->getCoinFromMeta(mint); + //TODO levon get transaction to generate serial_context vector for coins + std::vector serial_context; + spark::Coin coin = sparkWallet->getCoinFromMeta(mint, serial_context); coins.push_back(std::make_pair(coin, mint)); } @@ -2861,12 +2863,13 @@ std::list> CWallet::GetAvailableSparkCoin std::vector CWallet::CreateSparkMintRecipients( const std::vector& outputs, + const std::vector& serial_context, bool generate) { const spark::Params* params = spark::Params::get_default(); // create spark mints, if generate is false, skip actual math operations - spark::MintTransaction sparkMint(params, outputs, generate); + spark::MintTransaction sparkMint(params, outputs, serial_context, generate); // verify if the mint is valid if (generate && !sparkMint.verify()) { @@ -5342,7 +5345,8 @@ bool CWallet::CreateSparkMintTransactions( } // Generate dummy mint coins to save time - std::vector recipients = CWallet::CreateSparkMintRecipients(singleTxOutputs, false); + std::vector serial_context; + std::vector recipients = CWallet::CreateSparkMintRecipients(singleTxOutputs, serial_context, false); for (auto& recipient : recipients) { // vout to create mint CTxOut txout(recipient.nAmount, recipient.scriptPubKey); @@ -5525,7 +5529,13 @@ bool CWallet::CreateSparkMintTransactions( } // Generate real mint coins - recipients = CWallet::CreateSparkMintRecipients(singleTxOutputs, true); + CDataStream serialContextStream(SER_NETWORK, PROTOCOL_VERSION); + for (auto& input : tx.vin) { + serialContextStream << input; + } + + recipients = CWallet::CreateSparkMintRecipients(singleTxOutputs, std::vector(serial_context.begin(), serial_context.end()), true); + size_t i = 0; for (auto& recipient : recipients) { CTxOut txout(recipient.nAmount, recipient.scriptPubKey); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 1b9e4f8d71..7eb473660e 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -940,6 +940,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface // generate recipient data for mint transaction, static std::vector CreateSparkMintRecipients( const std::vector& outputs, + const std::vector& serial_context, bool generate); static int GetRequiredCoinCountForAmount( From ed03245a12679ede1abeeee2bd9108bae0e713cd Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 5 Jun 2022 04:11:22 +0400 Subject: [PATCH 028/197] Use default address when auto minting --- src/wallet/spark_wallet.cpp | 11 +++++++++-- src/wallet/spark_wallet.h | 1 + src/wallet/wallet.cpp | 9 +-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/wallet/spark_wallet.cpp b/src/wallet/spark_wallet.cpp index bf5c4402f4..5243ace634 100644 --- a/src/wallet/spark_wallet.cpp +++ b/src/wallet/spark_wallet.cpp @@ -25,9 +25,9 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { // Write incoming view key into db, it is safe to be kept in db, it is used to identify incoming coins belonging to the wallet walletdb.writeIncomingViewKey(viewKey); - lastDiversifier = -1; // generate one initial address for wallet - addresses[lastDiversifier] = generateNextAddress(); + lastDiversifier = 0; + addresses[lastDiversifier] = getDefaultAddress(); // set 0 as last diversifier into db, we will update it later, in case coin comes, or user manually generates new address walletdb.writeDiversifier(lastDiversifier); } else { @@ -101,6 +101,13 @@ spark::Address CSparkWallet::generateNextAddress() { return spark::Address(viewKey, lastDiversifier); } +spark::Address CSparkWallet::getDefaultAddress() { + if (addresses.count(0)) + return addresses[0]; + lastDiversifier = 0; + return spark::Address(viewKey, lastDiversifier); +} + spark::SpendKey CSparkWallet::generateSpendKey() { if (pwalletMain->IsLocked()) { LogPrintf("Spark spend key generation FAILED, wallet is locked\n"); diff --git a/src/wallet/spark_wallet.h b/src/wallet/spark_wallet.h index ca83a8dc90..d02fdeb92f 100644 --- a/src/wallet/spark_wallet.h +++ b/src/wallet/spark_wallet.h @@ -16,6 +16,7 @@ class CSparkWallet { // increment diversifier and generate address for that spark::Address generateNextAddress(); + spark::Address getDefaultAddress(); // assign difersifier to the value from db void resetDiversifierFromDB(CWalletDB& walletdb); // assign diversifier in to to current value diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index fb6f7bc388..b846123ab4 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5317,12 +5317,10 @@ bool CWallet::CreateSparkMintTransactions( std::vector remainingOutputs = outputs_; std::vector singleTxOutputs; if (autoMintAll) { - CWalletDB walletdb(strWalletFile); - sparkWallet->resetDiversifierFromDB(walletdb); spark::MintedCoinData mintedCoinData; mintedCoinData.v = mintedValue; mintedCoinData.memo = ""; - mintedCoinData.address = sparkWallet->generateNextAddress(); + mintedCoinData.address = sparkWallet->getDefaultAddress(); singleTxOutputs.push_back(mintedCoinData); } else { uint64_t remainingMintValue = mintedValue; @@ -5553,11 +5551,6 @@ bool CWallet::CreateSparkMintTransactions( //remove output from outputs_ vector if it got all requested value outputs_ = remainingOutputs; - if (autoMintAll) { - CWalletDB walletdb(strWalletFile); - sparkWallet->updatetDiversifierInDB(walletdb); - } - break; // Done, enough fee included. } From 8e6ae106e0e5adb68d232d28162e39f2f894c003 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 6 Jun 2022 06:10:25 +0400 Subject: [PATCH 029/197] Adding domain separators when doing hashes --- src/libspark/keys.cpp | 21 +++++++++++++++++---- src/primitives/mint_spend.cpp | 9 ++++++++- src/primitives/mint_spend.h | 1 + src/wallet/spark_wallet.cpp | 2 ++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/libspark/keys.cpp b/src/libspark/keys.cpp index 69c70027b3..f58e22ef65 100644 --- a/src/libspark/keys.cpp +++ b/src/libspark/keys.cpp @@ -18,12 +18,25 @@ SpendKey::SpendKey(const Params* params, const Scalar& r_) { this->r = r_; std::vector data; r.serialize(data.data()); - std::vector result(CSHA512().OUTPUT_SIZE); - CHash512 hash512; - hash512.Write(data.data(), data.size()).Finalize(&result[0]); + std::vector result(CSHA256().OUTPUT_SIZE); + CHash256 hash256; + std::string prefix1 = "s1_generation"; + hash256.Write(reinterpret_cast(prefix1.c_str()), prefix1.size()); + hash256.Write(data.data(), data.size()); + hash256.Finalize(&result[0]); this->s1.memberFromSeed(&result[0]); - this->s2.memberFromSeed(&result[32]); + + data.clear(); + result.clear(); + hash256.Reset(); + s1.serialize(data.data()); + + std::string prefix2 = "s2_generation"; + hash256.Write(reinterpret_cast(prefix2.c_str()), prefix2.size()); + hash256.Write(data.data(), data.size()); + hash256.Finalize(&result[0]); + this->s2.memberFromSeed(&result[0]); } const Params* SpendKey::get_params() const { diff --git a/src/primitives/mint_spend.cpp b/src/primitives/mint_spend.cpp index 82e57d4e21..8e97e14d5b 100644 --- a/src/primitives/mint_spend.cpp +++ b/src/primitives/mint_spend.cpp @@ -22,12 +22,19 @@ uint256 MintMeta::GetPubCoinValueHash() const { uint256 CSparkMintMeta::GetNonceHash() const { if(!nonceHash) - nonceHash.reset(primitives::GetSerialHash(k)); + nonceHash.reset(primitives::GetNonceHash(k)); return *nonceHash; } namespace primitives { +uint256 GetNonceHash(const secp_primitives::Scalar& nonce) { + CDataStream ss(SER_GETHASH, 0); + ss << "nonce_hash"; + ss << nonce; + return Hash(ss.begin(), ss.end()); +} + uint256 GetSerialHash(const secp_primitives::Scalar& bnSerial) { CDataStream ss(SER_GETHASH, 0); ss << bnSerial; diff --git a/src/primitives/mint_spend.h b/src/primitives/mint_spend.h index 81a7f90f26..c2800fa138 100644 --- a/src/primitives/mint_spend.h +++ b/src/primitives/mint_spend.h @@ -302,6 +302,7 @@ class CLelantusSpendEntry }; namespace primitives { +uint256 GetNonceHash(const secp_primitives::Scalar& nonce); uint256 GetSerialHash(const secp_primitives::Scalar& bnSerial); uint256 GetPubCoinValueHash(const secp_primitives::GroupElement& bnValue); } diff --git a/src/wallet/spark_wallet.cpp b/src/wallet/spark_wallet.cpp index 5243ace634..123855b2d4 100644 --- a/src/wallet/spark_wallet.cpp +++ b/src/wallet/spark_wallet.cpp @@ -124,6 +124,8 @@ spark::SpendKey CSparkWallet::generateSpendKey() { std::string nCountStr = std::to_string(nCount); CHash256 hasher; + std::string prefix = "r_generation"; + hasher.Write(reinterpret_cast(prefix.c_str()), prefix.size()); hasher.Write(secret.begin(), secret.size()); hasher.Write(reinterpret_cast(nCountStr.c_str()), nCountStr.size()); unsigned char hash[CSHA256::OUTPUT_SIZE]; From a98be5fc6cf84baa178f4d4160d5812c3703ddea Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 6 Jun 2022 07:15:01 +0400 Subject: [PATCH 030/197] Adding version and checksum in address serialization --- src/libspark/keys.cpp | 18 +++++++++++++++--- src/libspark/keys.h | 4 ++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/libspark/keys.cpp b/src/libspark/keys.cpp index f58e22ef65..d1398ed2be 100644 --- a/src/libspark/keys.cpp +++ b/src/libspark/keys.cpp @@ -159,24 +159,29 @@ std::string Address::GetHex() const { std::stringstream ss; ss << std::hex; + ss << version; + for (const auto b : buffer) { ss << (b >> 4); ss << (b & 0xF); } + std::string str = ss.str(); + uint160 checksum = Hash160(str.begin(), str.end()); + ss << checksum.GetHex(); return ss.str(); } void Address::SetHex(const std::string& str) { const std::size_t size = 2 * GroupElement::serialize_size + AES_BLOCKSIZE; - if (str.size() != size * 2) { + if (str.size() != ((size + 20) * 2 + 1)) { throw "Address: SetHex failed, invalid length"; } + version = *str.c_str(); std::array buffer; - for (std::size_t i = 0; i < buffer.size(); i++) { - auto hexs = str.substr(2 * i, 2); + auto hexs = str.substr(2 * i + 1, 2); if (::isxdigit(hexs[0]) && ::isxdigit(hexs[1])) { buffer[i] = strtol(hexs.c_str(), NULL, 16); @@ -188,6 +193,13 @@ void Address::SetHex(const std::string& str) { const unsigned char* ptr = Q1.deserialize(buffer.data()); Q2.deserialize(ptr); d.insert(d.end(), buffer.begin() + 2 * GroupElement::serialize_size, buffer.end()); + + // check for checksum validity + std::string checksum = str.substr (size * 2 + 1, str.size() - 1); + // get checksum from newly deserialized address to compare with checksum form input + std::string resultChecksum= GetHex().substr (size * 2 + 1, str.size() - 1); + if (checksum != resultChecksum) + throw "Address: SetHex failed, invalid checksum"; } } diff --git a/src/libspark/keys.h b/src/libspark/keys.h index 80aba7c7e0..c0c3b37147 100644 --- a/src/libspark/keys.h +++ b/src/libspark/keys.h @@ -5,6 +5,9 @@ namespace spark { +// Let's define inital spark address version as P (private), +const char SPARK_ADDRESS_VERSION = 'P'; + using namespace secp_primitives; class SpendKey { @@ -73,6 +76,7 @@ class Address { void SetHex(const std::string& str); private: + char version = SPARK_ADDRESS_VERSION; const Params* params; std::vector d; GroupElement Q1, Q2; From b72094411da45d9f7793bb47d3809d53114157d2 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 12 Jun 2022 19:21:53 +0400 Subject: [PATCH 031/197] Saving serial_context into CSparkMintMeta --- src/primitives/mint_spend.h | 2 ++ src/wallet/spark_wallet.cpp | 4 ++-- src/wallet/spark_wallet.h | 2 +- src/wallet/wallet.cpp | 6 ++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/primitives/mint_spend.h b/src/primitives/mint_spend.h index c2800fa138..d0d39988d2 100644 --- a/src/primitives/mint_spend.h +++ b/src/primitives/mint_spend.h @@ -26,6 +26,7 @@ struct CSparkMintMeta uint64_t v; // value Scalar k; // nonce std::string memo; // memo + std::vector serial_context; mutable boost::optional nonceHash; uint256 GetNonceHash() const; @@ -44,6 +45,7 @@ struct CSparkMintMeta READWRITE(v); READWRITE(k); READWRITE(memo); + READWRITE(serial_context); }; }; diff --git a/src/wallet/spark_wallet.cpp b/src/wallet/spark_wallet.cpp index 123855b2d4..1c0797aafc 100644 --- a/src/wallet/spark_wallet.cpp +++ b/src/wallet/spark_wallet.cpp @@ -176,12 +176,12 @@ std::vector CSparkWallet::ListSparkMints(bool fUnusedOnly, bool return setMints; } -spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta, const std::vector& serial_context) { +spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta) { const spark::Params* params = spark::Params::get_default(); spark::Address address = getAddress(meta.i); // type we are passing 0; as we don't care about type now char type = 0; - return spark::Coin(params, type, meta.k, address, meta.v, meta.memo, serial_context); + return spark::Coin(params, type, meta.k, address, meta.v, meta.memo, meta.serial_context); } void CSparkWallet::clearAllMints(CWalletDB& walletdb) { diff --git a/src/wallet/spark_wallet.h b/src/wallet/spark_wallet.h index d02fdeb92f..7e202a87b1 100644 --- a/src/wallet/spark_wallet.h +++ b/src/wallet/spark_wallet.h @@ -34,7 +34,7 @@ class CSparkWallet { // list spark mint, mint metadata in memory and in db should be the same at this moment, so get from memory std::vector ListSparkMints(bool fUnusedOnly = false, bool fMatureOnly = false); // generate spark Coin from meta data - spark::Coin getCoinFromMeta(const CSparkMintMeta& meta, const std::vector& serial_context); + spark::Coin getCoinFromMeta(const CSparkMintMeta& meta); // functions to get spark balance CAmount getFullBalance(); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b846123ab4..0d78c84715 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2823,10 +2823,8 @@ std::list> CWallet::GetAvailableSparkCoin for (const auto& mint : vecMints) { if(mint.v == 0) // ignore 0 mints which where created to increase privacy continue; - // get actual spark coin - //TODO levon get transaction to generate serial_context vector for coins - std::vector serial_context; - spark::Coin coin = sparkWallet->getCoinFromMeta(mint, serial_context); + + spark::Coin coin = sparkWallet->getCoinFromMeta(mint); coins.push_back(std::make_pair(coin, mint)); } From 17836f4b1407b133a08e098609abf1addbfba4a8 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 17 Jun 2022 02:03:34 +0400 Subject: [PATCH 032/197] Moving Spark related code into separate directory --- src/Makefile.am | 12 +- src/primitives/mint_spend.cpp | 13 - src/primitives/mint_spend.h | 37 - src/spark/primitives.cpp | 20 + src/spark/primitives.h | 48 ++ src/{spark.cpp => spark/state.cpp} | 2 +- src/{spark.h => spark/state.h} | 6 +- src/spark/wallet.cpp | 736 ++++++++++++++++++ src/{wallet/spark_wallet.h => spark/wallet.h} | 39 +- src/wallet/spark_wallet.cpp | 228 ------ src/wallet/wallet.cpp | 504 +----------- src/wallet/wallet.h | 18 +- src/wallet/walletdb.cpp | 1 + src/wallet/walletdb.h | 1 + 14 files changed, 857 insertions(+), 808 deletions(-) create mode 100644 src/spark/primitives.cpp create mode 100644 src/spark/primitives.h rename src/{spark.cpp => spark/state.cpp} (90%) rename src/{spark.h => spark/state.h} (79%) create mode 100644 src/spark/wallet.cpp rename src/{wallet/spark_wallet.h => spark/wallet.h} (66%) delete mode 100644 src/wallet/spark_wallet.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 3741ab0a7d..2882d44bf3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -218,7 +218,8 @@ BITCOIN_CORE_H = \ wallet/sigmaspendbuilder.h \ wallet/txbuilder.h \ wallet/lelantusjoinsplitbuilder.h \ - wallet/spark_wallet.h \ + spark/wallet.h \ + spark/primitives.h \ wallet/wallet.h \ wallet/walletexcept.h \ wallet/walletdb.h \ @@ -242,7 +243,7 @@ BITCOIN_CORE_H = \ bip47/secretpoint.h \ sigma.h \ lelantus.h \ - spark.h \ + spark/state.h \ blacklists.h \ coin_containers.h \ firo_params.h \ @@ -405,7 +406,7 @@ libbitcoin_server_a_SOURCES = \ versionbits.cpp \ sigma.cpp \ lelantus.cpp \ - spark.cpp \ + spark/state.cpp \ coin_containers.cpp \ mtpstate.cpp \ $(BITCOIN_CORE_H) @@ -432,7 +433,7 @@ libbitcoin_wallet_a_SOURCES = \ hdmint/wallet.cpp \ sigma.cpp \ lelantus.cpp \ - spark.cpp \ + spark/state.cpp \ wallet/crypter.cpp \ wallet/bip39.cpp \ wallet/mnemoniccontainer.cpp \ @@ -444,7 +445,8 @@ libbitcoin_wallet_a_SOURCES = \ wallet/lelantusjoinsplitbuilder.cpp \ wallet/walletexcept.cpp \ wallet/wallet.cpp \ - wallet/spark_wallet.cpp \ + spark/wallet.cpp \ + spark/primitives.cpp \ wallet/walletdb.cpp \ wallet/authhelper.cpp \ hdmint/tracker.cpp \ diff --git a/src/primitives/mint_spend.cpp b/src/primitives/mint_spend.cpp index 8e97e14d5b..486ca75996 100644 --- a/src/primitives/mint_spend.cpp +++ b/src/primitives/mint_spend.cpp @@ -20,21 +20,8 @@ uint256 MintMeta::GetPubCoinValueHash() const { return *pubCoinValueHash; } -uint256 CSparkMintMeta::GetNonceHash() const { - if(!nonceHash) - nonceHash.reset(primitives::GetNonceHash(k)); - return *nonceHash; -} - namespace primitives { -uint256 GetNonceHash(const secp_primitives::Scalar& nonce) { - CDataStream ss(SER_GETHASH, 0); - ss << "nonce_hash"; - ss << nonce; - return Hash(ss.begin(), ss.end()); -} - uint256 GetSerialHash(const secp_primitives::Scalar& bnSerial) { CDataStream ss(SER_GETHASH, 0); ss << bnSerial; diff --git a/src/primitives/mint_spend.h b/src/primitives/mint_spend.h index d0d39988d2..016db788da 100644 --- a/src/primitives/mint_spend.h +++ b/src/primitives/mint_spend.h @@ -13,42 +13,6 @@ #include "sigma/coin.h" #include "serialize.h" #include "firo_params.h" -#include "libspark/coin.h" - -struct CSparkMintMeta -{ - int nHeight; - int nId; - bool isUsed; - uint256 txid; - uint64_t i; // diversifier - std::vector d; // encrypted diversifier - uint64_t v; // value - Scalar k; // nonce - std::string memo; // memo - std::vector serial_context; - mutable boost::optional nonceHash; - - uint256 GetNonceHash() const; - - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action) - { - READWRITE(nHeight); - READWRITE(nId); - READWRITE(isUsed); - READWRITE(txid); - READWRITE(i); - READWRITE(d); - READWRITE(v); - READWRITE(k); - READWRITE(memo); - READWRITE(serial_context); - }; -}; - //struct that is safe to store essential mint data, without holding any information that allows for actual spending (serial, randomness, private key) struct MintMeta @@ -304,7 +268,6 @@ class CLelantusSpendEntry }; namespace primitives { -uint256 GetNonceHash(const secp_primitives::Scalar& nonce); uint256 GetSerialHash(const secp_primitives::Scalar& bnSerial); uint256 GetPubCoinValueHash(const secp_primitives::GroupElement& bnValue); } diff --git a/src/spark/primitives.cpp b/src/spark/primitives.cpp new file mode 100644 index 0000000000..e4aceab700 --- /dev/null +++ b/src/spark/primitives.cpp @@ -0,0 +1,20 @@ +#include "primitives.h" +#include "../hash.h" + + +uint256 CSparkMintMeta::GetNonceHash() const { + if(!nonceHash) + nonceHash.reset(primitives::GetNonceHash(k)); + return *nonceHash; +} + +namespace primitives { + +uint256 GetNonceHash(const secp_primitives::Scalar& nonce) { + CDataStream ss(SER_GETHASH, 0); + ss << "nonce_hash"; + ss << nonce; + return Hash(ss.begin(), ss.end()); +} + +} // namespace primitives \ No newline at end of file diff --git a/src/spark/primitives.h b/src/spark/primitives.h new file mode 100644 index 0000000000..01673b1962 --- /dev/null +++ b/src/spark/primitives.h @@ -0,0 +1,48 @@ +#ifndef FIRO_PRIMITIVES_H +#define FIRO_PRIMITIVES_H + +#include "libspark/coin.h" +#include "serialize.h" +#include "../uint256.h" + +struct CSparkMintMeta +{ + int nHeight; + int nId; + bool isUsed; + uint256 txid; + uint64_t i; // diversifier + std::vector d; // encrypted diversifier + uint64_t v; // value + Scalar k; // nonce + std::string memo; // memo + std::vector serial_context; + mutable boost::optional nonceHash; + + uint256 GetNonceHash() const; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(nHeight); + READWRITE(nId); + READWRITE(isUsed); + READWRITE(txid); + READWRITE(i); + READWRITE(d); + READWRITE(v); + READWRITE(k); + READWRITE(memo); + READWRITE(serial_context); + }; +}; + + +namespace primitives { + uint256 GetNonceHash(const secp_primitives::Scalar& nonce); +} + + +#endif //FIRO_PRIMITIVES_H diff --git a/src/spark.cpp b/src/spark/state.cpp similarity index 90% rename from src/spark.cpp rename to src/spark/state.cpp index 5f5150e7de..fc67847ef7 100644 --- a/src/spark.cpp +++ b/src/spark/state.cpp @@ -1,4 +1,4 @@ -#include "spark.h" +#include "state.h" namespace spark { diff --git a/src/spark.h b/src/spark/state.h similarity index 79% rename from src/spark.h rename to src/spark/state.h index 43adc28ace..2abebbdbb1 100644 --- a/src/spark.h +++ b/src/spark/state.h @@ -2,8 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef _MAIN_SPARK_H__ -#define _MAIN_SPARK_H__ +#ifndef _MAIN_SPARK_STATE_H_ +#define _MAIN_SPARK_STATE_H_ #include "libspark/coin.h" #include "chain.h" @@ -14,4 +14,4 @@ bool GetOutPoint(COutPoint& outPoint, const spark::Coin coin); } // namespace spark -#endif //_MAIN_SPARK_H__ +#endif //_MAIN_SPARK_STATE_H_ diff --git a/src/spark/wallet.cpp b/src/spark/wallet.cpp new file mode 100644 index 0000000000..2706dab20b --- /dev/null +++ b/src/spark/wallet.cpp @@ -0,0 +1,736 @@ +#include "wallet.h" +#include "../wallet/wallet.h" +#include "../wallet/coincontrol.h" +#include "../hash.h" +#include "../validation.h" +#include "../policy/policy.h" +#include "../script/sign.h" +#include "state.h" + +const uint32_t DEFAULT_SPARK_NCOUNT = 1; + +CSparkWallet::CSparkWallet(const std::string& strWalletFile) { + + CWalletDB walletdb(strWalletFile); + + const spark::Params* params = spark::Params::get_default(); + viewKey = spark::IncomingViewKey(params); + + // try to get incoming view key from db, if it fails, that means it is first start + if (!walletdb.readIncomingViewKey(viewKey)) { + if (pwalletMain->IsLocked()) { + LogPrintf("Spark wallet creation FAILED, wallet is locked\n"); + return; + } + // Generating spark key set first time + spark::SpendKey spendKey = generateSpendKey(); + spark::FullViewKey fullViewKey = generateFullViewKey(spendKey); + viewKey = generateIncomingViewKey(fullViewKey); + + // Write incoming view key into db, it is safe to be kept in db, it is used to identify incoming coins belonging to the wallet + walletdb.writeIncomingViewKey(viewKey); + // generate one initial address for wallet + lastDiversifier = 0; + addresses[lastDiversifier] = getDefaultAddress(); + // set 0 as last diversifier into db, we will update it later, in case coin comes, or user manually generates new address + walletdb.writeDiversifier(lastDiversifier); + } else { + int32_t diversifierInDB = 0; + // read diversifier from db + walletdb.readDiversifier(diversifierInDB); + lastDiversifier = -1; + + // generate all used addresses + while (lastDiversifier < diversifierInDB) { + addresses[lastDiversifier] = generateNextAddress(); + } + + // get the list of coin metadata from db + std::list listMints = walletdb.ListSparkMints(); + for (const auto& itr : listMints) { + coinMeta[itr.GetNonceHash()] = itr; + } + } +} + +void CSparkWallet::resetDiversifierFromDB(CWalletDB& walletdb) { + walletdb.writeDiversifier(lastDiversifier); +} + +void CSparkWallet::updatetDiversifierInDB(CWalletDB& walletdb) { + walletdb.readDiversifier(lastDiversifier); +} + +CAmount CSparkWallet::getFullBalance() { + return getAvailableBalance() + getUnconfirmedBalance(); +} + +CAmount CSparkWallet::getAvailableBalance() { + CAmount result = 0; + for (auto& it : coinMeta) { + CSparkMintMeta mint = it.second; + + if (mint.isUsed) + continue; + + // Not confirmed + if (!mint.nHeight) + continue; + + result += mint.v; + } + return result; +} + +CAmount CSparkWallet::getUnconfirmedBalance() { + CAmount result = 0; + + for (auto& it : coinMeta) { + CSparkMintMeta mint = it.second; + if (mint.isUsed) + continue; + + // Continue if confirmed + if (mint.nHeight) + continue; + + result += mint.v; + } + + return result; +} + +spark::Address CSparkWallet::generateNextAddress() { + lastDiversifier++; + return spark::Address(viewKey, lastDiversifier); +} + +spark::Address CSparkWallet::getDefaultAddress() { + if (addresses.count(0)) + return addresses[0]; + lastDiversifier = 0; + return spark::Address(viewKey, lastDiversifier); +} + +spark::SpendKey CSparkWallet::generateSpendKey() { + if (pwalletMain->IsLocked()) { + LogPrintf("Spark spend key generation FAILED, wallet is locked\n"); + return spark::SpendKey(); + } + + CKey secret; + uint32_t nCount; + { + LOCK(pwalletMain->cs_wallet); + nCount = GetArg("-sparkncount", DEFAULT_SPARK_NCOUNT); + pwalletMain->GetKeyFromKeypath(BIP44_SPARK_INDEX, nCount, secret); + } + + std::string nCountStr = std::to_string(nCount); + CHash256 hasher; + std::string prefix = "r_generation"; + hasher.Write(reinterpret_cast(prefix.c_str()), prefix.size()); + hasher.Write(secret.begin(), secret.size()); + hasher.Write(reinterpret_cast(nCountStr.c_str()), nCountStr.size()); + unsigned char hash[CSHA256::OUTPUT_SIZE]; + hasher.Finalize(hash); + + secp_primitives::Scalar r; + r.memberFromSeed(hash); + const spark::Params* params = spark::Params::get_default(); + spark::SpendKey key(params, r); + return key; +} + +spark::FullViewKey CSparkWallet::generateFullViewKey(const spark::SpendKey& spend_key) { + return spark::FullViewKey(spend_key); +} + +spark::IncomingViewKey CSparkWallet::generateIncomingViewKey(const spark::FullViewKey& full_view_key) { + viewKey = spark::IncomingViewKey(full_view_key); + return viewKey; +} + +std::unordered_map CSparkWallet::getAllAddresses() { + return addresses; +} + +spark::Address CSparkWallet::getAddress(const int32_t& i) { + if (lastDiversifier < i || addresses.count(i) == 0) + return spark::Address(viewKey, lastDiversifier); + + return addresses[i]; +} + +std::vector CSparkWallet::ListSparkMints(bool fUnusedOnly, bool fMatureOnly) const { + std::vector setMints; + + for (auto& it : coinMeta) { + CSparkMintMeta mint = it.second; + if (fUnusedOnly && mint.isUsed) + continue; + + // Not confirmed + if (fMatureOnly && !mint.nHeight) + continue; + + setMints.push_back(mint); + } + + return setMints; +} + +spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta) const { + const spark::Params* params = spark::Params::get_default(); + spark::Address address(viewKey, meta.i); + // type we are passing 0; as we don't care about type now + char type = 0; + return spark::Coin(params, type, meta.k, address, meta.v, meta.memo, meta.serial_context); +} + +void CSparkWallet::clearAllMints(CWalletDB& walletdb) { + + for (auto& itr : coinMeta) { + walletdb.EraseSparkMint(itr.first); + } + + coinMeta.clear(); + lastDiversifier = 0; + walletdb.writeDiversifier(lastDiversifier); +} + +void CSparkWallet::eraseMint(const uint256& hash, CWalletDB& walletdb) { + walletdb.EraseSparkMint(hash); + coinMeta.erase(hash); +} +void CSparkWallet::addOrUpdate(const CSparkMintMeta& mint, CWalletDB& walletdb) { + if (mint.i > lastDiversifier) { + lastDiversifier = mint.i; + walletdb.writeDiversifier(lastDiversifier); + } + coinMeta[mint.GetNonceHash()] = mint; + walletdb.WriteSparkMint(mint.GetNonceHash(), mint); +} + +CSparkMintMeta CSparkWallet::getMintMeta(const uint256& hash) { + if (coinMeta.count(hash)) + return coinMeta[hash]; + return CSparkMintMeta(); +} + +std::vector CSparkWallet::listAddressCoins(const int32_t& i, bool fUnusedOnly) { + std::vector listMints; + + for (auto& itr : coinMeta) { + if (itr.second.i == i) { + if (fUnusedOnly && itr.second.isUsed) + continue; + listMints.push_back(itr.second); + } + } + return listMints; +} + +std::vector CSparkWallet::CreateSparkMintRecipients( + const std::vector& outputs, + const std::vector& serial_context, + bool generate) +{ + const spark::Params* params = spark::Params::get_default(); + + // create spark mints, if generate is false, skip actual math operations + spark::MintTransaction sparkMint(params, outputs, serial_context, generate); + + // verify if the mint is valid + if (generate && !sparkMint.verify()) { + throw std::runtime_error("Unable to validate spark mint."); + } + + // get serialized coins, also a schnorr proof with first coin, + std::vector serializedCoins = sparkMint.getMintedCoinsSerialized(); + + if (outputs.size() != serializedCoins.size()) + throw std::runtime_error("Spark mit output number should be equal to required number."); + + std::vector results; + results.reserve(outputs.size()); + + // serialize coins and put into scripts + for (size_t i = 0; i < outputs.size(); i++) { + // Create script for a coin + CScript script; + // opcode is inserted as 1 byte according to file script/script.h + script << OP_SPARKMINT; + script.insert(script.end(), serializedCoins[i].begin(), serializedCoins[i].end()); + CRecipient recipient = {script, CAmount(outputs[i].v), false}; + results.emplace_back(recipient); + } + + return results; +} + +bool CSparkWallet::CreateSparkMintTransactions( + const std::vector& outputs, + std::vector>& wtxAndFee, + CAmount& nAllFeeRet, + std::list& reservekeys, + int& nChangePosInOut, + std::string& strFailReason, + const CCoinControl *coinControl, + bool autoMintAll) +{ + + int nChangePosRequest = nChangePosInOut; + + // Create transaction template + CWalletTx wtxNew; + wtxNew.fTimeReceivedIsTxTime = true; + wtxNew.BindWallet(pwalletMain); + + CMutableTransaction txNew; + txNew.nLockTime = chainActive.Height(); + + assert(txNew.nLockTime <= (unsigned int) chainActive.Height()); + assert(txNew.nLockTime < LOCKTIME_THRESHOLD); + std::vector outputs_ = outputs; + CAmount valueToMint = 0; + + for (auto& output : outputs_) + valueToMint += output.v; + + { + LOCK2(cs_main, pwalletMain->cs_wallet); + { + std::list cacheWtxs; + // vector pairs for each transparent address + std::vector>> valueAndUTXO; + pwalletMain->AvailableCoinsForLMint(valueAndUTXO, coinControl); + + Shuffle(valueAndUTXO.begin(), valueAndUTXO.end(), FastRandomContext()); + + while (!valueAndUTXO.empty()) { + + // initialize + CWalletTx wtx = wtxNew; + CMutableTransaction tx = txNew; + + reservekeys.emplace_back(pwalletMain); + auto &reservekey = reservekeys.back(); + + if (GetRandInt(10) == 0) + tx.nLockTime = std::max(0, (int) tx.nLockTime - GetRandInt(100)); + + auto nFeeRet = 0; + LogPrintf("nFeeRet=%s\n", nFeeRet); + + auto itr = valueAndUTXO.begin(); + + // TODO(levon) do we need mint limit? if yes, define new MaxValue Mint for spark + CAmount valueToMintInTx = std::min( + ::Params().GetConsensus().nMaxValueLelantusMint, + itr->first); + + if (!autoMintAll) { + valueToMintInTx = std::min(valueToMintInTx, valueToMint); + } + + CAmount nValueToSelect, mintedValue; + + std::set> setCoins; + bool skipCoin = false; + // Start with no fee and loop until there is enough fee + while (true) { + mintedValue = valueToMintInTx; + nValueToSelect = mintedValue + nFeeRet; + + // if have no enough coins in this group then subtract fee from mint + if (nValueToSelect > itr->first) { + mintedValue -= nFeeRet; + nValueToSelect = mintedValue + nFeeRet; + } + + if (!MoneyRange(mintedValue) || mintedValue == 0) { + valueAndUTXO.erase(itr); + skipCoin = true; + break; + } + + nChangePosInOut = nChangePosRequest; + tx.vin.clear(); + tx.vout.clear(); + wtx.fFromMe = true; + wtx.changes.clear(); + setCoins.clear(); + std::vector remainingOutputs = outputs_; + std::vector singleTxOutputs; + if (autoMintAll) { + spark::MintedCoinData mintedCoinData; + mintedCoinData.v = mintedValue; + mintedCoinData.memo = ""; + mintedCoinData.address = getDefaultAddress(); + singleTxOutputs.push_back(mintedCoinData); + } else { + uint64_t remainingMintValue = mintedValue; + while (remainingMintValue > 0){ + // Create the mint data and push into vector + uint64_t singleMintValue = std::min(remainingMintValue, remainingOutputs.begin()->v); + spark::MintedCoinData mintedCoinData; + mintedCoinData.v = singleMintValue; + mintedCoinData.address = remainingOutputs.begin()->address; + mintedCoinData.memo = remainingOutputs.begin()->memo; + singleTxOutputs.push_back(mintedCoinData); + + // subtract minted amount from remaining value + remainingMintValue -= singleMintValue; + remainingOutputs.begin()->v -= singleMintValue; + + if (remainingOutputs.begin()->v == 0) + remainingOutputs.erase(remainingOutputs.begin()); + } + } + + // Generate dummy mint coins to save time + std::vector serial_context; + std::vector recipients = CSparkWallet::CreateSparkMintRecipients(singleTxOutputs, serial_context, false); + for (auto& recipient : recipients) { + // vout to create mint + CTxOut txout(recipient.nAmount, recipient.scriptPubKey); + + if (txout.IsDust(::minRelayTxFee)) { + strFailReason = _("Transaction amount too small"); + return false; + } + + tx.vout.push_back(txout); + } + // Choose coins to use + CAmount nValueIn = 0; + if (!pwalletMain->SelectCoins(itr->second, nValueToSelect, setCoins, nValueIn, coinControl)) { + + if (nValueIn < nValueToSelect) { + strFailReason = _("Insufficient funds"); + } + return false; + } + + double dPriority = 0; + for (auto const &pcoin : setCoins) { + CAmount nCredit = pcoin.first->tx->vout[pcoin.second].nValue; + //The coin age after the next block (depth+1) is used instead of the current, + //reflecting an assumption the user would accept a bit more delay for + //a chance at a free transaction. + //But mempool inputs might still be in the mempool, so their age stays 0 + int age = pcoin.first->GetDepthInMainChain(); + assert(age >= 0); + if (age != 0) + age += 1; + dPriority += (double) nCredit * age; + } + + CAmount nChange = nValueIn - nValueToSelect; + + if (nChange > 0) { + // Fill a vout to ourself + // TODO: pass in scriptChange instead of reservekey so + // change transaction isn't always pay-to-bitcoin-address + CScript scriptChange; + + // coin control: send change to custom address + if (coinControl && !boost::get(&coinControl->destChange)) + scriptChange = GetScriptForDestination(coinControl->destChange); + + // send change to one of the specified change addresses + else if (IsArgSet("-change") && mapMultiArgs.at("-change").size() > 0) { + CBitcoinAddress address( + mapMultiArgs.at("change")[GetRandInt(mapMultiArgs.at("-change").size())]); + CKeyID keyID; + if (!address.GetKeyID(keyID)) { + strFailReason = _("Bad change address"); + return false; + } + scriptChange = GetScriptForDestination(keyID); + } + + // no coin control: send change to newly generated address + else { + // Note: We use a new key here to keep it from being obvious which side is the change. + // The drawback is that by not reusing a previous key, the change may be lost if a + // backup is restored, if the backup doesn't have the new private key for the change. + // If we reused the old key, it would be possible to add code to look for and + // rediscover unknown transactions that were written with keys of ours to recover + // post-backup change. + + // Reserve a new key pair from key pool + CPubKey vchPubKey; + bool ret; + ret = reservekey.GetReservedKey(vchPubKey); + if (!ret) { + strFailReason = _("Keypool ran out, please call keypoolrefill first"); + return false; + } + + scriptChange = GetScriptForDestination(vchPubKey.GetID()); + } + + CTxOut newTxOut(nChange, scriptChange); + + // Never create dust outputs; if we would, just + // add the dust to the fee. + if (newTxOut.IsDust(::minRelayTxFee)) { + nChangePosInOut = -1; + nFeeRet += nChange; + reservekey.ReturnKey(); + } else { + + if (nChangePosInOut == -1) { + + // Insert change txn at random position: + nChangePosInOut = GetRandInt(tx.vout.size() + 1); + } else if ((unsigned int) nChangePosInOut > tx.vout.size()) { + + strFailReason = _("Change index out of range"); + return false; + } + + std::vector::iterator position = tx.vout.begin() + nChangePosInOut; + tx.vout.insert(position, newTxOut); + wtx.changes.insert(static_cast(nChangePosInOut)); + } + } else { + reservekey.ReturnKey(); + } + + // Fill vin + // + // Note how the sequence number is set to max()-1 so that the + // nLockTime set above actually works. + for (const auto &coin : setCoins) { + tx.vin.push_back(CTxIn( + coin.first->GetHash(), + coin.second, + CScript(), + std::numeric_limits::max() - 1)); + } + + // Fill in dummy signatures for fee calculation. + if (!pwalletMain->DummySignTx(tx, setCoins)) { + strFailReason = _("Signing transaction failed"); + return false; + } + + unsigned int nBytes = GetVirtualTransactionSize(tx); + + // Limit size + CTransaction txConst(tx); + if (GetTransactionWeight(txConst) >= MAX_STANDARD_TX_WEIGHT) { + strFailReason = _("Transaction too large"); + return false; + } + dPriority = txConst.ComputePriority(dPriority, nBytes); + + // Remove scriptSigs to eliminate the fee calculation dummy signatures + for (auto &vin : tx.vin) { + vin.scriptSig = CScript(); + vin.scriptWitness.SetNull(); + } + + // Can we complete this as a free transaction? + if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE) { + // Not enough fee: enough priority? + double dPriorityNeeded = mempool.estimateSmartPriority(nTxConfirmTarget); + // Require at least hard-coded AllowFree. + if (dPriority >= dPriorityNeeded && AllowFree(dPriority)) + break; + } + CAmount nFeeNeeded = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool); + + if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) { + nFeeNeeded = coinControl->nMinimumTotalFee; + } + + if (coinControl && coinControl->fOverrideFeeRate) + nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes); + + // If we made it here and we aren't even able to meet the relay fee on the next pass, give up + // because we must be at the maximum allowed fee. + if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) { + strFailReason = _("Transaction too large for fee policy"); + return false; + } + + if (nFeeRet >= nFeeNeeded) { + for (auto &usedCoin : setCoins) { + for (auto coin = itr->second.begin(); coin != itr->second.end(); coin++) { + if (usedCoin.first == coin->tx && usedCoin.second == coin->i) { + itr->first -= coin->tx->tx->vout[coin->i].nValue; + itr->second.erase(coin); + break; + } + } + } + + if (itr->second.empty()) { + valueAndUTXO.erase(itr); + } + + // Generate real mint coins + CDataStream serialContextStream(SER_NETWORK, PROTOCOL_VERSION); + for (auto& input : tx.vin) { + serialContextStream << input; + } + + recipients = CSparkWallet::CreateSparkMintRecipients(singleTxOutputs, std::vector(serial_context.begin(), serial_context.end()), true); + + size_t i = 0; + for (auto& recipient : recipients) { + CTxOut txout(recipient.nAmount, recipient.scriptPubKey); + LogPrintf("txout: %s\n", txout.ToString()); + while (i < tx.vout.size() - 1) { + if (tx.vout[i].scriptPubKey.IsSparkMint()) { + tx.vout[i] = txout; + break; + } + ++i; + } + ++i; + } + + //remove output from outputs_ vector if it got all requested value + outputs_ = remainingOutputs; + + break; // Done, enough fee included. + } + + // Include more fee and try again. + nFeeRet = nFeeNeeded; + continue; + } + + if(skipCoin) + continue; + + if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { + // Lastly, ensure this tx will pass the mempool's chain limits + LockPoints lp; + CTxMemPoolEntry entry(MakeTransactionRef(tx), 0, 0, 0, 0, false, 0, lp); + CTxMemPool::setEntries setAncestors; + size_t nLimitAncestors = GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); + size_t nLimitAncestorSize = GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; + size_t nLimitDescendants = GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); + size_t nLimitDescendantSize = + GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; + std::string errString; + if (!mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, + nLimitDescendants, nLimitDescendantSize, errString)) { + strFailReason = _("Transaction has too long of a mempool chain"); + return false; + } + } + + // Sign + int nIn = 0; + CTransaction txNewConst(tx); + for (const auto &coin : setCoins) { + bool signSuccess = false; + const CScript &scriptPubKey = coin.first->tx->vout[coin.second].scriptPubKey; + SignatureData sigdata; + signSuccess = ProduceSignature(TransactionSignatureCreator(pwalletMain, &txNewConst, nIn, + coin.first->tx->vout[coin.second].nValue, + SIGHASH_ALL), scriptPubKey, sigdata); + + if (!signSuccess) { + strFailReason = _("Signing transaction failed"); + return false; + } else { + UpdateTransaction(tx, nIn, sigdata); + } + nIn++; + } + + wtx.SetTx(MakeTransactionRef(std::move(tx))); + + wtxAndFee.push_back(std::make_pair(wtx, nFeeRet)); + + if (nChangePosInOut >= 0) { + // Cache wtx to somewhere because COutput use pointer of it. + cacheWtxs.push_back(wtx); + auto &wtx = cacheWtxs.back(); + + COutput out(&wtx, nChangePosInOut, wtx.GetDepthInMainChain(false), true, true); + auto val = wtx.tx->vout[nChangePosInOut].nValue; + + bool added = false; + for (auto &utxos : valueAndUTXO) { + auto const &o = utxos.second.front(); + if (o.tx->tx->vout[o.i].scriptPubKey == wtx.tx->vout[nChangePosInOut].scriptPubKey) { + utxos.first += val; + utxos.second.push_back(out); + + added = true; + } + } + + if (!added) { + valueAndUTXO.push_back({val, {out}}); + } + } + + nAllFeeRet += nFeeRet; + if(!autoMintAll) { + valueToMint -= mintedValue; + if (valueToMint == 0) + break; + } + } + } + } + + if (!autoMintAll && valueToMint > 0) { + return false; + } + + return true; +} + +std::list> CSparkWallet::GetAvailableSparkCoins(CWalletDB& walletdb, const CCoinControl *coinControl) const { + std::list> coins; + // get all unsued coins from spark wallet + std::vector vecMints = this->ListSparkMints(true, true); + for (const auto& mint : vecMints) { + if(mint.v == 0) // ignore 0 mints which where created to increase privacy + continue; + + spark::Coin coin = this->getCoinFromMeta(mint); + coins.push_back(std::make_pair(coin, mint)); + } + + std::set lockedCoins = pwalletMain->setLockedCoins; + + // Filter out coins that have not been selected from CoinControl should that be used + coins.remove_if([lockedCoins, coinControl](const std::pair& coin) { + COutPoint outPoint; + + // ignore if the coin is not actually on chain + if (!spark::GetOutPoint(outPoint, coin.first)) { + return true; + } + + // ignore if coin is locked + if(lockedCoins.count(outPoint) > 0){ + return true; + } + + // if we are using coincontrol, filter out unselected coins + if(coinControl != NULL){ + if(coinControl->HasSelected()){ + if(!coinControl->IsSelected(outPoint)){ + return true; + } + } + } + + return false; + }); + + return coins; +} \ No newline at end of file diff --git a/src/wallet/spark_wallet.h b/src/spark/wallet.h similarity index 66% rename from src/wallet/spark_wallet.h rename to src/spark/wallet.h index 7e202a87b1..ccf8502c3b 100644 --- a/src/wallet/spark_wallet.h +++ b/src/spark/wallet.h @@ -5,10 +5,18 @@ #ifndef FIRO_SPARK_WALLET_H #define FIRO_SPARK_WALLET_H +#include "primitives.h" #include "../libspark/keys.h" -#include "../coin_containers.h" -#include "../primitives/mint_spend.h" -#include "walletdb.h" +#include "../libspark/mint_transaction.h" +#include "../wallet/walletdb.h" + +class CRecipient; +class CReserveKey; +class CCoinControl; + +extern CChain chainActive; + +const uint32_t BIP44_SPARK_INDEX = 0x6; class CSparkWallet { public: @@ -32,9 +40,9 @@ class CSparkWallet { // get address for a diversifier spark::Address getAddress(const int32_t& i); // list spark mint, mint metadata in memory and in db should be the same at this moment, so get from memory - std::vector ListSparkMints(bool fUnusedOnly = false, bool fMatureOnly = false); + std::vector ListSparkMints(bool fUnusedOnly = false, bool fMatureOnly = false) const; // generate spark Coin from meta data - spark::Coin getCoinFromMeta(const CSparkMintMeta& meta); + spark::Coin getCoinFromMeta(const CSparkMintMeta& meta) const; // functions to get spark balance CAmount getFullBalance(); @@ -52,6 +60,27 @@ class CSparkWallet { // get the vector of mint metadata for a single address std::vector listAddressCoins(const int32_t& i, bool fUnusedOnly = false); + + // generate recipient data for mint transaction, + static std::vector CreateSparkMintRecipients( + const std::vector& outputs, + const std::vector& serial_context, + bool generate); + + bool CreateSparkMintTransactions( + const std::vector& outputs, + std::vector>& wtxAndFee, + CAmount& nAllFeeRet, + std::list& reservekeys, + int& nChangePosInOut, + std::string& strFailReason, + const CCoinControl *coinControl, + bool autoMintAll = false); + + // Returns the list of pairs of coins and meta data for that coin, + std::list> GetAvailableSparkCoins(CWalletDB& walletdb, const CCoinControl *coinControl = NULL) const; + private: // this is latest used diversifier int32_t lastDiversifier; diff --git a/src/wallet/spark_wallet.cpp b/src/wallet/spark_wallet.cpp deleted file mode 100644 index 1c0797aafc..0000000000 --- a/src/wallet/spark_wallet.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include "spark_wallet.h" -#include "wallet.h" -#include "walletdb.h" -#include "../hash.h" - -const uint32_t DEFAULT_SPARK_NCOUNT = 1; - -CSparkWallet::CSparkWallet(const std::string& strWalletFile) { - - CWalletDB walletdb(strWalletFile); - - const spark::Params* params = spark::Params::get_default(); - viewKey = spark::IncomingViewKey(params); - - // try to get incoming view key from db, if it fails, that means it is first start - if (!walletdb.readIncomingViewKey(viewKey)) { - if (pwalletMain->IsLocked()) { - LogPrintf("Spark wallet creation FAILED, wallet is locked\n"); - return; - } - // Generating spark key set first time - spark::SpendKey spendKey = generateSpendKey(); - spark::FullViewKey fullViewKey = generateFullViewKey(spendKey); - viewKey = generateIncomingViewKey(fullViewKey); - - // Write incoming view key into db, it is safe to be kept in db, it is used to identify incoming coins belonging to the wallet - walletdb.writeIncomingViewKey(viewKey); - // generate one initial address for wallet - lastDiversifier = 0; - addresses[lastDiversifier] = getDefaultAddress(); - // set 0 as last diversifier into db, we will update it later, in case coin comes, or user manually generates new address - walletdb.writeDiversifier(lastDiversifier); - } else { - int32_t diversifierInDB = 0; - // read diversifier from db - walletdb.readDiversifier(diversifierInDB); - lastDiversifier = -1; - - // generate all used addresses - while (lastDiversifier < diversifierInDB) { - addresses[lastDiversifier] = generateNextAddress(); - } - - // get the list of coin metadata from db - std::list listMints = walletdb.ListSparkMints(); - for (const auto& itr : listMints) { - coinMeta[itr.GetNonceHash()] = itr; - } - } -} - -void CSparkWallet::resetDiversifierFromDB(CWalletDB& walletdb) { - walletdb.writeDiversifier(lastDiversifier); -} - -void CSparkWallet::updatetDiversifierInDB(CWalletDB& walletdb) { - walletdb.readDiversifier(lastDiversifier); -} - -CAmount CSparkWallet::getFullBalance() { - return getAvailableBalance() + getUnconfirmedBalance(); -} - -CAmount CSparkWallet::getAvailableBalance() { - CAmount result = 0; - for (auto& it : coinMeta) { - CSparkMintMeta mint = it.second; - - if (mint.isUsed) - continue; - - // Not confirmed - if (!mint.nHeight) - continue; - - result += mint.v; - } - return result; -} - -CAmount CSparkWallet::getUnconfirmedBalance() { - CAmount result = 0; - - for (auto& it : coinMeta) { - CSparkMintMeta mint = it.second; - if (mint.isUsed) - continue; - - // Continue if confirmed - if (mint.nHeight) - continue; - - result += mint.v; - } - - return result; -} - -spark::Address CSparkWallet::generateNextAddress() { - lastDiversifier++; - return spark::Address(viewKey, lastDiversifier); -} - -spark::Address CSparkWallet::getDefaultAddress() { - if (addresses.count(0)) - return addresses[0]; - lastDiversifier = 0; - return spark::Address(viewKey, lastDiversifier); -} - -spark::SpendKey CSparkWallet::generateSpendKey() { - if (pwalletMain->IsLocked()) { - LogPrintf("Spark spend key generation FAILED, wallet is locked\n"); - return spark::SpendKey(); - } - - CKey secret; - uint32_t nCount; - { - LOCK(pwalletMain->cs_wallet); - nCount = GetArg("-sparkncount", DEFAULT_SPARK_NCOUNT); - pwalletMain->GetKeyFromKeypath(BIP44_SPARK_INDEX, nCount, secret); - } - - std::string nCountStr = std::to_string(nCount); - CHash256 hasher; - std::string prefix = "r_generation"; - hasher.Write(reinterpret_cast(prefix.c_str()), prefix.size()); - hasher.Write(secret.begin(), secret.size()); - hasher.Write(reinterpret_cast(nCountStr.c_str()), nCountStr.size()); - unsigned char hash[CSHA256::OUTPUT_SIZE]; - hasher.Finalize(hash); - - secp_primitives::Scalar r; - r.memberFromSeed(hash); - const spark::Params* params = spark::Params::get_default(); - spark::SpendKey key(params, r); - return key; -} - -spark::FullViewKey CSparkWallet::generateFullViewKey(const spark::SpendKey& spend_key) { - return spark::FullViewKey(spend_key); -} - -spark::IncomingViewKey CSparkWallet::generateIncomingViewKey(const spark::FullViewKey& full_view_key) { - viewKey = spark::IncomingViewKey(full_view_key); - return viewKey; -} - -std::unordered_map CSparkWallet::getAllAddresses() { - return addresses; -} - -spark::Address CSparkWallet::getAddress(const int32_t& i) { - if (lastDiversifier < i || addresses.count(i) == 0) - return spark::Address(viewKey, lastDiversifier); - - return addresses[i]; -} - -std::vector CSparkWallet::ListSparkMints(bool fUnusedOnly, bool fMatureOnly) { - std::vector setMints; - - for (auto& it : coinMeta) { - CSparkMintMeta mint = it.second; - if (fUnusedOnly && mint.isUsed) - continue; - - // Not confirmed - if (fMatureOnly && !mint.nHeight) - continue; - - setMints.push_back(mint); - } - - return setMints; -} - -spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta) { - const spark::Params* params = spark::Params::get_default(); - spark::Address address = getAddress(meta.i); - // type we are passing 0; as we don't care about type now - char type = 0; - return spark::Coin(params, type, meta.k, address, meta.v, meta.memo, meta.serial_context); -} - -void CSparkWallet::clearAllMints(CWalletDB& walletdb) { - - for (auto& itr : coinMeta) { - walletdb.EraseSparkMint(itr.first); - } - - coinMeta.clear(); - lastDiversifier = 0; - walletdb.writeDiversifier(lastDiversifier); -} - -void CSparkWallet::eraseMint(const uint256& hash, CWalletDB& walletdb) { - walletdb.EraseSparkMint(hash); - coinMeta.erase(hash); -} -void CSparkWallet::addOrUpdate(const CSparkMintMeta& mint, CWalletDB& walletdb) { - if (mint.i > lastDiversifier) { - lastDiversifier = mint.i; - walletdb.writeDiversifier(lastDiversifier); - } - coinMeta[mint.GetNonceHash()] = mint; - walletdb.WriteSparkMint(mint.GetNonceHash(), mint); -} - -CSparkMintMeta CSparkWallet::getMintMeta(const uint256& hash) { - if (coinMeta.count(hash)) - return coinMeta[hash]; - return CSparkMintMeta(); -} - -std::vector CSparkWallet::listAddressCoins(const int32_t& i, bool fUnusedOnly) { - std::vector listMints; - - for (auto& itr : coinMeta) { - if (itr.second.i == i) { - if (fUnusedOnly && itr.second.isUsed) - continue; - listMints.push_back(itr.second); - } - } - return listMints; -} \ No newline at end of file diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0d78c84715..66a267c8ab 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -22,7 +22,6 @@ #include "../sigma/spend_metadata.h" #include "../sigma/coin.h" #include "lelantus.h" -#include "spark.h" #include "llmq/quorums_instantsend.h" #include "llmq/quorums_chainlocks.h" #include "net.h" @@ -2817,84 +2816,8 @@ std::list> CWallet::GetAvailableSparkCoin LOCK2(cs_main, cs_wallet); CWalletDB walletdb(strWalletFile); - std::list> coins; - // get all unsued coins from spark wallet - std::vector vecMints = sparkWallet->ListSparkMints(true, true); - for (const auto& mint : vecMints) { - if(mint.v == 0) // ignore 0 mints which where created to increase privacy - continue; - - spark::Coin coin = sparkWallet->getCoinFromMeta(mint); - coins.push_back(std::make_pair(coin, mint)); - } - - std::set lockedCoins = setLockedCoins; - - // Filter out coins that have not been selected from CoinControl should that be used - coins.remove_if([lockedCoins, coinControl](const std::pair& coin) { - COutPoint outPoint; - - // ignore if the coin is not actually on chain - if (!spark::GetOutPoint(outPoint, coin.first)) { - return true; - } - - // ignore if coin is locked - if(lockedCoins.count(outPoint) > 0){ - return true; - } - // if we are using coincontrol, filter out unselected coins - if(coinControl != NULL){ - if(coinControl->HasSelected()){ - if(!coinControl->IsSelected(outPoint)){ - return true; - } - } - } - - return false; - }); - - return coins; -} - -std::vector CWallet::CreateSparkMintRecipients( - const std::vector& outputs, - const std::vector& serial_context, - bool generate) -{ - const spark::Params* params = spark::Params::get_default(); - - // create spark mints, if generate is false, skip actual math operations - spark::MintTransaction sparkMint(params, outputs, serial_context, generate); - - // verify if the mint is valid - if (generate && !sparkMint.verify()) { - throw std::runtime_error("Unable to validate spark mint."); - } - - // get serialized coins, also a schnorr proof with first coin, - std::vector serializedCoins = sparkMint.getMintedCoinsSerialized(); - - if (outputs.size() != serializedCoins.size()) - throw std::runtime_error("Spark mit output number should be equal to required number."); - - std::vector results; - results.reserve(outputs.size()); - - // serialize coins and put into scripts - for (size_t i = 0; i < outputs.size(); i++) { - // Create script for a coin - CScript script; - // opcode is inserted as 1 byte according to file script/script.h - script << OP_SPARKMINT; - script.insert(script.end(), serializedCoins[i].begin(), serializedCoins[i].end()); - CRecipient recipient = {script, CAmount(outputs[i].v), false}; - results.emplace_back(recipient); - } - - return results; + return sparkWallet->GetAvailableSparkCoins(walletdb, coinControl); } // coinsIn has to be sorted in descending order. @@ -5220,429 +5143,6 @@ bool CWallet::CreateLelantusMintTransactions( return true; } -bool CWallet::CreateSparkMintTransactions( - const std::vector& outputs, - std::vector>& wtxAndFee, - CAmount& nAllFeeRet, - std::list& reservekeys, - int& nChangePosInOut, - std::string& strFailReason, - const CCoinControl *coinControl, - bool autoMintAll) -{ - - int nChangePosRequest = nChangePosInOut; - - // Create transaction template - CWalletTx wtxNew; - wtxNew.fTimeReceivedIsTxTime = true; - wtxNew.BindWallet(this); - - CMutableTransaction txNew; - txNew.nLockTime = chainActive.Height(); - - assert(txNew.nLockTime <= (unsigned int) chainActive.Height()); - assert(txNew.nLockTime < LOCKTIME_THRESHOLD); - std::vector outputs_ = outputs; - CAmount valueToMint = 0; - - for (auto& output : outputs_) - valueToMint += output.v; - - { - LOCK2(cs_main, cs_wallet); - { - std::list cacheWtxs; - // vector pairs for each transparent address - std::vector>> valueAndUTXO; - AvailableCoinsForLMint(valueAndUTXO, coinControl); - - Shuffle(valueAndUTXO.begin(), valueAndUTXO.end(), FastRandomContext()); - - while (!valueAndUTXO.empty()) { - - // initialize - CWalletTx wtx = wtxNew; - CMutableTransaction tx = txNew; - - reservekeys.emplace_back(this); - auto &reservekey = reservekeys.back(); - - if (GetRandInt(10) == 0) - tx.nLockTime = std::max(0, (int) tx.nLockTime - GetRandInt(100)); - - auto nFeeRet = 0; - LogPrintf("nFeeRet=%s\n", nFeeRet); - - auto itr = valueAndUTXO.begin(); - - // TODO(levon) do we need mint limit? if yes, define new MaxValue Mint for spark - CAmount valueToMintInTx = std::min( - ::Params().GetConsensus().nMaxValueLelantusMint, - itr->first); - - if (!autoMintAll) { - valueToMintInTx = std::min(valueToMintInTx, valueToMint); - } - - CAmount nValueToSelect, mintedValue; - - std::set> setCoins; - bool skipCoin = false; - // Start with no fee and loop until there is enough fee - while (true) { - mintedValue = valueToMintInTx; - nValueToSelect = mintedValue + nFeeRet; - - // if have no enough coins in this group then subtract fee from mint - if (nValueToSelect > itr->first) { - mintedValue -= nFeeRet; - nValueToSelect = mintedValue + nFeeRet; - } - - if (!MoneyRange(mintedValue) || mintedValue == 0) { - valueAndUTXO.erase(itr); - skipCoin = true; - break; - } - - nChangePosInOut = nChangePosRequest; - tx.vin.clear(); - tx.vout.clear(); - wtx.fFromMe = true; - wtx.changes.clear(); - setCoins.clear(); - std::vector remainingOutputs = outputs_; - std::vector singleTxOutputs; - if (autoMintAll) { - spark::MintedCoinData mintedCoinData; - mintedCoinData.v = mintedValue; - mintedCoinData.memo = ""; - mintedCoinData.address = sparkWallet->getDefaultAddress(); - singleTxOutputs.push_back(mintedCoinData); - } else { - uint64_t remainingMintValue = mintedValue; - while (remainingMintValue > 0){ - // Create the mint data and push into vector - uint64_t singleMintValue = std::min(remainingMintValue, remainingOutputs.begin()->v); - spark::MintedCoinData mintedCoinData; - mintedCoinData.v = singleMintValue; - mintedCoinData.address = remainingOutputs.begin()->address; - mintedCoinData.memo = remainingOutputs.begin()->memo; - singleTxOutputs.push_back(mintedCoinData); - - // subtract minted amount from remaining value - remainingMintValue -= singleMintValue; - remainingOutputs.begin()->v -= singleMintValue; - - if (remainingOutputs.begin()->v == 0) - remainingOutputs.erase(remainingOutputs.begin()); - } - } - - // Generate dummy mint coins to save time - std::vector serial_context; - std::vector recipients = CWallet::CreateSparkMintRecipients(singleTxOutputs, serial_context, false); - for (auto& recipient : recipients) { - // vout to create mint - CTxOut txout(recipient.nAmount, recipient.scriptPubKey); - - if (txout.IsDust(::minRelayTxFee)) { - strFailReason = _("Transaction amount too small"); - return false; - } - - tx.vout.push_back(txout); - } - // Choose coins to use - CAmount nValueIn = 0; - if (!SelectCoins(itr->second, nValueToSelect, setCoins, nValueIn, coinControl)) { - - if (nValueIn < nValueToSelect) { - strFailReason = _("Insufficient funds"); - } - return false; - } - - double dPriority = 0; - for (auto const &pcoin : setCoins) { - CAmount nCredit = pcoin.first->tx->vout[pcoin.second].nValue; - //The coin age after the next block (depth+1) is used instead of the current, - //reflecting an assumption the user would accept a bit more delay for - //a chance at a free transaction. - //But mempool inputs might still be in the mempool, so their age stays 0 - int age = pcoin.first->GetDepthInMainChain(); - assert(age >= 0); - if (age != 0) - age += 1; - dPriority += (double) nCredit * age; - } - - CAmount nChange = nValueIn - nValueToSelect; - - if (nChange > 0) { - // Fill a vout to ourself - // TODO: pass in scriptChange instead of reservekey so - // change transaction isn't always pay-to-bitcoin-address - CScript scriptChange; - - // coin control: send change to custom address - if (coinControl && !boost::get(&coinControl->destChange)) - scriptChange = GetScriptForDestination(coinControl->destChange); - - // send change to one of the specified change addresses - else if (IsArgSet("-change") && mapMultiArgs.at("-change").size() > 0) { - CBitcoinAddress address( - mapMultiArgs.at("change")[GetRandInt(mapMultiArgs.at("-change").size())]); - CKeyID keyID; - if (!address.GetKeyID(keyID)) { - strFailReason = _("Bad change address"); - return false; - } - scriptChange = GetScriptForDestination(keyID); - } - - // no coin control: send change to newly generated address - else { - // Note: We use a new key here to keep it from being obvious which side is the change. - // The drawback is that by not reusing a previous key, the change may be lost if a - // backup is restored, if the backup doesn't have the new private key for the change. - // If we reused the old key, it would be possible to add code to look for and - // rediscover unknown transactions that were written with keys of ours to recover - // post-backup change. - - // Reserve a new key pair from key pool - CPubKey vchPubKey; - bool ret; - ret = reservekey.GetReservedKey(vchPubKey); - if (!ret) { - strFailReason = _("Keypool ran out, please call keypoolrefill first"); - return false; - } - - scriptChange = GetScriptForDestination(vchPubKey.GetID()); - } - - CTxOut newTxOut(nChange, scriptChange); - - // Never create dust outputs; if we would, just - // add the dust to the fee. - if (newTxOut.IsDust(::minRelayTxFee)) { - nChangePosInOut = -1; - nFeeRet += nChange; - reservekey.ReturnKey(); - } else { - - if (nChangePosInOut == -1) { - - // Insert change txn at random position: - nChangePosInOut = GetRandInt(tx.vout.size() + 1); - } else if ((unsigned int) nChangePosInOut > tx.vout.size()) { - - strFailReason = _("Change index out of range"); - return false; - } - - std::vector::iterator position = tx.vout.begin() + nChangePosInOut; - tx.vout.insert(position, newTxOut); - wtx.changes.insert(static_cast(nChangePosInOut)); - } - } else { - reservekey.ReturnKey(); - } - - // Fill vin - // - // Note how the sequence number is set to max()-1 so that the - // nLockTime set above actually works. - for (const auto &coin : setCoins) { - tx.vin.push_back(CTxIn( - coin.first->GetHash(), - coin.second, - CScript(), - std::numeric_limits::max() - 1)); - } - - // Fill in dummy signatures for fee calculation. - if (!DummySignTx(tx, setCoins)) { - strFailReason = _("Signing transaction failed"); - return false; - } - - unsigned int nBytes = GetVirtualTransactionSize(tx); - - // Limit size - CTransaction txConst(tx); - if (GetTransactionWeight(txConst) >= MAX_STANDARD_TX_WEIGHT) { - strFailReason = _("Transaction too large"); - return false; - } - dPriority = txConst.ComputePriority(dPriority, nBytes); - - // Remove scriptSigs to eliminate the fee calculation dummy signatures - for (auto &vin : tx.vin) { - vin.scriptSig = CScript(); - vin.scriptWitness.SetNull(); - } - - // Can we complete this as a free transaction? - if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE) { - // Not enough fee: enough priority? - double dPriorityNeeded = mempool.estimateSmartPriority(nTxConfirmTarget); - // Require at least hard-coded AllowFree. - if (dPriority >= dPriorityNeeded && AllowFree(dPriority)) - break; - } - CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool); - - if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) { - nFeeNeeded = coinControl->nMinimumTotalFee; - } - - if (coinControl && coinControl->fOverrideFeeRate) - nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes); - - // If we made it here and we aren't even able to meet the relay fee on the next pass, give up - // because we must be at the maximum allowed fee. - if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) { - strFailReason = _("Transaction too large for fee policy"); - return false; - } - - if (nFeeRet >= nFeeNeeded) { - for (auto &usedCoin : setCoins) { - for (auto coin = itr->second.begin(); coin != itr->second.end(); coin++) { - if (usedCoin.first == coin->tx && usedCoin.second == coin->i) { - itr->first -= coin->tx->tx->vout[coin->i].nValue; - itr->second.erase(coin); - break; - } - } - } - - if (itr->second.empty()) { - valueAndUTXO.erase(itr); - } - - // Generate real mint coins - CDataStream serialContextStream(SER_NETWORK, PROTOCOL_VERSION); - for (auto& input : tx.vin) { - serialContextStream << input; - } - - recipients = CWallet::CreateSparkMintRecipients(singleTxOutputs, std::vector(serial_context.begin(), serial_context.end()), true); - - size_t i = 0; - for (auto& recipient : recipients) { - CTxOut txout(recipient.nAmount, recipient.scriptPubKey); - LogPrintf("txout: %s\n", txout.ToString()); - while (i < tx.vout.size() - 1) { - if (tx.vout[i].scriptPubKey.IsSparkMint()) { - tx.vout[i] = txout; - break; - } - ++i; - } - ++i; - } - - //remove output from outputs_ vector if it got all requested value - outputs_ = remainingOutputs; - - break; // Done, enough fee included. - } - - // Include more fee and try again. - nFeeRet = nFeeNeeded; - continue; - } - - if(skipCoin) - continue; - - if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { - // Lastly, ensure this tx will pass the mempool's chain limits - LockPoints lp; - CTxMemPoolEntry entry(MakeTransactionRef(tx), 0, 0, 0, 0, false, 0, lp); - CTxMemPool::setEntries setAncestors; - size_t nLimitAncestors = GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); - size_t nLimitAncestorSize = GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; - size_t nLimitDescendants = GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); - size_t nLimitDescendantSize = - GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; - std::string errString; - if (!mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, - nLimitDescendants, nLimitDescendantSize, errString)) { - strFailReason = _("Transaction has too long of a mempool chain"); - return false; - } - } - - // Sign - int nIn = 0; - CTransaction txNewConst(tx); - for (const auto &coin : setCoins) { - bool signSuccess = false; - const CScript &scriptPubKey = coin.first->tx->vout[coin.second].scriptPubKey; - SignatureData sigdata; - signSuccess = ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, - coin.first->tx->vout[coin.second].nValue, - SIGHASH_ALL), scriptPubKey, sigdata); - - if (!signSuccess) { - strFailReason = _("Signing transaction failed"); - return false; - } else { - UpdateTransaction(tx, nIn, sigdata); - } - nIn++; - } - - wtx.SetTx(MakeTransactionRef(std::move(tx))); - - wtxAndFee.push_back(std::make_pair(wtx, nFeeRet)); - - if (nChangePosInOut >= 0) { - // Cache wtx to somewhere because COutput use pointer of it. - cacheWtxs.push_back(wtx); - auto &wtx = cacheWtxs.back(); - - COutput out(&wtx, nChangePosInOut, wtx.GetDepthInMainChain(false), true, true); - auto val = wtx.tx->vout[nChangePosInOut].nValue; - - bool added = false; - for (auto &utxos : valueAndUTXO) { - auto const &o = utxos.second.front(); - if (o.tx->tx->vout[o.i].scriptPubKey == wtx.tx->vout[nChangePosInOut].scriptPubKey) { - utxos.first += val; - utxos.second.push_back(out); - - added = true; - } - } - - if (!added) { - valueAndUTXO.push_back({val, {out}}); - } - } - - nAllFeeRet += nFeeRet; - if(!autoMintAll) { - valueToMint -= mintedValue; - if (valueToMint == 0) - break; - } - } - } - } - - if (!autoMintAll && valueToMint > 0) { - return false; - } - - return true; -} - bool CWallet::CreateMintTransaction(CScript pubCoin, int64_t nValue, CWalletTx &wtxNew, CReserveKey &reservekey, int64_t &nFeeRet, std::string &strFailReason, @@ -5848,7 +5348,7 @@ std::string CWallet::MintAndStoreSpark( int nChangePosRet = -1; std::list reservekeys; - if (!CreateSparkMintTransactions(outputs, wtxAndFee, nFeeRequired, reservekeys, nChangePosRet, strError, coinControl, autoMintAll)) { + if (!sparkWallet->CreateSparkMintTransactions(outputs, wtxAndFee, nFeeRequired, reservekeys, nChangePosRet, strError, coinControl, autoMintAll)) { return strError; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 7eb473660e..bd7387fe77 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -9,7 +9,6 @@ #include "amount.h" #include "../sigma/coin.h" #include "../liblelantus/coin.h" -#include "../libspark/mint_transaction.h" #include "streams.h" #include "tinyformat.h" #include "ui_interface.h" @@ -21,7 +20,7 @@ #include "wallet/walletdb.h" #include "wallet/rpcwallet.h" #include "wallet/mnemoniccontainer.h" -#include "wallet/spark_wallet.h" +#include "../spark/wallet.h" #include "../base58.h" #include "firo_params.h" #include "univalue.h" @@ -107,8 +106,6 @@ const uint32_t BIP44_ELYSIUM_MINT_INDEX_V1 = 0x4; #endif const uint32_t BIP44_MINT_VALUE_INDEX = 0x5; -const uint32_t BIP44_SPARK_INDEX = 0x6; - class CBlockIndex; class CCoinControl; class COutput; @@ -652,6 +649,8 @@ class LelantusJoinSplitBuilder; class CWallet : public CCryptoKeyStore, public CValidationInterface { private: + friend class CSparkWallet; + static std::atomic fFlushThreadRunning; /** @@ -937,12 +936,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CHDMint& vDMint, bool generate = true); - // generate recipient data for mint transaction, - static std::vector CreateSparkMintRecipients( - const std::vector& outputs, - const std::vector& serial_context, - bool generate); - static int GetRequiredCoinCountForAmount( const CAmount& required, const std::vector& denominations); @@ -1024,10 +1017,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::list& reservekeys, int& nChangePosInOut, std::string& strFailReason, const CCoinControl *coinControl, bool autoMintAll = false, bool sign = true); - bool CreateSparkMintTransactions(const std::vector& outputs, std::vector>& wtxAndFee, - CAmount& nAllFeeRet, - std::list& reservekeys, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl *coinControl, bool autoMintAll = false); + CWalletTx CreateSigmaSpendTransaction( const std::vector& recipients, diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 1f4d136762..cdf4b5b5db 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -13,6 +13,7 @@ #include "util.h" #include "utiltime.h" #include "wallet/wallet.h" +#include "spark/wallet.h" #include "bip47/account.h" diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 3ebf6addc8..d368671e34 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -19,6 +19,7 @@ #include "../secp256k1/include/GroupElement.h" #include "../secp256k1/include/Scalar.h" #include "../libspark/keys.h" +#include "../spark/primitives.h" #include #include From ff4e4c2742d1eebe4d215ae29f596ede46fa12c7 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 27 Jun 2022 04:52:31 +0400 Subject: [PATCH 033/197] Spark state implemented --- src/chainparams.cpp | 10 + src/consensus/params.h | 4 + src/firo_params.h | 6 + src/libspark/coin.cpp | 9 + src/libspark/coin.h | 4 + src/libspark/mint_transaction.cpp | 18 ++ src/libspark/mint_transaction.h | 4 + src/primitives/transaction.cpp | 21 ++ src/primitives/transaction.h | 7 +- src/script/script.cpp | 5 + src/script/script.h | 3 + src/spark/primitives.cpp | 45 +++- src/spark/primitives.h | 21 ++ src/spark/state.cpp | 332 +++++++++++++++++++++++++++++- src/spark/state.h | 157 ++++++++++++++ src/txmempool.h | 2 + src/validation.cpp | 26 ++- 17 files changed, 665 insertions(+), 9 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 1ce88b938b..3ad61cd9b4 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -401,6 +401,8 @@ class CMainParams : public CChainParams { consensus.nOldSigmaBanBlock = ZC_OLD_SIGMA_BAN_BLOCK; consensus.nLelantusStartBlock = ZC_LELANTUS_STARTING_BLOCK; consensus.nLelantusFixesStartBlock = ZC_LELANTUS_FIXES_START_BLOCK; + consensus.nSparkStartBlock = SPARK_START_BLOCK; + consensus.nLelantusGracefulPeriod = LELANTUS_GRACEFUL_PERIOD; consensus.nZerocoinV2MintMempoolGracefulPeriod = ZC_V2_MINT_GRACEFUL_MEMPOOL_PERIOD; consensus.nZerocoinV2MintGracefulPeriod = ZC_V2_MINT_GRACEFUL_PERIOD; consensus.nZerocoinV2SpendMempoolGracefulPeriod = ZC_V2_SPEND_GRACEFUL_MEMPOOL_PERIOD; @@ -698,6 +700,9 @@ class CTestNetParams : public CChainParams { consensus.nLelantusStartBlock = ZC_LELANTUS_TESTNET_STARTING_BLOCK; consensus.nLelantusFixesStartBlock = ZC_LELANTUS_TESTNET_FIXES_START_BLOCK; + consensus.nSparkStartBlock = SPARK_TESTNET_START_BLOCK; + consensus.nLelantusGracefulPeriod = LELANTUS_TESTNET_GRACEFUL_PERIOD; + consensus.nZerocoinV2MintMempoolGracefulPeriod = ZC_V2_MINT_TESTNET_GRACEFUL_MEMPOOL_PERIOD; consensus.nZerocoinV2MintGracefulPeriod = ZC_V2_MINT_TESTNET_GRACEFUL_PERIOD; consensus.nZerocoinV2SpendMempoolGracefulPeriod = ZC_V2_SPEND_TESTNET_GRACEFUL_MEMPOOL_PERIOD; @@ -940,6 +945,9 @@ class CDevNetParams : public CChainParams { consensus.nLelantusStartBlock = 1; consensus.nLelantusFixesStartBlock = 1; + consensus.nSparkStartBlock = 1000; + consensus.nLelantusGracefulPeriod = 1500; + consensus.nMaxSigmaInputPerBlock = ZC_SIGMA_INPUT_LIMIT_PER_BLOCK; consensus.nMaxValueSigmaSpendPerBlock = ZC_SIGMA_VALUE_SPEND_LIMIT_PER_BLOCK; consensus.nMaxSigmaInputPerTransaction = ZC_SIGMA_INPUT_LIMIT_PER_TRANSACTION; @@ -1164,6 +1172,8 @@ class CRegTestParams : public CChainParams { consensus.nOldSigmaBanBlock = 1; consensus.nLelantusStartBlock = 400; consensus.nLelantusFixesStartBlock = 400; + consensus.nSparkStartBlock = 1000; + consensus.nLelantusGracefulPeriod = 1500; consensus.nZerocoinV2MintMempoolGracefulPeriod = 1; consensus.nZerocoinV2MintGracefulPeriod = 1; consensus.nZerocoinV2SpendMempoolGracefulPeriod = 1; diff --git a/src/consensus/params.h b/src/consensus/params.h index 73d86013e7..263ce25d9d 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -251,6 +251,10 @@ struct Params { int nLelantusFixesStartBlock; + int nSparkStartBlock; + + int nLelantusGracefulPeriod; + // Lelantus Blacklist std::unordered_set lelantusBlacklist; diff --git a/src/firo_params.h b/src/firo_params.h index e8b8d26e72..21c2e4af83 100644 --- a/src/firo_params.h +++ b/src/firo_params.h @@ -166,6 +166,12 @@ static const int64_t DUST_HARD_LIMIT = 1000; // 0.00001 FIRO mininput /** Probability (percentage) that a Dandelion transaction enters fluff phase */ #define DANDELION_FLUFF 10 +// Spark +#define SPARK_START_BLOCK 550000 +#define SPARK_TESTNET_START_BLOCK 150000 +#define LELANTUS_GRACEFUL_PERIOD 600000 +#define LELANTUS_TESTNET_GRACEFUL_PERIOD 200000 + // Versions of zerocoin mint/spend transactions #define ZEROCOIN_TX_VERSION_3 30 #define ZEROCOIN_TX_VERSION_3_1 31 diff --git a/src/libspark/coin.cpp b/src/libspark/coin.cpp index 764e9a379c..892c8c9cb3 100644 --- a/src/libspark/coin.cpp +++ b/src/libspark/coin.cpp @@ -153,4 +153,13 @@ IdentifiedCoinData Coin::identify(const IncomingViewKey& incoming_view_key) { return data; } +std::size_t Coin::memoryRequired() { + secp_primitives::GroupElement groupElement; + return 1 + groupElement.memoryRequired() * 3 + 32 + AEAD_TAG_SIZE; +} + +bool Coin::operator==(const Coin& other) const { + return this->S == other.S; +} + } diff --git a/src/libspark/coin.h b/src/libspark/coin.h index 800159c40a..1184a730f1 100644 --- a/src/libspark/coin.h +++ b/src/libspark/coin.h @@ -81,6 +81,10 @@ class Coin { // Given a full view key, extract the coin's serial number and tag RecoveredCoinData recover(const FullViewKey& full_view_key, const IdentifiedCoinData& data); + static std::size_t memoryRequired(); + + bool operator==(const Coin& other) const; + protected: bool validate(const IncomingViewKey& incoming_view_key, IdentifiedCoinData& data); diff --git a/src/libspark/mint_transaction.cpp b/src/libspark/mint_transaction.cpp index ee492aa75b..f7dd8c4836 100644 --- a/src/libspark/mint_transaction.cpp +++ b/src/libspark/mint_transaction.cpp @@ -2,6 +2,10 @@ namespace spark { +MintTransaction::MintTransaction(const Params* params) { + this->params = params; +} + MintTransaction::MintTransaction( const Params* params, const std::vector& outputs, @@ -74,6 +78,20 @@ std::vector MintTransaction::getMintedCoinsSerialized() { return serializedCoins; } +void MintTransaction::setMintTransaction(std::vector& serializedCoins) { + bool first = true; + coins.resize(serializedCoins.size()); + size_t i = 0; + for (auto& stream : serializedCoins) { + stream >> coins[i]; + i++; + if (first) { + stream >> value_proof; + first = false; + } + } +} + void MintTransaction::getCoins(std::vector& coins_) { coins_.insert(coins_.end(), coins.begin(), coins.end()); } diff --git a/src/libspark/mint_transaction.h b/src/libspark/mint_transaction.h index 6f3af9cd20..fc537197f9 100644 --- a/src/libspark/mint_transaction.h +++ b/src/libspark/mint_transaction.h @@ -17,6 +17,7 @@ struct MintedCoinData { class MintTransaction { public: + MintTransaction(const Params* params); MintTransaction( const Params* params, const std::vector& outputs, @@ -28,6 +29,9 @@ class MintTransaction { // returns the vector of serialized coins, with first one it puts also the chnorr proof; std::vector getMintedCoinsSerialized(); + // deserialize from the vector of CDataStreams + void setMintTransaction(std::vector& serializedCoins); + void getCoins(std::vector& coins_); private: diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index c03fe6cc83..8bd3b5270f 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -210,6 +210,27 @@ bool CTransaction::IsLelantusMint() const return false; } +bool CTransaction::IsSparkTransaction() const +{ + return IsSparkMint() || IsSparkSpend(); +} + +bool CTransaction::IsSparkSpend() const +{ + if (nVersion >= 3 && nType == TRANSACTION_SPARK) + return true; + return false; +} + +bool CTransaction::IsSparkMint() const +{ + for (const CTxOut &txout: vout) { + if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) + return true; + } + return false; +} + bool CTransaction::IsZerocoinTransaction() const { return IsZerocoinSpend() || IsZerocoinMint(); diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index ae04b04deb..da12723003 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -35,7 +35,8 @@ enum { TRANSACTION_COINBASE = 5, TRANSACTION_QUORUM_COMMITMENT = 6, TRANSACTION_SPORK = 7, - TRANSACTION_LELANTUS = 8 + TRANSACTION_LELANTUS = 8, + TRANSACTION_SPARK = 9 }; /** An outpoint - a combination of a transaction hash and an index n into its vout */ @@ -453,6 +454,10 @@ class CTransaction bool IsZerocoinRemint() const; + bool IsSparkTransaction() const; + bool IsSparkSpend() const; + bool IsSparkMint() const; + bool HasNoRegularInputs() const; /** diff --git a/src/script/script.cpp b/src/script/script.cpp index c463d93595..be5f9e1b6f 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -331,6 +331,11 @@ bool CScript::IsSparkMint() const { (*this)[0] == OP_SPARKMINT); } +bool CScript::IsSparkSMint() const { + return (this->size() > 0 && + (*this)[0] == OP_SPARKSMINT); +} + bool CScript::IsMint() const { return IsZerocoinMint() || IsSigmaMint() || IsZerocoinRemint() || IsLelantusMint() || IsLelantusJMint(); } diff --git a/src/script/script.h b/src/script/script.h index 3c5170a9b3..9e0d1b7464 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -205,6 +205,7 @@ enum opcodetype // spark params OP_SPARKMINT = 0xd1, + OP_SPARKSMINT = 0xd2, }; const char* GetOpName(opcodetype opcode); @@ -678,6 +679,8 @@ class CScript : public CScriptBase // Spark bool IsSparkMint() const; + bool IsSparkSMint() const; + bool IsZerocoinRemint() const; bool IsMint() const; diff --git a/src/spark/primitives.cpp b/src/spark/primitives.cpp index e4aceab700..20ee2ed0e4 100644 --- a/src/spark/primitives.cpp +++ b/src/spark/primitives.cpp @@ -17,4 +17,47 @@ uint256 GetNonceHash(const secp_primitives::Scalar& nonce) { return Hash(ss.begin(), ss.end()); } -} // namespace primitives \ No newline at end of file +uint256 GetLTagHash(const secp_primitives::GroupElement& tag) { + CDataStream ss(SER_GETHASH, 0); + ss << "tag_hash"; + ss << tag; + return Hash(ss.begin(), ss.end()); +} + + +} // namespace primitives + +namespace spark { + +std::size_t CoinHash::operator ()(const spark::Coin& coin) const noexcept { + CDataStream ss(SER_GETHASH, 0); + ss << "coin_hash"; + ss << coin; + + uint256 hash = ::Hash(ss.begin(), ss.end()); + + std::size_t result; + std::memcpy(&result, hash.begin(), sizeof(std::size_t)); + return result; +} + +std::size_t CLTagHash::operator ()(const secp_primitives::GroupElement& tag) const noexcept { + CDataStream ss(SER_GETHASH, 0); + ss << "tag_hash"; + ss << tag; + uint256 hash = ::Hash(ss.begin(), ss.end()); + + std::size_t result; + std::memcpy(&result, hash.begin(), sizeof(std::size_t)); + return result; +} + + +CMintedCoinInfo CMintedCoinInfo::make(int coinGroupId, int nHeight) { + CMintedCoinInfo coinInfo; + coinInfo.coinGroupId = coinGroupId; + coinInfo.nHeight = nHeight; + return coinInfo; +} + +} // namespace spark \ No newline at end of file diff --git a/src/spark/primitives.h b/src/spark/primitives.h index 01673b1962..ac57bb68c8 100644 --- a/src/spark/primitives.h +++ b/src/spark/primitives.h @@ -42,6 +42,27 @@ struct CSparkMintMeta namespace primitives { uint256 GetNonceHash(const secp_primitives::Scalar& nonce); + uint256 GetLTagHash(const secp_primitives::GroupElement& tag); +} + +namespace spark { +// Custom hash for the spark coin. norte. THIS IS NOT SECURE HASH FUNCTION +struct CoinHash { + std::size_t operator()(const spark::Coin& coin) const noexcept; +}; + +// Custom hash for the linking tag. THIS IS NOT SECURE HASH FUNCTION +struct CLTagHash { + std::size_t operator()(const secp_primitives::GroupElement& tag) const noexcept; +}; + +struct CMintedCoinInfo { + int coinGroupId; + int nHeight; + + static CMintedCoinInfo make(int coinGroupId, int nHeight); +}; + } diff --git a/src/spark/state.cpp b/src/spark/state.cpp index fc67847ef7..1e8c3124db 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -1,9 +1,339 @@ #include "state.h" +#include "../validation.h" namespace spark { -bool GetOutPoint(COutPoint& outPoint, const spark::Coin coin) { +static CSparkState sparkState; + +static bool CheckLTag( + CValidationState &state, + CSparkTxInfo *sparkTxInfo, + const GroupElement& lTag, + int nHeight, + bool fConnectTip) { + // check for Spark transaction in this block as well + if (sparkTxInfo && + !sparkTxInfo->fInfoIsComplete && + sparkTxInfo->spentLTags.find(lTag) != sparkTxInfo->spentLTags.end()) + return state.DoS(0, error("CTransaction::CheckTransaction() : two or more spands with same linking tag in the same block")); + + // check for used linking tags in state + if (sparkState.IsUsedLTag(lTag)) { + // Proceed with checks ONLY if we're accepting tx into the memory pool or connecting block to the existing blockchain + if (nHeight == INT_MAX || fConnectTip) { + return state.DoS(0, error("CTransaction::CheckTransaction() : The Spark coin has been used")); + } + } + return true; +} + +// CSparkTxInfo +void CSparkTxInfo::Complete() { + // We need to sort mints lexicographically by serialized value of pubCoin. That's the way old code + // works, we need to stick to it. + sort(mints.begin(), mints.end(), + [](decltype(mints)::const_reference m1, decltype(mints)::const_reference m2)->bool { + CDataStream ds1(SER_DISK, CLIENT_VERSION), ds2(SER_DISK, CLIENT_VERSION); + ds1 << m1; + ds2 << m2; + return ds1.str() < ds2.str(); + }); + + // Mark this info as complete + fInfoIsComplete = true; +} + +bool IsSparkAllowed() +{ + LOCK(cs_main); + return IsSparkAllowed(chainActive.Height()); +} + +bool IsSparkAllowed(int height) +{ + return height >= ::Params().GetConsensus().nSparkStartBlock; +} + +void ParseSparkMintTransaction(const std::vector& scripts, MintTransaction& mintTransaction) +{ + std::vector serializedCoins; + bool first = true; + for (const auto& script : scripts) { + if (!script.IsSparkMint()) + throw std::invalid_argument("Script is not a Spark mint"); + + std::vector serialized(script.begin() + 1, script.end()); + size_t size = spark::Coin::memoryRequired() + 8; // 8 is the size of uint64_t + if (serialized.size() < size) { + throw std::invalid_argument("Script is not a valid Spark mint"); + } + + CDataStream stream( + std::vector(serialized.begin(), serialized.end()), + SER_NETWORK, + PROTOCOL_VERSION + ); + + serializedCoins.push_back(stream); + } + + mintTransaction.setMintTransaction(serializedCoins); +} + +bool CheckSparkMintTransaction( + const std::vector& scripts, + CValidationState &state, + uint256 hashTx, + bool fStatefulSigmaCheck, + CSparkTxInfo* sparkTxInfo) { + + LogPrintf("CheckSparkMintTransaction txHash = %s\n", hashTx.GetHex()); + const spark::Params* params = spark::Params::get_default(); + + MintTransaction mintTransaction(params); + try { + ParseSparkMintTransaction(scripts, mintTransaction); + } catch (std::invalid_argument&) { + return state.DoS(100, + false, + PUBCOIN_NOT_VALIDATE, + "CTransaction::CheckTransaction() : SparkMint parsing failure."); + } + + //checking whether MintTransaction is valid + if(!mintTransaction.verify()) + return state.DoS(100, + false, + PUBCOIN_NOT_VALIDATE, + "CheckSparkMintTransaction : mintTransaction verification failed"); + + std::vector coins; + mintTransaction.getCoins(coins); + + for (auto& coin : coins) { + if (coin.v > ::Params().GetConsensus().nMaxValueLelantusMint) + return state.DoS(100, + false, + REJECT_INVALID, + "CTransaction::CheckTransaction() : Spark Mint is out of limit."); + + bool hasCoin = sparkState.HasCoin(coin); + if (hasCoin) + break; + + if (sparkTxInfo != NULL && !sparkTxInfo->fInfoIsComplete) { + BOOST_FOREACH(const auto& mint, sparkTxInfo->mints) { + if (mint == coin) { + hasCoin = true; + break; + } + } + + // Update coin list in the info + sparkTxInfo->mints.push_back(coin); + sparkTxInfo->spTransactions.insert(hashTx); + } + + if (hasCoin && fStatefulSigmaCheck) { + LogPrintf("CheckSparkMintTransaction: double mint, tx=%s\n", hashTx.GetHex()); + return state.DoS(100, + false, + PUBCOIN_NOT_VALIDATE, + "CheckSparkMintTransaction: double mint"); + } + } + + return true; +} + +bool CheckSparkTransaction( + const CTransaction &tx, + CValidationState &state, + uint256 hashTx, + bool isVerifyDB, + int nHeight, + bool isCheckWallet, + bool fStatefulSigmaCheck, + CSparkTxInfo* sparkTxInfo) +{ + Consensus::Params const & consensus = ::Params().GetConsensus(); + + bool const allowSpark = IsSparkAllowed(); + + if (!isVerifyDB && !isCheckWallet) { + if (allowSpark && sparkState.IsSurgeConditionDetected()) { + return state.DoS(100, false, + REJECT_INVALID, + "Spark surge protection is ON."); + } + } + + // Check Spark Mint Transaction + if (allowSpark && !isVerifyDB) { + for (const CTxOut &txout : tx.vout) { + std::vector scripts; + if (!txout.scriptPubKey.empty() && txout.scriptPubKey.IsSparkMint()) { + scripts.push_back(txout.scriptPubKey); + } + if (!scripts.empty()) { + if (!CheckSparkMintTransaction(scripts, state, hashTx, fStatefulSigmaCheck, sparkTxInfo)) + return false; + } + } + } + + return true; +} + +bool GetOutPoint(COutPoint& outPoint, const spark::Coin coin) +{ // TODO levon, implement this function after state implementation } +/******************************************************************************/ +// CLelantusState +/******************************************************************************/ + +CSparkState::CSparkState( + size_t maxCoinInGroup, + size_t startGroupSize) + : + maxCoinInGroup(maxCoinInGroup), + startGroupSize(startGroupSize) +{ + Reset(); +} + +void CSparkState::Reset() { + coinGroups.clear(); + latestCoinId = 0; + mintedCoins.clear(); + usedLTags.clear(); + mintMetaInfo.clear(); + spendMetaInfo.clear(); + surgeCondition = false; +} + +bool CSparkState::IsSurgeConditionDetected() const { + return surgeCondition; +} + +bool CSparkState::HasCoin(const spark::Coin& coin) { + return mintedCoins.find(coin) != mintedCoins.end(); + +} + +bool CSparkState::GetCoinGroupInfo( + int group_id, + SparkCoinGroupInfo& result) { + if (coinGroups.count(group_id) == 0) + return false; + + result = coinGroups[group_id]; + return true; +} + +int CSparkState::GetLatestCoinID() const { + return latestCoinId; +} + +bool CSparkState::IsUsedLTag(const GroupElement& lTag) { + return usedLTags.count(lTag) != 0; +} + +bool CSparkState::IsUsedLTagHash(GroupElement& lTag, const uint256 &coinLTaglHash) { + for ( auto it = GetSpends().begin(); it != GetSpends().end(); ++it ) { + if (primitives::GetLTagHash(it->first) == coinLTaglHash) { + lTag = it->first; + return true; + } + } + return false; +} + + +bool CSparkState::CanAddSpendToMempool(const GroupElement& lTag) { + LOCK(mempool.cs); + return !IsUsedLTag(lTag) && !mempool.sparkState.HasLTag(lTag); +} + +bool CSparkState::CanAddMintToMempool(const spark::Coin& coin){ + LOCK(mempool.cs); + return !HasCoin(coin) && !mempool.sparkState.HasMint(coin); +} + +void CSparkState::AddMint(const spark::Coin& coin, const CMintedCoinInfo& coinInfo) { + mintedCoins.insert(std::make_pair(coin, coinInfo)); + mintMetaInfo[coinInfo.coinGroupId] += 1; +} + +void CSparkState::RemoveMint(const spark::Coin& coin) { + auto iter = mintedCoins.find(coin); + if (iter != mintedCoins.end()) { + mintMetaInfo[iter->second.coinGroupId] -= 1; + mintedCoins.erase(iter); + } +} + +void CSparkState::AddSpend(const GroupElement& lTag, int coinGroupId) { + if (!mintMetaInfo.count(coinGroupId)) { + throw std::invalid_argument("group id doesn't exist"); + } + + usedLTags[lTag] = coinGroupId; + spendMetaInfo[coinGroupId] += 1; +} + +void CSparkState::RemoveSpend(const GroupElement& lTag) { + auto iter = usedLTags.find(lTag); + if (iter != usedLTags.end()) { + spendMetaInfo[iter->second] -= 1; + usedLTags.erase(iter); + } +} + +std::unordered_map const & CSparkState::GetMints() const { + return mintedCoins; +} +std::unordered_map const & CSparkState::GetSpends() const { + return usedLTags; +} + +// CSparkMempoolState +bool CSparkMempoolState::HasMint(const spark::Coin& coin) { + return mempoolMints.count(coin) > 0; +} + +void CSparkMempoolState::AddMintToMempool(const spark::Coin& coin) { + mempoolMints.insert(coin); +} + +void CSparkMempoolState::RemoveMintFromMempool(const spark::Coin& coin) { + mempoolMints.erase(coin); +} + +bool CSparkMempoolState::HasLTag(const GroupElement& lTag) { + return mempoolLTags.count(lTag) > 0; +} + +bool CSparkMempoolState::AddSpendToMempool(const GroupElement& lTag, uint256 txHash) { + return mempoolLTags.insert({lTag, txHash}).second; +} + +void CSparkMempoolState::RemoveSpendFromMempool(const GroupElement& lTag) { + mempoolLTags.erase(lTag); +} + +uint256 CSparkMempoolState::GetMempoolConflictingTxHash(const GroupElement& lTag) { + if (mempoolLTags.count(lTag) == 0) + return uint256(); + + return mempoolLTags[lTag]; +} + +void CSparkMempoolState::Reset() { + mempoolLTags.clear(); + mempoolMints.clear(); +} + } // namespace spark \ No newline at end of file diff --git a/src/spark/state.h b/src/spark/state.h index 2abebbdbb1..ff87235f46 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -7,11 +7,168 @@ #include "libspark/coin.h" #include "chain.h" +#include "../libspark/mint_transaction.h" +#include "primitives.h" namespace spark { +// Spark transaction info, added to the CBlock to ensure spark mint/spend transactions got their info stored into index +class CSparkTxInfo { +public: + // all the spark transactions encountered so far + std::set spTransactions; + + // Vector of all mints + std::vector mints; + + // linking tag for every spend (map from lTag to coin group id) + std::unordered_map spentLTags; + + // information about transactions in the block is complete + bool fInfoIsComplete; + + CSparkTxInfo(): fInfoIsComplete(false) {} + + // finalize everything + void Complete(); +}; + +// check if spark activation block is passed +bool IsSparkAllowed(); +bool IsSparkAllowed(int height); + +// Pass Scripts form mint transaction and get spark MintTransaction object +void ParseSparkMintTransaction(const std::vector& scripts, MintTransaction& mintTransaction); + +bool CheckSparkMintTransaction( + const std::vector& scripts, + CValidationState &state, + uint256 hashTx, + bool fStatefulSigmaCheck, + CSparkTxInfo* sparkTxInfo); + +bool CheckSparkTransaction( + const CTransaction &tx, + CValidationState &state, + uint256 hashTx, + bool isVerifyDB, + int nHeight, + bool isCheckWallet, + bool fStatefulSigmaCheck, + CSparkTxInfo* sparkTxInfo); + bool GetOutPoint(COutPoint& outPoint, const spark::Coin coin); + +class CSparkMempoolState { +private: + // mints in the mempool + std::unordered_set mempoolMints; + + // linking tags of spends currently in the mempool mapped to tx hashes + std::unordered_map mempoolLTags; + +public: + // Check if there is a conflicting tx in the blockchain or mempool + bool HasMint(const spark::Coin& coin); + void AddMintToMempool(const spark::Coin& coin); + void RemoveMintFromMempool(const spark::Coin& coin); + + // Check if there is a conflicting tx in the blockchain or mempool + bool HasLTag(const GroupElement& lTag); + + // Add spend into the mempool. + bool AddSpendToMempool(const GroupElement& lTag, uint256 txHash); + + // Remove spend from the mempool (usually as the result of adding tx to the block) + void RemoveSpendFromMempool(const GroupElement& lTag); + + // Get conflicting tx hash by coin serial number + uint256 GetMempoolConflictingTxHash(const GroupElement& lTag); + + std::unordered_map const & GetMempoolLTags() const { return mempoolLTags; } + + void Reset(); +}; + +/* + * State of minted/spent coins as extracted from the index + */ +class CSparkState { +public: + // First and last block where mint with given id was seen + struct SparkCoinGroupInfo { + SparkCoinGroupInfo() : firstBlock(nullptr), lastBlock(nullptr), nCoins(0) {} + + // first and last blocks having coins with given id minted + CBlockIndex *firstBlock; + CBlockIndex *lastBlock; + // total number of minted coins with such parameters + int nCoins; + }; + +public: + CSparkState( + size_t maxCoinInGroup = ZC_LELANTUS_MAX_MINT_NUM, + size_t startGroupSize = ZC_LELANTUS_SET_START_SIZE); + + // Reset to initial values + void Reset(); + + // Query if the coin linking tag was previously used + bool IsUsedLTag(const GroupElement& lTag); + // Query if the hash of a linking tag was previously used. If so, store preimage in coinSerial param + bool IsUsedLTagHash(GroupElement& lTag, const uint256 &coinLTaglHash); + + // Query if there is a coin with given pubCoin value + bool HasCoin(const spark::Coin& coin); + + bool IsSurgeConditionDetected() const; + + // Query coin group with given id + bool GetCoinGroupInfo(int group_id, SparkCoinGroupInfo &result); + + int GetLatestCoinID() const; + + // Check if there is a conflicting tx in the blockchain or mempool + bool CanAddSpendToMempool(const GroupElement& lTag); + + bool CanAddMintToMempool(const spark::Coin& coin); + + void AddMint(const spark::Coin& coin, const CMintedCoinInfo& coinInfo); + void RemoveMint(const spark::Coin& coin); + + void AddSpend(const GroupElement& lTag, int coinGroupId); + void RemoveSpend(const GroupElement& lTag); + + std::unordered_map const & GetMints() const; + std::unordered_map const & GetSpends() const; + +private: + // Group Limit + size_t maxCoinInGroup; + size_t startGroupSize; + + // Latest anonymity set id; + int latestCoinId; + + // Collection of coin groups. Map from id to LelantusCoinGroupInfo structure + std::unordered_map coinGroups; + + std::atomic surgeCondition; + + // Set of all minted coins + std::unordered_map mintedCoins; + // Set of all used coin linking tags. + std::unordered_map usedLTags; + + typedef std::map metainfo_container_t; + metainfo_container_t extendedMintMetaInfo, mintMetaInfo, spendMetaInfo; + + void CheckSurgeCondition(); + +}; + } // namespace spark #endif //_MAIN_SPARK_STATE_H_ diff --git a/src/txmempool.h b/src/txmempool.h index 95ddf7f30d..c83ebb29a0 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -24,6 +24,7 @@ #include "netaddress.h" #include "bls/bls.h" #include "lelantus.h" +#include "spark/state.h" #include "evo/spork.h" @@ -521,6 +522,7 @@ class CTxMemPool const setEntries & GetMemPoolChildren(txiter entry) const; lelantus::CLelantusMempoolState lelantusState; + spark::CSparkMempoolState sparkState; private: typedef std::map cacheMap; diff --git a/src/validation.cpp b/src/validation.cpp index cad5d46a78..7b39d3379c 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -773,12 +773,6 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C const Consensus::Params& consensus = Params().GetConsensus(); - if (tx.IsSigmaMint() || tx.IsSigmaSpend()) { - if (consensus.nStartSigmaBlacklist != INT_MAX && chainActive.Height() < consensus.nRestartSigmaWithBlacklistCheck) - return state.DoS(100, error("Sigma is temporarily disabled"), - REJECT_INVALID, "bad-txns-zerocoin"); - } - bool startLelantusRejectSigma = (chainActive.Height() >= consensus.nLelantusStartBlock); if (startLelantusRejectSigma) { if(tx.IsSigmaMint() || tx.IsSigmaSpend()) { @@ -792,6 +786,26 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C } } + bool startSpark = (chainActive.Height() >= consensus.nSparkStartBlock); + if (startSpark) { + if (tx.IsLelantusMint() && !tx.IsLelantusJoinSplit()) { + return state.DoS(100, error("Lelantus mints no more allowed in mempool"), + REJECT_INVALID, "bad-txns-zerocoin"); + } + } else { + if(tx.IsSparkTransaction()) { + return state.DoS(100, error("Spark transactions are not allowed in mempool yet"), + REJECT_INVALID, "bad-txns-zerocoin"); + } + } + + if (chainActive.Height() >= consensus.nLelantusGracefulPeriod) { + if(tx.IsLelantusTransaction()) { + return state.DoS(100, error("Lelantus transactions are no more allowed into mempool"), + REJECT_INVALID, "bad-txns-zerocoin"); + } + } + // V3 sigma spends. sigma::CSigmaState *sigmaState = sigma::CSigmaState::GetState(); std::vector zcSpendSerialsV3; From 20c75daebfafe2ebc9c19c868bfffa40a510307a Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 27 Jun 2022 06:51:48 +0400 Subject: [PATCH 034/197] More state functionality implemented --- src/libspark/coin.cpp | 11 +++ src/libspark/coin.h | 4 ++ src/spark/primitives.cpp | 14 ++-- src/spark/primitives.h | 1 + src/spark/state.cpp | 151 +++++++++++++++++++++++++++++++++++---- src/spark/state.h | 41 +++++++---- src/validation.cpp | 6 ++ 7 files changed, 191 insertions(+), 37 deletions(-) diff --git a/src/libspark/coin.cpp b/src/libspark/coin.cpp index 892c8c9cb3..9bffd2c837 100644 --- a/src/libspark/coin.cpp +++ b/src/libspark/coin.cpp @@ -1,4 +1,5 @@ #include "coin.h" +#include "../hash.h" namespace spark { @@ -162,4 +163,14 @@ bool Coin::operator==(const Coin& other) const { return this->S == other.S; } +uint256 Coin::getHash() const { + CDataStream ss(SER_GETHASH, 0); + ss << "coin_hash"; + ss << S; + ss << K; + ss << C; + ss << r_; + return ::Hash(ss.begin(), ss.end()); +} + } diff --git a/src/libspark/coin.h b/src/libspark/coin.h index 1184a730f1..526e104e63 100644 --- a/src/libspark/coin.h +++ b/src/libspark/coin.h @@ -6,6 +6,7 @@ #include "params.h" #include "aead.h" #include "util.h" +#include "../uint256.h" namespace spark { @@ -85,6 +86,9 @@ class Coin { bool operator==(const Coin& other) const; + // type and v are not included in hash + uint256 getHash() const; + protected: bool validate(const IncomingViewKey& incoming_view_key, IdentifiedCoinData& data); diff --git a/src/spark/primitives.cpp b/src/spark/primitives.cpp index 20ee2ed0e4..8dd4fe9a3a 100644 --- a/src/spark/primitives.cpp +++ b/src/spark/primitives.cpp @@ -24,17 +24,16 @@ uint256 GetLTagHash(const secp_primitives::GroupElement& tag) { return Hash(ss.begin(), ss.end()); } +uint256 GetSparkCoinHash(const spark::Coin& coin) { + return coin.getHash(); +} } // namespace primitives namespace spark { std::size_t CoinHash::operator ()(const spark::Coin& coin) const noexcept { - CDataStream ss(SER_GETHASH, 0); - ss << "coin_hash"; - ss << coin; - - uint256 hash = ::Hash(ss.begin(), ss.end()); + uint256 hash = primitives::GetSparkCoinHash(coin); std::size_t result; std::memcpy(&result, hash.begin(), sizeof(std::size_t)); @@ -42,10 +41,7 @@ std::size_t CoinHash::operator ()(const spark::Coin& coin) const noexcept { } std::size_t CLTagHash::operator ()(const secp_primitives::GroupElement& tag) const noexcept { - CDataStream ss(SER_GETHASH, 0); - ss << "tag_hash"; - ss << tag; - uint256 hash = ::Hash(ss.begin(), ss.end()); + uint256 hash = primitives::GetLTagHash(tag); std::size_t result; std::memcpy(&result, hash.begin(), sizeof(std::size_t)); diff --git a/src/spark/primitives.h b/src/spark/primitives.h index ac57bb68c8..3a4ebe2e9a 100644 --- a/src/spark/primitives.h +++ b/src/spark/primitives.h @@ -43,6 +43,7 @@ struct CSparkMintMeta namespace primitives { uint256 GetNonceHash(const secp_primitives::Scalar& nonce); uint256 GetLTagHash(const secp_primitives::GroupElement& tag); + uint256 GetSparkCoinHash(const spark::Coin& coin); } namespace spark { diff --git a/src/spark/state.cpp b/src/spark/state.cpp index 1e8c3124db..7f7a9fa116 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -57,7 +57,6 @@ bool IsSparkAllowed(int height) void ParseSparkMintTransaction(const std::vector& scripts, MintTransaction& mintTransaction) { std::vector serializedCoins; - bool first = true; for (const auto& script : scripts) { if (!script.IsSparkMint()) throw std::invalid_argument("Script is not a Spark mint"); @@ -80,6 +79,22 @@ void ParseSparkMintTransaction(const std::vector& scripts, MintTransact mintTransaction.setMintTransaction(serializedCoins); } +void ParseSparkMintCoin(const CScript& script, spark::Coin& txCoin) +{ + if (!script.IsSparkMint() && !script.IsSparkSMint()) + throw std::invalid_argument("Script is not a Spark mint"); + + std::vector serialized(script.begin() + 1, script.end()); + CDataStream stream( + std::vector(serialized.begin(), serialized.end()), + SER_NETWORK, + PROTOCOL_VERSION + ); + + stream >> txCoin; +} + + bool CheckSparkMintTransaction( const std::vector& scripts, CValidationState &state, @@ -122,7 +137,7 @@ bool CheckSparkMintTransaction( break; if (sparkTxInfo != NULL && !sparkTxInfo->fInfoIsComplete) { - BOOST_FOREACH(const auto& mint, sparkTxInfo->mints) { + for (const auto& mint : sparkTxInfo->mints) { if (mint == coin) { hasCoin = true; break; @@ -160,14 +175,6 @@ bool CheckSparkTransaction( bool const allowSpark = IsSparkAllowed(); - if (!isVerifyDB && !isCheckWallet) { - if (allowSpark && sparkState.IsSurgeConditionDetected()) { - return state.DoS(100, false, - REJECT_INVALID, - "Spark surge protection is ON."); - } - } - // Check Spark Mint Transaction if (allowSpark && !isVerifyDB) { for (const CTxOut &txout : tx.vout) { @@ -185,11 +192,61 @@ bool CheckSparkTransaction( return true; } -bool GetOutPoint(COutPoint& outPoint, const spark::Coin coin) +bool GetOutPoint(COutPoint& outPoint, const spark::Coin& coin) { - // TODO levon, implement this function after state implementation + spark::CSparkState *sparkState = spark::CSparkState::GetState(); + auto mintedCoinHeightAndId = sparkState->GetMintedCoinHeightAndId(coin); + int mintHeight = mintedCoinHeightAndId.first; + int coinId = mintedCoinHeightAndId.second; + + if(mintHeight==-1 && coinId==-1) + return false; + + // get block containing mint + CBlockIndex *mintBlock = chainActive[mintHeight]; + CBlock block; + if(!ReadBlockFromDisk(block, mintBlock, ::Params().GetConsensus())) + LogPrintf("can't read block from disk.\n"); + + return GetOutPointFromBlock(outPoint, coin, block); } +bool GetOutPoint(COutPoint& outPoint, const uint256& coinHash) +{ + spark::Coin coin; + spark::CSparkState *sparkState = spark::CSparkState::GetState(); + if(!sparkState->HasCoinHash(coin, coinHash)) { + return false; + } + + return GetOutPoint(outPoint, coin); +} + +bool GetOutPointFromBlock(COutPoint& outPoint, const spark::Coin& coin, const CBlock &block) { + spark::Coin txCoin; + // cycle transaction hashes, looking for this coin + for (CTransactionRef tx : block.vtx){ + uint32_t nIndex = 0; + for (const CTxOut &txout : tx->vout) { + if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) { + try { + ParseSparkMintCoin(txout.scriptPubKey, txCoin); + } + catch (...) { + continue; + } + if(coin == txCoin){ + outPoint = COutPoint(tx->GetHash(), nIndex); + return true; + } + } + nIndex++; + } + } + return false; +} + + /******************************************************************************/ // CLelantusState /******************************************************************************/ @@ -211,11 +268,15 @@ void CSparkState::Reset() { usedLTags.clear(); mintMetaInfo.clear(); spendMetaInfo.clear(); - surgeCondition = false; } -bool CSparkState::IsSurgeConditionDetected() const { - return surgeCondition; +std::pair CSparkState::GetMintedCoinHeightAndId(const spark::Coin& coin) { + auto coinIt = mintedCoins.find(coin); + + if (coinIt != mintedCoins.end()) { + return std::make_pair(coinIt->second.nHeight, coinIt->second.coinGroupId); + } + return std::make_pair(-1, -1); } bool CSparkState::HasCoin(const spark::Coin& coin) { @@ -223,6 +284,17 @@ bool CSparkState::HasCoin(const spark::Coin& coin) { } +bool CSparkState::HasCoinHash(spark::Coin& coin, const uint256& coinHash) { + for (auto it = mintedCoins.begin(); it != mintedCoins.end(); ++it ){ + const spark::Coin& coin_ = (*it).first; + if(primitives::GetSparkCoinHash(coin_) == coinHash){ + coin = coin_; + return true; + } + } + return false; +} + bool CSparkState::GetCoinGroupInfo( int group_id, SparkCoinGroupInfo& result) { @@ -292,6 +364,46 @@ void CSparkState::RemoveSpend(const GroupElement& lTag) { } } +bool CSparkState::AddSpendToMempool(const std::vector& lTags, uint256 txHash) { + LOCK(mempool.cs); + for (const auto& lTag : lTags){ + if (IsUsedLTag(lTag) || mempool.sparkState.HasLTag(lTag)) + return false; + + mempool.sparkState.AddSpendToMempool(lTag, txHash); + } + + return true; +} + +void CSparkState::RemoveSpendFromMempool(const std::vector& lTags) { + LOCK(mempool.cs); + for (const auto& lTag : lTags) { + mempool.sparkState.RemoveSpendFromMempool(lTag); + } +} + +void CSparkState::AddMintsToMempool(const std::vector& coins) { + LOCK(mempool.cs); + for (const auto& coin : coins) { + mempool.sparkState.AddMintToMempool(coin); + } +} + +void CSparkState::RemoveMintFromMempool(const spark::Coin& coin) { + LOCK(mempool.cs); + mempool.sparkState.RemoveMintFromMempool(coin); +} + +uint256 CSparkState::GetMempoolConflictingTxHash(const GroupElement& lTag) { + LOCK(mempool.cs); + return mempool.sparkState.GetMempoolConflictingTxHash(lTag); +} + +CSparkState* CSparkState::GetState() { + return &sparkState; +} + std::unordered_map const & CSparkState::GetMints() const { return mintedCoins; } @@ -299,6 +411,15 @@ std::unordered_map const & CSparkState::Get return usedLTags; } +std::unordered_map const& CSparkState::GetCoinGroups() const { + return coinGroups; +} + +std::unordered_map const& CSparkState::GetMempoolLTags() const { + LOCK(mempool.cs); + return mempool.sparkState.GetMempoolLTags(); +} + // CSparkMempoolState bool CSparkMempoolState::HasMint(const spark::Coin& coin) { return mempoolMints.count(coin) > 0; diff --git a/src/spark/state.h b/src/spark/state.h index ff87235f46..9766ba8b99 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -39,13 +39,8 @@ bool IsSparkAllowed(int height); // Pass Scripts form mint transaction and get spark MintTransaction object void ParseSparkMintTransaction(const std::vector& scripts, MintTransaction& mintTransaction); +void ParseSparkMintCoin(const CScript& script, spark::Coin& txCoin); -bool CheckSparkMintTransaction( - const std::vector& scripts, - CValidationState &state, - uint256 hashTx, - bool fStatefulSigmaCheck, - CSparkTxInfo* sparkTxInfo); bool CheckSparkTransaction( const CTransaction &tx, @@ -57,7 +52,9 @@ bool CheckSparkTransaction( bool fStatefulSigmaCheck, CSparkTxInfo* sparkTxInfo); -bool GetOutPoint(COutPoint& outPoint, const spark::Coin coin); +bool GetOutPoint(COutPoint& outPoint, const spark::Coin& coin); +bool GetOutPoint(COutPoint& outPoint, const uint256& coinHash); +bool GetOutPointFromBlock(COutPoint& outPoint, const spark::Coin& coin, const CBlock &block); class CSparkMempoolState { @@ -120,10 +117,14 @@ class CSparkState { // Query if the hash of a linking tag was previously used. If so, store preimage in coinSerial param bool IsUsedLTagHash(GroupElement& lTag, const uint256 &coinLTaglHash); + // Return height of mint transaction and id of minted coin + std::pair GetMintedCoinHeightAndId(const spark::Coin& coin); + // Query if there is a coin with given pubCoin value bool HasCoin(const spark::Coin& coin); - bool IsSurgeConditionDetected() const; + // Query if there is a coin with given hash of a coin value. + bool HasCoinHash(spark::Coin& coin, const uint256& coinHash); // Query coin group with given id bool GetCoinGroupInfo(int group_id, SparkCoinGroupInfo &result); @@ -141,8 +142,27 @@ class CSparkState { void AddSpend(const GroupElement& lTag, int coinGroupId); void RemoveSpend(const GroupElement& lTag); + // Add spend into the mempool. + // Check if there is a coin with such serial in either blockchain or mempool + bool AddSpendToMempool(const std::vector& lTags, uint256 txHash); + + void AddMintsToMempool(const std::vector& coins); + void RemoveMintFromMempool(const spark::Coin& coin); + + // Get conflicting tx hash by coin linking tag + uint256 GetMempoolConflictingTxHash(const GroupElement& lTag); + + // Remove spend from the mempool (usually as the result of adding tx to the block) + void RemoveSpendFromMempool(const std::vector& lTags); + std::unordered_map const & GetMints() const; std::unordered_map const & GetSpends() const; + std::unordered_map const & GetCoinGroups() const; + std::unordered_map const & GetMempoolLTags() const; + + static CSparkState* GetState(); + + std::size_t GetTotalCoins() const { return mintedCoins.size(); } private: // Group Limit @@ -155,8 +175,6 @@ class CSparkState { // Collection of coin groups. Map from id to LelantusCoinGroupInfo structure std::unordered_map coinGroups; - std::atomic surgeCondition; - // Set of all minted coins std::unordered_map mintedCoins; // Set of all used coin linking tags. @@ -164,9 +182,6 @@ class CSparkState { typedef std::map metainfo_container_t; metainfo_container_t extendedMintMetaInfo, mintMetaInfo, spendMetaInfo; - - void CheckSurgeCondition(); - }; } // namespace spark diff --git a/src/validation.cpp b/src/validation.cpp index 7b39d3379c..fe6f8cb2d1 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -816,6 +816,12 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C std::vector lelantusSpendSerials; std::vector lelantusMintPubcoins; std::vector lelantusAmounts; + + // Spark + spark::CSparkState *sparkState = spark::CSparkState::GetState(); + std::vector sparkMintCoins; + std::vector sparkUsedLTags; + { LOCK(pool.cs); if (tx.IsSigmaSpend()) { From 9c3ade8124a0efb2cb93bbca3d1266b9d2421b0f Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Wed, 6 Jul 2022 17:35:21 +0400 Subject: [PATCH 035/197] Adding Spark state into mempool --- src/evo/specialtx.cpp | 3 + src/libspark/keys.cpp | 1 + src/libspark/spend_transaction.cpp | 14 +++++ src/libspark/spend_transaction.h | 6 ++ src/spark/state.cpp | 26 +++++++++ src/spark/state.h | 6 ++ src/spark/wallet.cpp | 20 ++++--- src/spark/wallet.h | 7 ++- src/validation.cpp | 94 +++++++++++++++++++++++++++--- src/wallet/walletdb.cpp | 22 +++---- src/wallet/walletdb.h | 8 +-- 11 files changed, 175 insertions(+), 32 deletions(-) diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp index 35e044b37b..456403450d 100644 --- a/src/evo/specialtx.cpp +++ b/src/evo/specialtx.cpp @@ -42,6 +42,9 @@ bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVali return llmq::CheckLLMQCommitment(tx, pindexPrev, state); case TRANSACTION_SPORK: return CheckSporkTx(tx, pindexPrev, state); + case TRANSACTION_SPARK: + // spark transaction checks are done in other places + return true; case TRANSACTION_LELANTUS: // lelantus transaction checks are done in other places return true; diff --git a/src/libspark/keys.cpp b/src/libspark/keys.cpp index d1398ed2be..ddbf2c2266 100644 --- a/src/libspark/keys.cpp +++ b/src/libspark/keys.cpp @@ -17,6 +17,7 @@ SpendKey::SpendKey(const Params* params, const Scalar& r_) { this->params = params; this->r = r_; std::vector data; + data.resize(32); r.serialize(data.data()); std::vector result(CSHA256().OUTPUT_SIZE); diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index 6d8fde1066..824b41f7b5 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -2,6 +2,11 @@ namespace spark { +SpendTransaction::SpendTransaction( + const Params* params) { + this->params = params; +} + SpendTransaction::SpendTransaction( const Params* params, const FullViewKey& full_view_key, @@ -193,6 +198,15 @@ SpendTransaction::SpendTransaction( ); } +uint64_t SpendTransaction::getFee() { + return f; +} + +std::vector& SpendTransaction::getUsedLTags() { + return T; +} + + bool SpendTransaction::verify() { // Size parameters const std::size_t w = this->grootle_proofs.size(); diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h index fd515576d0..067dc76d61 100644 --- a/src/libspark/spend_transaction.h +++ b/src/libspark/spend_transaction.h @@ -28,6 +28,9 @@ struct OutputCoinData { class SpendTransaction { public: + SpendTransaction( + const Params* params); + SpendTransaction( const Params* params, const FullViewKey& full_view_key, @@ -39,6 +42,9 @@ class SpendTransaction { const std::vector& outputs ); bool verify(); + + uint64_t getFee(); + std::vector& getUsedLTags(); static Scalar hash_bind( const std::vector>& roots, diff --git a/src/spark/state.cpp b/src/spark/state.cpp index 7f7a9fa116..3f7c1e6fdd 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -94,6 +94,32 @@ void ParseSparkMintCoin(const CScript& script, spark::Coin& txCoin) stream >> txCoin; } +spark::SpendTransaction ParseSparkSpend(const CTransaction &tx) +{ + //TODO levon implement this after spark spend implementation + +} + + +std::vector GetSparkUsedTags(const CTransaction &tx) +{ + const spark::Params* params = spark::Params::get_default(); + + spark::SpendTransaction spendTransaction(params); + try { + spendTransaction = ParseSparkSpend(tx); + } catch (...) { + return std::vector(); + } + + return spendTransaction.getUsedLTags(); +} + +std::vector& GetSparkMintCoins(const CTransaction &tx) +{ + //TODO levon implement this after spark spend implementation +} + bool CheckSparkMintTransaction( const std::vector& scripts, diff --git a/src/spark/state.h b/src/spark/state.h index 9766ba8b99..2ac5286d34 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -8,6 +8,7 @@ #include "libspark/coin.h" #include "chain.h" #include "../libspark/mint_transaction.h" +#include "../libspark/spend_transaction.h" #include "primitives.h" namespace spark { @@ -41,6 +42,11 @@ bool IsSparkAllowed(int height); void ParseSparkMintTransaction(const std::vector& scripts, MintTransaction& mintTransaction); void ParseSparkMintCoin(const CScript& script, spark::Coin& txCoin); +spark::SpendTransaction ParseSparkSpend(const CTransaction &tx); + +std::vector GetSparkUsedTags(const CTransaction &tx); +std::vector& GetSparkMintCoins(const CTransaction &tx); + bool CheckSparkTransaction( const CTransaction &tx, diff --git a/src/spark/wallet.cpp b/src/spark/wallet.cpp index 2706dab20b..c8eabedcc1 100644 --- a/src/spark/wallet.cpp +++ b/src/spark/wallet.cpp @@ -46,10 +46,7 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { } // get the list of coin metadata from db - std::list listMints = walletdb.ListSparkMints(); - for (const auto& itr : listMints) { - coinMeta[itr.GetNonceHash()] = itr; - } + coinMeta = walletdb.ListSparkMints(); } } @@ -203,13 +200,13 @@ void CSparkWallet::eraseMint(const uint256& hash, CWalletDB& walletdb) { walletdb.EraseSparkMint(hash); coinMeta.erase(hash); } -void CSparkWallet::addOrUpdate(const CSparkMintMeta& mint, CWalletDB& walletdb) { +void CSparkWallet::addOrUpdate(const CSparkMintMeta& mint, const uint256& lTagHash, CWalletDB& walletdb) { if (mint.i > lastDiversifier) { lastDiversifier = mint.i; walletdb.writeDiversifier(lastDiversifier); } - coinMeta[mint.GetNonceHash()] = mint; - walletdb.WriteSparkMint(mint.GetNonceHash(), mint); + coinMeta[lTagHash] = mint; + walletdb.WriteSparkMint(lTagHash, mint); } CSparkMintMeta CSparkWallet::getMintMeta(const uint256& hash) { @@ -218,6 +215,15 @@ CSparkMintMeta CSparkWallet::getMintMeta(const uint256& hash) { return CSparkMintMeta(); } +void CSparkWallet::UpdateSpendStateFromMempool(const std::vector& lTags) { + //TODO levon +} + +void CSparkWallet::UpdateMintStateFromMempool(const std::vector& coins) { + //TODO levon +} + + std::vector CSparkWallet::listAddressCoins(const int32_t& i, bool fUnusedOnly) { std::vector listMints; diff --git a/src/spark/wallet.h b/src/spark/wallet.h index ccf8502c3b..d235affa85 100644 --- a/src/spark/wallet.h +++ b/src/spark/wallet.h @@ -54,9 +54,12 @@ class CSparkWallet { // erase mint meta data from memory and from db void eraseMint(const uint256& hash, CWalletDB& walletdb); // add mint meta data to memory and to db - void addOrUpdate(const CSparkMintMeta& mint, CWalletDB& walletdb); + void addOrUpdate(const CSparkMintMeta& mint, const uint256& lTagHash, CWalletDB& walletdb); CSparkMintMeta getMintMeta(const uint256& hash); + void UpdateSpendStateFromMempool(const std::vector& lTags); + void UpdateMintStateFromMempool(const std::vector& coins); + // get the vector of mint metadata for a single address std::vector listAddressCoins(const int32_t& i, bool fUnusedOnly = false); @@ -91,7 +94,7 @@ class CSparkWallet { // map diversifier to address. std::unordered_map addresses; - // map nonceHash to coin meta + // map lTagHash to coin meta std::unordered_map coinMeta; }; diff --git a/src/validation.cpp b/src/validation.cpp index fe6f8cb2d1..a122a1e615 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -703,7 +703,8 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, tx.nType != TRANSACTION_COINBASE && tx.nType != TRANSACTION_QUORUM_COMMITMENT && tx.nType != TRANSACTION_SPORK && - tx.nType != TRANSACTION_LELANTUS) { + tx.nType != TRANSACTION_LELANTUS && + tx.nType != TRANSACTION_SPARK) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-type"); } if (tx.IsCoinBase() && tx.nType != TRANSACTION_COINBASE) @@ -713,6 +714,8 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, return state.DoS(100, false, REJECT_INVALID, "bad-txns-type"); if (tx.nType == TRANSACTION_LELANTUS && nHeight < consensusParams.nLelantusV3PayloadStartBlock) return state.DoS(100, false, REJECT_INVALID, "bad-txns-type"); + if (tx.nType == TRANSACTION_SPARK && nHeight < consensusParams.nSparkStartBlock) + return state.DoS(100, false, REJECT_INVALID, "bad-txns-type"); } else if (tx.nType != TRANSACTION_NORMAL) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-type"); @@ -886,6 +889,25 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C } lelantusSpendSerials.push_back(serials[i]); } + } else if (tx.IsSparkSpend()) { + if (tx.vin.size() > 1) { + return state.Invalid(false, REJECT_CONFLICT, "txn-invalid-spark-spend"); + } + + try { + sparkUsedLTags = spark::GetSparkUsedTags(tx); + } + catch (...) { + return state.Invalid(false, REJECT_CONFLICT, "failed to deserialize spark spend"); + } + + for (const auto& lTag : sparkUsedLTags) { + if (sparkState->IsUsedLTag(lTag) || pool.sparkState.HasLTag(lTag)) { + LogPrintf("AcceptToMemoryPool(): spark linking tag %s has been used\n", + lTag.tostring()); + return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict"); + } + } } BOOST_FOREACH(const CTxOut &txout, tx.vout) @@ -918,6 +940,22 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C lelantusMintPubcoins.push_back(pubCoinValue); } } + + if (tx.IsSparkSpend() || tx.IsSparkMint()) { + try { + sparkMintCoins = spark::GetSparkMintCoins(tx); + } + catch (...) { + return state.Invalid(false, REJECT_CONFLICT, "failed to deserialize spark mint"); + } + + for (const auto& coin : sparkMintCoins) { + if (sparkState->HasCoin(coin) || pool.sparkState.HasMint(coin)) { + LogPrintf("AcceptToMemoryPool(): Spark mint with the same value %s is already in the mempool\n", coin.getHash().GetHex()); + return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict"); + } + } + } } if (!CheckTransaction(tx, state, true, hash, false, INT_MAX, isCheckWalletTransaction)) { @@ -979,7 +1017,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C std::set setConflicts; { LOCK(pool.cs); // protect pool.mapNextTx - if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit()) { + if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { BOOST_FOREACH(const CTxIn &txin, tx.vin) { auto itConflicting = pool.mapNextTx.find(txin.prevout); @@ -1045,7 +1083,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C } } - if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit()) { + if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { // do all inputs exist? BOOST_FOREACH(const CTxIn txin, tx.vin) { if (!pcoinsTip->HaveCoinInCache(txin.prevout)) { @@ -1094,9 +1132,9 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C CAmount nValueOut = tx.GetValueOut(); CAmount nFees; - if (!tx.IsLelantusJoinSplit()) { + if (!tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { nFees = nValueIn - nValueOut; - } else { + } else if (tx.IsLelantusJoinSplit()) { try { nFees = lelantus::ParseLelantusJoinSplit(tx)->getFee(); } @@ -1106,6 +1144,16 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C catch (...) { return state.DoS(0, false, REJECT_INVALID, "failed to deserialize joinsplit"); } + } else { + try { + nFees = spark::ParseSparkSpend(tx).getFee(); + } + catch (CBadTxIn&) { + return state.DoS(0, false, REJECT_INVALID, "unable to parse joinsplit"); + } + catch (...) { + return state.DoS(0, false, REJECT_INVALID, "failed to deserialize joinsplit"); + } } // nModifiedFees includes any fee deltas from PrioritiseTransaction CAmount nModifiedFees = nFees; @@ -1114,7 +1162,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C CAmount inChainInputValue = 0; bool fSpendsCoinbase = false; - if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit()) { + if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { // Keep track of transactions that spend a coinbase, which we re-scan // during reorgs to ensure COINBASE_MATURITY is still met. BOOST_FOREACH(const CTxIn &txin, tx.vin) { @@ -1461,12 +1509,30 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C #endif } + + if (tx.IsSparkSpend()) { + if(markFiroSpendTransactionSerial) { + LogPrintf("Adding spends to mempool state..\n"); + for (const auto &usedLTag: sparkUsedLTags) + pool.sparkState.AddSpendToMempool(usedLTag, hash); + } + +#ifdef ENABLE_WALLET + if (!GetBoolArg("-disablewallet", false) && pwalletMain->sparkWallet) { + LogPrintf("Adding spends to wallet from Mempool..\n"); + pwalletMain->sparkWallet->UpdateSpendStateFromMempool(sparkUsedLTags); + } +#endif + } + if(markFiroSpendTransactionSerial) { sigmaState->AddMintsToMempool(zcMintPubcoinsV3); for (const auto &pubCoin: lelantusMintPubcoins) pool.lelantusState.AddMintToMempool(pubCoin); - } + // Add spark mints to mempool + sparkState->AddMintsToMempool(sparkMintCoins); + } #ifdef ENABLE_WALLET if(tx.IsSigmaMint() && !GetBoolArg("-disablewallet", false) && pwalletMain->zwallet) { @@ -1474,6 +1540,11 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C pwalletMain->zwallet->GetTracker().UpdateMintStateFromMempool(zcMintPubcoinsV3); } + if(tx.IsSparkMint() && !GetBoolArg("-disablewallet", false) && pwalletMain->sparkWallet) { + LogPrintf("Adding Spark mints to Mempool..\n"); + pwalletMain->sparkWallet->UpdateMintStateFromMempool(sparkMintCoins); + } + if(tx.IsLelantusMint() && !GetBoolArg("-disablewallet", false) && pwalletMain->zwallet) { LogPrintf("Updating mint state from Mempool..\n"); BOOST_FOREACH(const CTxOut &txout, tx.vout) @@ -2141,7 +2212,7 @@ bool CheckZerocoinFoundersInputs(const CTransaction &tx, CValidationState &state bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, PrecomputedTransactionData& txdata, std::vector *pvChecks) { - if (!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit()) + if (!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit() &&!tx.IsSparkSpend()) { if (!Consensus::CheckTxInputs(tx, state, inputs, GetSpendHeight(inputs))) return false; @@ -2231,6 +2302,13 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi REJECT_MALFORMED, " Can't mix Lelantus joinsplit input with regular ones or have more than one input"); } + } else if (tx.IsSparkSpend()) { + if(tx.vin.size() > 1) { + return state.DoS( + 100, false, + REJECT_MALFORMED, + " Can't mix Lelantus joinsplit input with regular ones or have more than one input"); + } } return true; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index cdf4b5b5db..6a337034b6 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1704,9 +1704,9 @@ unsigned int CWalletDB::GetUpdateCounter() return nWalletDBUpdateCounter; } -std::list CWalletDB::ListSparkMints() +std::unordered_map CWalletDB::ListSparkMints() { - std::list listMints; + std::unordered_map listMints; Dbc* pcursor = GetCursor(); if (!pcursor) throw std::runtime_error(std::string(__func__)+" : cannot create DB cursor"); @@ -1735,32 +1735,32 @@ std::list CWalletDB::ListSparkMints() if (strType != mintName) break; - uint256 nonceHash; - ssKey >> nonceHash; + uint256 lTagHash; + ssKey >> lTagHash; CSparkMintMeta mint; ssValue >> mint; - listMints.emplace_back(mint); + listMints[lTagHash] = mint; } pcursor->close(); return listMints; } -bool CWalletDB::WriteSparkMint(const uint256& nonceHash, const CSparkMintMeta& mint) +bool CWalletDB::WriteSparkMint(const uint256& lTagHash, const CSparkMintMeta& mint) { - return Write(std::make_pair(std::string("sparkMint"), nonceHash), mint); + return Write(std::make_pair(std::string("sparkMint"), lTagHash), mint); } -bool CWalletDB::ReadSparkMint(const uint256& nonceHash, CSparkMintMeta& mint) +bool CWalletDB::ReadSparkMint(const uint256& lTagHash, CSparkMintMeta& mint) { - return Read(std::make_pair(std::string("sparkMint"), nonceHash), mint); + return Read(std::make_pair(std::string("sparkMint"), lTagHash), mint); } -bool CWalletDB::EraseSparkMint(const uint256& nonceHash) +bool CWalletDB::EraseSparkMint(const uint256& lTagHash) { - return Erase(std::make_pair(std::string("sparkMint"), nonceHash)); + return Erase(std::make_pair(std::string("sparkMint"), lTagHash)); } diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index d368671e34..8e7ffbfc1b 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -303,10 +303,10 @@ class CWalletDB : public CDB bool ReadMintPoolPair(const uint256& hashPubcoin, uint160& hashSeedMaster, CKeyID& seedId, int32_t& nCount); std::vector> ListMintPool(); - std::list ListSparkMints(); - bool WriteSparkMint(const uint256& nonceHash, const CSparkMintMeta& mint); - bool ReadSparkMint(const uint256& nonceHash, CSparkMintMeta& mint); - bool EraseSparkMint(const uint256& nonceHash); + std::unordered_map ListSparkMints(); + bool WriteSparkMint(const uint256& lTagHash, const CSparkMintMeta& mint); + bool ReadSparkMint(const uint256& lTagHash, CSparkMintMeta& mint); + bool EraseSparkMint(const uint256& lTagHash); //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain); From 3b01ebcd4aab5cedfa2227a5f95d7546ca5710f9 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 29 Aug 2022 04:24:57 +0400 Subject: [PATCH 036/197] More spark state functionality implemented --- src/libspark/keys.cpp | 3 + src/libspark/keys.h | 18 +++--- src/spark/primitives.h | 29 ++++++++++ src/spark/state.cpp | 6 +- src/spark/state.h | 4 +- src/spark/wallet.cpp | 118 +++++++++++++++++++++++++++++++++++--- src/spark/wallet.h | 19 ++++-- src/test/test_bitcoin.cpp | 2 + src/validation.cpp | 10 ++-- src/wallet/wallet.cpp | 15 +++++ src/wallet/wallet.h | 6 ++ src/wallet/walletdb.cpp | 59 +++++++++++++++++-- src/wallet/walletdb.h | 9 ++- 13 files changed, 261 insertions(+), 37 deletions(-) diff --git a/src/libspark/keys.cpp b/src/libspark/keys.cpp index ddbf2c2266..29c361f89d 100644 --- a/src/libspark/keys.cpp +++ b/src/libspark/keys.cpp @@ -57,6 +57,9 @@ const Scalar& SpendKey::get_r() const { } FullViewKey::FullViewKey() {} +FullViewKey::FullViewKey(const Params* params) { + this->params = params; +} FullViewKey::FullViewKey(const SpendKey& spend_key) { this->params = spend_key.get_params(); this->s1 = spend_key.get_s1(); diff --git a/src/libspark/keys.h b/src/libspark/keys.h index c0c3b37147..eae985c063 100644 --- a/src/libspark/keys.h +++ b/src/libspark/keys.h @@ -28,6 +28,7 @@ class SpendKey { class FullViewKey { public: FullViewKey(); + FullViewKey(const Params* params); FullViewKey(const SpendKey& spend_key); const Params* get_params() const; const Scalar& get_s1() const; @@ -35,6 +36,16 @@ class FullViewKey { const GroupElement& get_D() const; const GroupElement& get_P2() const; + + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(s1); + READWRITE(s2); + READWRITE(D); + READWRITE(P2); + } + private: const Params* params; Scalar s1, s2; @@ -51,13 +62,6 @@ class IncomingViewKey { const GroupElement& get_P2() const; uint64_t get_diversifier(const std::vector& d) const; - ADD_SERIALIZE_METHODS; - template - inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(s1); - READWRITE(P2); - } - private: const Params* params; Scalar s1; diff --git a/src/spark/primitives.h b/src/spark/primitives.h index 3a4ebe2e9a..27b739c0eb 100644 --- a/src/spark/primitives.h +++ b/src/spark/primitives.h @@ -39,6 +39,35 @@ struct CSparkMintMeta }; }; +class CSparkSpendEntry +{ +public: + GroupElement lTag; + uint256 lTagHash; + uint256 hashTx; + int64_t amount; + + CSparkSpendEntry() + { + SetNull(); + } + + void SetNull() + { + lTag = GroupElement(); + lTagHash = uint256(); + amount = 0; + } + + ADD_SERIALIZE_METHODS; + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(lTag); + READWRITE(lTagHash); + READWRITE(hashTx); + READWRITE(amount); + } +}; namespace primitives { uint256 GetNonceHash(const secp_primitives::Scalar& nonce); diff --git a/src/spark/state.cpp b/src/spark/state.cpp index 3f7c1e6fdd..03d4982698 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -115,7 +115,7 @@ std::vector GetSparkUsedTags(const CTransaction &tx) return spendTransaction.getUsedLTags(); } -std::vector& GetSparkMintCoins(const CTransaction &tx) +std::vector>> GetSparkMintCoins(const CTransaction &tx) { //TODO levon implement this after spark spend implementation } @@ -409,10 +409,10 @@ void CSparkState::RemoveSpendFromMempool(const std::vector& lTags) } } -void CSparkState::AddMintsToMempool(const std::vector& coins) { +void CSparkState::AddMintsToMempool(const std::vector>>& coins) { LOCK(mempool.cs); for (const auto& coin : coins) { - mempool.sparkState.AddMintToMempool(coin); + mempool.sparkState.AddMintToMempool(coin.first); } } diff --git a/src/spark/state.h b/src/spark/state.h index 2ac5286d34..338218e503 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -45,7 +45,7 @@ void ParseSparkMintCoin(const CScript& script, spark::Coin& txCoin); spark::SpendTransaction ParseSparkSpend(const CTransaction &tx); std::vector GetSparkUsedTags(const CTransaction &tx); -std::vector& GetSparkMintCoins(const CTransaction &tx); +std::vector>> GetSparkMintCoins(const CTransaction &tx); bool CheckSparkTransaction( @@ -152,7 +152,7 @@ class CSparkState { // Check if there is a coin with such serial in either blockchain or mempool bool AddSpendToMempool(const std::vector& lTags, uint256 txHash); - void AddMintsToMempool(const std::vector& coins); + void AddMintsToMempool(const std::vector>>& coins); void RemoveMintFromMempool(const spark::Coin& coin); // Get conflicting tx hash by coin linking tag diff --git a/src/spark/wallet.cpp b/src/spark/wallet.cpp index c8eabedcc1..5b20d2bb2b 100644 --- a/src/spark/wallet.cpp +++ b/src/spark/wallet.cpp @@ -7,34 +7,37 @@ #include "../script/sign.h" #include "state.h" +#include + const uint32_t DEFAULT_SPARK_NCOUNT = 1; CSparkWallet::CSparkWallet(const std::string& strWalletFile) { CWalletDB walletdb(strWalletFile); - + this->strWalletFile = strWalletFile; const spark::Params* params = spark::Params::get_default(); viewKey = spark::IncomingViewKey(params); // try to get incoming view key from db, if it fails, that means it is first start - if (!walletdb.readIncomingViewKey(viewKey)) { + if (!walletdb.readFullViewKey(fullViewKey)) { if (pwalletMain->IsLocked()) { LogPrintf("Spark wallet creation FAILED, wallet is locked\n"); return; } // Generating spark key set first time spark::SpendKey spendKey = generateSpendKey(); - spark::FullViewKey fullViewKey = generateFullViewKey(spendKey); + fullViewKey = generateFullViewKey(spendKey); viewKey = generateIncomingViewKey(fullViewKey); // Write incoming view key into db, it is safe to be kept in db, it is used to identify incoming coins belonging to the wallet - walletdb.writeIncomingViewKey(viewKey); + walletdb.writeFullViewKey(fullViewKey); // generate one initial address for wallet lastDiversifier = 0; addresses[lastDiversifier] = getDefaultAddress(); // set 0 as last diversifier into db, we will update it later, in case coin comes, or user manually generates new address walletdb.writeDiversifier(lastDiversifier); } else { + viewKey = generateIncomingViewKey(fullViewKey); int32_t diversifierInDB = 0; // read diversifier from db walletdb.readDiversifier(diversifierInDB); @@ -200,7 +203,7 @@ void CSparkWallet::eraseMint(const uint256& hash, CWalletDB& walletdb) { walletdb.EraseSparkMint(hash); coinMeta.erase(hash); } -void CSparkWallet::addOrUpdate(const CSparkMintMeta& mint, const uint256& lTagHash, CWalletDB& walletdb) { +void CSparkWallet::addOrUpdateMint(const CSparkMintMeta& mint, const uint256& lTagHash, CWalletDB& walletdb) { if (mint.i > lastDiversifier) { lastDiversifier = mint.i; walletdb.writeDiversifier(lastDiversifier); @@ -215,12 +218,66 @@ CSparkMintMeta CSparkWallet::getMintMeta(const uint256& hash) { return CSparkMintMeta(); } -void CSparkWallet::UpdateSpendStateFromMempool(const std::vector& lTags) { - //TODO levon +void CSparkWallet::UpdateSpendStateFromMempool(const std::vector& lTags, const uint256& txHash, bool fUpdateMint) { + for (const auto& lTag : lTags) { + uint256 lTagHash = primitives::GetLTagHash(lTag); + if (coinMeta.count(lTagHash)) { + auto mintMeta = coinMeta[lTagHash]; + + CSparkSpendEntry spendEntry; + spendEntry.lTag = lTag; + spendEntry.lTagHash = lTagHash; + spendEntry.hashTx = txHash; + spendEntry.amount = mintMeta.v; + + CWalletDB walletdb(strWalletFile); + walletdb.WriteSparkSpendEntry(spendEntry); + + if (fUpdateMint) { + mintMeta.isUsed = true; + addOrUpdateMint(mintMeta, lTagHash, walletdb); + } + } + } } -void CSparkWallet::UpdateMintStateFromMempool(const std::vector& coins) { - //TODO levon +void CSparkWallet::UpdateMintStateFromMempool(const std::vector>>& coins, const uint256& txHash) { + for (auto coin : coins) { + try { + spark::IdentifiedCoinData identifiedCoinData = coin.first.identify(this->viewKey); + spark::RecoveredCoinData recoveredCoinData = coin.first.recover(this->fullViewKey, identifiedCoinData); + CSparkMintMeta mintMeta; + mintMeta.nHeight = -1; + mintMeta.nId = -1; + mintMeta.isUsed = false; + mintMeta.txid = txHash; + mintMeta.i = identifiedCoinData.i; + mintMeta.d = identifiedCoinData.d; + mintMeta.v = identifiedCoinData.v; + mintMeta.k = identifiedCoinData.k; + mintMeta.memo = identifiedCoinData.memo; + mintMeta.serial_context = coin.second; + //! Check whether this mint has been spent and is considered 'pending' or 'confirmed' + { + LOCK(mempool.cs); + mintMeta.isUsed = mempool.sparkState.HasLTag(recoveredCoinData.T); + } + + CWalletDB walletdb(strWalletFile); + uint256 lTagHash = primitives::GetLTagHash(recoveredCoinData.T); + addOrUpdateMint(mintMeta, lTagHash, walletdb); + + if (mintMeta.isUsed) { + LOCK(mempool.cs); + uint256 spendTxHash = mempool.sparkState.GetMempoolConflictingTxHash(recoveredCoinData.T); + std::vector lTags; + lTags.push_back(recoveredCoinData.T); + UpdateSpendStateFromMempool(lTags, spendTxHash, false); + } + } catch (const std::runtime_error& e) { + continue; + } + } } @@ -698,6 +755,49 @@ bool CSparkWallet::CreateSparkMintTransactions( return true; } +std::vector CSparkWallet::CreateSparkSpendTransaction( + const std::vector& recipients, + const std::vector& privateRecipients, + CAmount &fee, + const CCoinControl *coinControl) { + + if (recipients.empty() && privateRecipients.empty()) { + throw std::runtime_error(_("Either recipients or newMints has to be nonempty.")); + } + + // calculate total value to spend + CAmount vOut = 0; + CAmount mintVOut = 0; + unsigned recipientsToSubtractFee = 0; + + for (size_t i = 0; i < recipients.size(); i++) { + auto& recipient = recipients[i]; + + if (!MoneyRange(recipient.nAmount)) { + throw std::runtime_error(boost::str(boost::format(_("Recipient has invalid amount")) % i)); + } + + vOut += recipient.nAmount; + + if (recipient.fSubtractFeeFromAmount) { + recipientsToSubtractFee++; + } + } + + for(const auto& privRecipient : privateRecipients) { + mintVOut += privRecipient.v; + } + + std::vector result; + CWalletTx wtxNew; + CMutableTransaction tx; + wtxNew.fTimeReceivedIsTxTime = true; + wtxNew.BindWallet(pwalletMain); + + + return result; +} + std::list> CSparkWallet::GetAvailableSparkCoins(CWalletDB& walletdb, const CCoinControl *coinControl) const { std::list> coins; // get all unsued coins from spark wallet diff --git a/src/spark/wallet.h b/src/spark/wallet.h index d235affa85..1eb6216852 100644 --- a/src/spark/wallet.h +++ b/src/spark/wallet.h @@ -54,11 +54,11 @@ class CSparkWallet { // erase mint meta data from memory and from db void eraseMint(const uint256& hash, CWalletDB& walletdb); // add mint meta data to memory and to db - void addOrUpdate(const CSparkMintMeta& mint, const uint256& lTagHash, CWalletDB& walletdb); + void addOrUpdateMint(const CSparkMintMeta& mint, const uint256& lTagHash, CWalletDB& walletdb); CSparkMintMeta getMintMeta(const uint256& hash); - void UpdateSpendStateFromMempool(const std::vector& lTags); - void UpdateMintStateFromMempool(const std::vector& coins); + void UpdateSpendStateFromMempool(const std::vector& lTags, const uint256& txHash, bool fUpdateMint = true); + void UpdateMintStateFromMempool(const std::vector>>& coins, const uint256& txHash); // get the vector of mint metadata for a single address std::vector listAddressCoins(const int32_t& i, bool fUnusedOnly = false); @@ -81,14 +81,23 @@ class CSparkWallet { const CCoinControl *coinControl, bool autoMintAll = false); - // Returns the list of pairs of coins and meta data for that coin, + std::vector CreateSparkSpendTransaction( + const std::vector& recipients, + const std::vector& privateRecipients, + CAmount &fee, + const CCoinControl *coinControl = NULL); + + // Returns the list of pairs of coins and metadata for that coin, std::list> GetAvailableSparkCoins(CWalletDB& walletdb, const CCoinControl *coinControl = NULL) const; private: + std::string strWalletFile; // this is latest used diversifier int32_t lastDiversifier; - // this is incoming view key, which is saved into db and is used to identify our coins + // this is full view key, which is saved into db + spark::FullViewKey fullViewKey; + // this is incoming view key spark::IncomingViewKey viewKey; // map diversifier to address. diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 1560c63419..4ab45d56c7 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -127,6 +127,8 @@ TestingSetup::TestingSetup(const std::string& chainName, std::string suf) : Basi pwalletMain->SetBestChain(chainActive.GetLocator()); pwalletMain->zwallet = std::make_unique(pwalletMain->strWalletFile); + pwalletMain->sparkWallet = std::make_unique(pwalletMain->strWalletFile); + pwalletMain->zwallet->GetTracker().Init(); pwalletMain->zwallet->LoadMintPoolFromDB(); pwalletMain->zwallet->SyncWithChain(); diff --git a/src/validation.cpp b/src/validation.cpp index a122a1e615..d288aeefc2 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -822,7 +822,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C // Spark spark::CSparkState *sparkState = spark::CSparkState::GetState(); - std::vector sparkMintCoins; + std::vector>> sparkMintCoins; std::vector sparkUsedLTags; { @@ -950,8 +950,8 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C } for (const auto& coin : sparkMintCoins) { - if (sparkState->HasCoin(coin) || pool.sparkState.HasMint(coin)) { - LogPrintf("AcceptToMemoryPool(): Spark mint with the same value %s is already in the mempool\n", coin.getHash().GetHex()); + if (sparkState->HasCoin(coin.first) || pool.sparkState.HasMint(coin.first)) { + LogPrintf("AcceptToMemoryPool(): Spark mint with the same value %s is already in the mempool\n", coin.first.getHash().GetHex()); return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict"); } } @@ -1520,7 +1520,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C #ifdef ENABLE_WALLET if (!GetBoolArg("-disablewallet", false) && pwalletMain->sparkWallet) { LogPrintf("Adding spends to wallet from Mempool..\n"); - pwalletMain->sparkWallet->UpdateSpendStateFromMempool(sparkUsedLTags); + pwalletMain->sparkWallet->UpdateSpendStateFromMempool(sparkUsedLTags, hash); } #endif } @@ -1542,7 +1542,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C if(tx.IsSparkMint() && !GetBoolArg("-disablewallet", false) && pwalletMain->sparkWallet) { LogPrintf("Adding Spark mints to Mempool..\n"); - pwalletMain->sparkWallet->UpdateMintStateFromMempool(sparkMintCoins); + pwalletMain->sparkWallet->UpdateMintStateFromMempool(sparkMintCoins, hash); } if(tx.IsLelantusMint() && !GetBoolArg("-disablewallet", false) && pwalletMain->zwallet) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 66a267c8ab..5b4b3a163a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5521,6 +5521,21 @@ CWalletTx CWallet::CreateLelantusJoinSplitTransaction( return tx; } +std::vector CWallet::CreateSparkSpendTransaction( + const std::vector& recipients, + const std::vector& privateRecipients, + CAmount &fee, + const CCoinControl *coinControl) +{ + // sanity check + EnsureMintWalletAvailable(); + + if (IsLocked()) { + throw std::runtime_error(_("Wallet locked")); + } + + return sparkWallet->CreateSparkSpendTransaction(recipients, privateRecipients, fee, coinControl); +} std::pair CWallet::EstimateJoinSplitFee( CAmount required, bool subtractFeeFromAmount, diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index bd7387fe77..80df282bd9 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1065,6 +1065,12 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool fAskFee = false, const CCoinControl *coinControl = NULL); + std::vector CreateSparkSpendTransaction( + const std::vector& recipients, + const std::vector& privateRecipients, + CAmount &fee, + const CCoinControl *coinControl = NULL); + std::vector SpendSigma(const std::vector& recipients, CWalletTx& result); std::vector SpendSigma(const std::vector& recipients, CWalletTx& result, CAmount& fee); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 6a337034b6..5446d137e5 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1451,14 +1451,14 @@ bool CWalletDB::writeDiversifier(const int32_t& diversifier) return Write(std::string("div"), diversifier); } -bool CWalletDB::readIncomingViewKey(spark::IncomingViewKey& viewKey) +bool CWalletDB::readFullViewKey(spark::FullViewKey& fullViewKey) { - return Read(std::string("viewkey"), viewKey); + return Read(std::string("fullViewKey"), fullViewKey); } -bool CWalletDB::writeIncomingViewKey(const spark::IncomingViewKey& viewKey) +bool CWalletDB::writeFullViewKey(const spark::FullViewKey& fullViewKey) { - return Write(std::string("viewkey"), viewKey); + return Write(std::string("fullViewKey"), fullViewKey); } bool CWalletDB::WritePubcoin(const uint256& hashSerial, const GroupElement& pubcoin) @@ -1763,6 +1763,57 @@ bool CWalletDB::EraseSparkMint(const uint256& lTagHash) return Erase(std::make_pair(std::string("sparkMint"), lTagHash)); } +void CWalletDB::ListSparkSpends(std::list& listSparkSpends) +{ + Dbc *pcursor = GetCursor(); + if (!pcursor) + throw std::runtime_error("CWalletDB::ListCoinSpendSerial() : cannot create DB cursor"); + bool setRange = true; + while (true) { + // Read next record + CDataStream ssKey(SER_DISK, CLIENT_VERSION); + if (setRange) + ssKey << std::make_pair(std::string("spark_spend"), secp_primitives::GroupElement()); + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + int ret = ReadAtCursor(pcursor, ssKey, ssValue, setRange); + setRange = false; + if (ret == DB_NOTFOUND) + break; + else if (ret != 0) { + pcursor->close(); + throw std::runtime_error("CWalletDB::ListSparkSpends() : error scanning DB"); + } + + // Unserialize + std::string strType; + ssKey >> strType; + if (strType != "spark_spend") + break; + GroupElement value; + ssKey >> value; + CSparkSpendEntry sparkSpendItem; + ssValue >> sparkSpendItem; + listSparkSpends.push_back(sparkSpendItem); + } + + pcursor->close(); +} + +bool CWalletDB::WriteSparkSpendEntry(const CSparkSpendEntry& sparkSpend) { + return Write(std::make_pair(std::string("spark_spend"), sparkSpend.lTag), sparkSpend, true); +} + +bool CWalletDB::ReadSparkSpendEntry(const secp_primitives::GroupElement& lTag, CSparkSpendEntry& sparkSpend) { + return Read(std::make_pair(std::string("spark_spend"), lTag), sparkSpend); +} + +bool CWalletDB::HasSparkSpendEntry(const secp_primitives::GroupElement& lTag) { + return Exists(std::make_pair(std::string("spark_spend"), lTag)); +} + +bool CWalletDB::EraseSparkSpendEntry(const CSparkSpendEntry& sparkSpend) { + return Erase(std::make_pair(std::string("spark_spend"), sparkSpend.lTag)); +} /******************************************************************************/ // BIP47 diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 8e7ffbfc1b..d72da6c48d 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -277,8 +277,8 @@ class CWalletDB : public CDB bool readDiversifier(int32_t& nCount); bool writeDiversifier(const int32_t& nCount); - bool readIncomingViewKey(spark::IncomingViewKey& viewKey); - bool writeIncomingViewKey(const spark::IncomingViewKey& viewKey); + bool readFullViewKey(spark::FullViewKey& viewKey); + bool writeFullViewKey(const spark::FullViewKey& viewKey); bool ArchiveDeterministicOrphan(const CHDMint& dMint); bool UnarchiveSigmaMint(const uint256& hashPubcoin, CSigmaEntry& sigma); @@ -307,6 +307,11 @@ class CWalletDB : public CDB bool WriteSparkMint(const uint256& lTagHash, const CSparkMintMeta& mint); bool ReadSparkMint(const uint256& lTagHash, CSparkMintMeta& mint); bool EraseSparkMint(const uint256& lTagHash); + void ListSparkSpends(std::list& listSparkSpends); + bool WriteSparkSpendEntry(const CSparkSpendEntry& sparkSpend); + bool ReadSparkSpendEntry(const secp_primitives::GroupElement& lTag, CSparkSpendEntry& sparkSpend); + bool HasSparkSpendEntry(const secp_primitives::GroupElement& lTag); + bool EraseSparkSpendEntry(const CSparkSpendEntry& sparkSpend); //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain); From 944d2c7c23d86ae774b3544256e2068687a68ba9 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Wed, 22 Jun 2022 12:43:30 -0500 Subject: [PATCH 037/197] Embed KDF derived key size into hash instantiation --- src/libspark/kdf.cpp | 21 +++++++++++---------- src/libspark/kdf.h | 5 +++-- src/libspark/util.cpp | 8 ++++---- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/libspark/kdf.cpp b/src/libspark/kdf.cpp index 920974e976..c397eab071 100644 --- a/src/libspark/kdf.cpp +++ b/src/libspark/kdf.cpp @@ -3,7 +3,7 @@ namespace spark { // Set up a labeled KDF -KDF::KDF(const std::string label) { +KDF::KDF(const std::string label, std::size_t derived_key_size) { this->ctx = EVP_MD_CTX_new(); EVP_DigestInit_ex(this->ctx, EVP_blake2b512(), NULL); @@ -16,6 +16,13 @@ KDF::KDF(const std::string label) { include_size(label.size()); std::vector label_bytes(label.begin(), label.end()); EVP_DigestUpdate(this->ctx, label_bytes.data(), label_bytes.size()); + + // Embed and set the derived key size + if (derived_key_size > EVP_MD_size(EVP_blake2b512())) { + throw std::invalid_argument("Requested KDF size is too large"); + } + include_size(derived_key_size); + this->derived_key_size = derived_key_size; } // Clean up @@ -30,19 +37,13 @@ void KDF::include(CDataStream& data) { } // Finalize the KDF with arbitrary size -std::vector KDF::finalize(std::size_t size) { - // Assert valid size - const std::size_t hash_size = EVP_MD_size(EVP_blake2b512()); - if (size > hash_size) { - throw std::invalid_argument("Requested KDF size is too large"); - } - +std::vector KDF::finalize() { std::vector result; - result.resize(hash_size); + result.resize(EVP_MD_size(EVP_blake2b512())); unsigned int TEMP; EVP_DigestFinal_ex(this->ctx, result.data(), &TEMP); - result.resize(size); + result.resize(this->derived_key_size); return result; } diff --git a/src/libspark/kdf.h b/src/libspark/kdf.h index 1c5348b0e6..6484c2c362 100644 --- a/src/libspark/kdf.h +++ b/src/libspark/kdf.h @@ -7,14 +7,15 @@ namespace spark { class KDF { public: - KDF(const std::string label); + KDF(const std::string label, std::size_t derived_key_size); ~KDF(); void include(CDataStream& data); - std::vector finalize(std::size_t size); + std::vector finalize(); private: void include_size(std::size_t size); EVP_MD_CTX* ctx; + std::size_t derived_key_size; }; } diff --git a/src/libspark/util.cpp b/src/libspark/util.cpp index d6c35b61de..eecf41ce45 100644 --- a/src/libspark/util.cpp +++ b/src/libspark/util.cpp @@ -129,24 +129,24 @@ GroupElement SparkUtils::hash_generator(const std::string label) { // Derive an AES key for diversifier encryption/decryption std::vector SparkUtils::kdf_diversifier(const Scalar& s1) { - KDF kdf(LABEL_KDF_DIVERSIFIER); + KDF kdf(LABEL_KDF_DIVERSIFIER, AES256_KEYSIZE); CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << s1; kdf.include(stream); - return kdf.finalize(AES256_KEYSIZE); + return kdf.finalize(); } // Derive a ChaCha20 key for AEAD operations std::vector SparkUtils::kdf_aead(const GroupElement& K_der) { - KDF kdf(LABEL_KDF_AEAD); + KDF kdf(LABEL_KDF_AEAD, AEAD_KEY_SIZE); CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); stream << K_der; kdf.include(stream); - return kdf.finalize(AEAD_KEY_SIZE); + return kdf.finalize(); } // Hash-to-group function H_div From 0d043e5d212514316a9a8d00fa006b509b490c5e Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Wed, 22 Jun 2022 16:39:25 -0500 Subject: [PATCH 038/197] Generalize range proofs to arbitrary set sizes --- src/libspark/bpplus.cpp | 111 ++++++++++---- src/libspark/bpplus.h | 9 +- src/libspark/coin.cpp | 1 + src/libspark/test/bpplus_test.cpp | 151 ++++++++++++++++--- src/libspark/test/spend_transaction_test.cpp | 2 +- 5 files changed, 217 insertions(+), 57 deletions(-) diff --git a/src/libspark/bpplus.cpp b/src/libspark/bpplus.cpp index 958021a705..0ed981ba66 100644 --- a/src/libspark/bpplus.cpp +++ b/src/libspark/bpplus.cpp @@ -24,8 +24,8 @@ BPPlus::BPPlus( throw std::invalid_argument("Bad BPPlus generator sizes!"); } - // Bit length must be a power of two - if ((N & (N - 1) != 0)) { + // Bit length must be a nonzero power of two + if (!is_nonzero_power_of_2(N)) { throw std::invalid_argument("Bad BPPlus bit length!"); } @@ -37,7 +37,8 @@ BPPlus::BPPlus( TWO_N_MINUS_ONE -= ONE; } -static inline std::size_t log2(std::size_t n) { +// The floor function of log2 +std::size_t log2(std::size_t n) { std::size_t l = 0; while ((n >>= 1) != 0) { l++; @@ -46,33 +47,68 @@ static inline std::size_t log2(std::size_t n) { return l; } +// Is this value a nonzero power of 2? +bool is_nonzero_power_of_2(std::size_t n) { + return n > 0 && (n & (n - 1)) == 0; +} + void BPPlus::prove( - const std::vector& v, - const std::vector& r, - const std::vector& C, + const std::vector& unpadded_v, + const std::vector& unpadded_r, + const std::vector& unpadded_C, BPPlusProof& proof) { - // Check statement validity - std::size_t M = C.size(); - if (N*M > Gi.size()) { - throw std::invalid_argument("Bad BPPlus statement!"); + // Bulletproofs+ are only defined when the input set size is a nonzero power of two + // To get around this, we can trivially pad the input set with zero commitments + // We make sure this is done canonically in a way that's transparent to the caller + + // Define the original and padded sizes + std::size_t unpadded_M = unpadded_C.size(); + if (unpadded_M == 0) { + throw std::invalid_argument("Bad BPPlus statement!1"); } - if (!(v.size() == M && r.size() == M)) { - throw std::invalid_argument("Bad BPPlus statement!"); - } - for (std::size_t j = 0; j < M; j++) { - if (!(G*v[j] + H*r[j] == C[j])) { - throw std::invalid_argument("Bad BPPlus statement!"); - } + std::size_t M = unpadded_M; + if (!is_nonzero_power_of_2(M)) { + M = 1 << (log2(unpadded_M) + 1); } - // Set up transcript + // Set up transcript, using the unpadded values + // This is fine since the verifier canonically generates the same transcript Transcript transcript(LABEL_TRANSCRIPT_BPPLUS); transcript.add("G", G); transcript.add("H", H); transcript.add("Gi", Gi); transcript.add("Hi", Hi); transcript.add("N", Scalar(N)); - transcript.add("C", C); + transcript.add("C", unpadded_C); + + // Now pad the input set to produce a valid statement + std::vector v(unpadded_v); + std::vector r(unpadded_r); + std::vector C(unpadded_C); + for (std::size_t i = unpadded_M; i < M; i++) { + v.emplace_back(); // zero scalar + r.emplace_back(); // zero scalar + C.emplace_back(); // identity group element, a valid commitment using the corresponding scalars + } + + // Check statement validity + if (C.size() != M) { + throw std::invalid_argument("Bad BPPlus statement!2"); + } + if (!is_nonzero_power_of_2(M)) { + throw std::invalid_argument("Unexpected bad padding!3"); + } + if (N*M > Gi.size()) { + throw std::invalid_argument("Bad BPPlus statement!4"); + } + if (!(v.size() == M && r.size() == M)) { + throw std::invalid_argument("Bad BPPlus statement!5"); + } + for (std::size_t j = 0; j < M; j++) { + if (!(G*v[j] + H*r[j] == C[j])) { + throw std::invalid_argument("Bad BPPlus statement!6"); + } + } // Decompose bits std::vector> bits; @@ -254,31 +290,32 @@ void BPPlus::prove( proof.d1 = eta_ + d_*e1 + alpha1*e1.square(); } -bool BPPlus::verify(const std::vector& C, const BPPlusProof& proof) { - std::vector> C_batch = {C}; +bool BPPlus::verify(const std::vector& unpadded_C, const BPPlusProof& proof) { + std::vector> unpadded_C_batch = {unpadded_C}; std::vector proof_batch = {proof}; - return verify(C_batch, proof_batch); + return verify(unpadded_C_batch, proof_batch); } -bool BPPlus::verify(const std::vector>& C, const std::vector& proofs) { +bool BPPlus::verify(const std::vector>& unpadded_C, const std::vector& proofs) { // Preprocess all proofs - if (!(C.size() == proofs.size())) { + if (!(unpadded_C.size() == proofs.size())) { return false; } std::size_t N_proofs = proofs.size(); - std::size_t max_M = 0; // maximum number of aggregated values across all proofs + std::size_t max_M = 0; // maximum number of padded aggregated values across all proofs // Check aggregated input consistency for (std::size_t k = 0; k < N_proofs; k++) { - std::size_t M = C[k].size(); + std::size_t unpadded_M = unpadded_C[k].size(); + std::size_t M = unpadded_M; // Require a power of two if (M == 0) { return false; } - if ((M & (M - 1)) != 0) { - return false; + if (!is_nonzero_power_of_2(M)) { + M = 1 << log2(unpadded_M) + 1; } // Track the maximum value @@ -286,7 +323,7 @@ bool BPPlus::verify(const std::vector>& C, const std:: max_M = M; } - // Check inner produce round consistency + // Check inner product round consistency std::size_t rounds = proofs[k].L.size(); if (proofs[k].R.size() != rounds) { return false; @@ -317,7 +354,7 @@ bool BPPlus::verify(const std::vector>& C, const std:: // Process each proof and add to the batch for (std::size_t k_proofs = 0; k_proofs < N_proofs; k_proofs++) { const BPPlusProof proof = proofs[k_proofs]; - const std::size_t M = C[k_proofs].size(); + const std::size_t unpadded_M = unpadded_C[k_proofs].size(); const std::size_t rounds = proof.L.size(); // Weight this proof in the batch @@ -333,9 +370,19 @@ bool BPPlus::verify(const std::vector>& C, const std:: transcript.add("Gi", Gi); transcript.add("Hi", Hi); transcript.add("N", Scalar(N)); - transcript.add("C", C[k_proofs]); + transcript.add("C", unpadded_C[k_proofs]); transcript.add("A", proof.A); + // Pad to a valid statement if needed + std::size_t M = unpadded_M; + if (!is_nonzero_power_of_2(M)) { + M = 1 << (log2(unpadded_M) + 1); + } + std::vector C(unpadded_C[k_proofs]); + for (std::size_t i = unpadded_M; i < M; i++) { + C.emplace_back(); + } + // Get challenges Scalar y = transcript.challenge("y"); Scalar y_inverse = y.inverse(); @@ -365,7 +412,7 @@ bool BPPlus::verify(const std::vector>& C, const std:: // C_j: -e1**2 * z**(2*(j + 1)) * y**(N*M + 1) * w Scalar C_scalar = e1_square.negate()*z_square*y_NM_1*w; for (std::size_t j = 0; j < M; j++) { - points.emplace_back(C[k_proofs][j]); + points.emplace_back(C[j]); scalars.emplace_back(C_scalar); C_scalar *= z.square(); diff --git a/src/libspark/bpplus.h b/src/libspark/bpplus.h index 9c6983ec7f..120b67528e 100644 --- a/src/libspark/bpplus.h +++ b/src/libspark/bpplus.h @@ -6,6 +6,9 @@ namespace spark { +std::size_t log2(std::size_t n); +bool is_nonzero_power_of_2(std::size_t n); + class BPPlus { public: BPPlus( @@ -15,9 +18,9 @@ class BPPlus { const std::vector& Hi, const std::size_t N); - void prove(const std::vector& v, const std::vector& r, const std::vector& C, BPPlusProof& proof); - bool verify(const std::vector& C, const BPPlusProof& proof); // single proof - bool verify(const std::vector>& C, const std::vector& proofs); // batch of proofs + void prove(const std::vector& unpadded_v, const std::vector& unpadded_r, const std::vector& unpadded_C, BPPlusProof& proof); + bool verify(const std::vector& unpadded_C, const BPPlusProof& proof); // single proof + bool verify(const std::vector>& unpadded_C, const std::vector& proofs); // batch of proofs private: GroupElement G; diff --git a/src/libspark/coin.cpp b/src/libspark/coin.cpp index 9bffd2c837..f1dd132d0c 100644 --- a/src/libspark/coin.cpp +++ b/src/libspark/coin.cpp @@ -76,6 +76,7 @@ Coin::Coin( } // Validate a coin for identification +// NOTE: This assumes the coin has a valid associated range proof, which MUST be separately checked as part of the valid transaction that produced it bool Coin::validate( const IncomingViewKey& incoming_view_key, IdentifiedCoinData& data diff --git a/src/libspark/test/bpplus_test.cpp b/src/libspark/test/bpplus_test.cpp index 74ddb6f827..492d40ce11 100644 --- a/src/libspark/test/bpplus_test.cpp +++ b/src/libspark/test/bpplus_test.cpp @@ -7,8 +7,8 @@ namespace spark { BOOST_FIXTURE_TEST_SUITE(spark_bpplus_tests, BasicTestingSetup) -// Generate and verify a single aggregated proof -BOOST_AUTO_TEST_CASE(completeness_single) +// Generate and verify a single aggregated proof with no padding +BOOST_AUTO_TEST_CASE(completeness_single_unpadded) { // Parameters std::size_t N = 64; // bit length @@ -20,9 +20,61 @@ BOOST_AUTO_TEST_CASE(completeness_single) H.randomize(); std::vector Gi, Hi; - Gi.resize(N*M); - Hi.resize(N*M); - for (std::size_t i = 0; i < N*M; i++) { + std::size_t gens_needed = N*M; + if (!is_nonzero_power_of_2(gens_needed)) { + gens_needed = 1 << (log2(N*M) + 1); + } + BOOST_CHECK_EQUAL(gens_needed, N*M); + Gi.resize(gens_needed); + Hi.resize(gens_needed); + for (std::size_t i = 0; i < gens_needed; i++) { + Gi[i].randomize(); + Hi[i].randomize(); + } + + // Commitments + std::vector v, r; + v.resize(M); + v[0] = Scalar(uint64_t(0)); + v[1] = Scalar(uint64_t(1)); + v[2] = Scalar(uint64_t(2)); + v[3] = Scalar(uint64_t(3)); + r.resize(M); + std::vector C; + C.resize(M); + for (std::size_t j = 0; j < M; j++) { + r[j].randomize(); + C[j] = G*v[j] + H*r[j]; + } + + BPPlus bpplus(G, H, Gi, Hi, N); + BPPlusProof proof; + bpplus.prove(v, r, C, proof); + + BOOST_CHECK(bpplus.verify(C, proof)); +} + +// Generate and verify a single aggregated proof with padding +BOOST_AUTO_TEST_CASE(completeness_single_padded) +{ + // Parameters + std::size_t N = 64; // bit length + std::size_t M = 5; // aggregation + + // Generators + GroupElement G, H; + G.randomize(); + H.randomize(); + + std::vector Gi, Hi; + std::size_t gens_needed = N*M; + if (!is_nonzero_power_of_2(gens_needed)) { + gens_needed = 1 << (log2(N*M) + 1); + } + BOOST_CHECK_EQUAL(gens_needed, 8*N); // hardcoded for this test + Gi.resize(gens_needed); + Hi.resize(gens_needed); + for (std::size_t i = 0; i < gens_needed; i++) { Gi[i].randomize(); Hi[i].randomize(); } @@ -33,7 +85,8 @@ BOOST_AUTO_TEST_CASE(completeness_single) v[0] = Scalar(uint64_t(0)); v[1] = Scalar(uint64_t(1)); v[2] = Scalar(uint64_t(2)); - v[3] = Scalar(std::numeric_limits::max()); + v[3] = Scalar(uint64_t(3)); + v[4] = Scalar(std::numeric_limits::max()); r.resize(M); std::vector C; C.resize(M); @@ -49,8 +102,8 @@ BOOST_AUTO_TEST_CASE(completeness_single) BOOST_CHECK(bpplus.verify(C, proof)); } -// A single proof with invalid value -BOOST_AUTO_TEST_CASE(invalid_single) +// A single proof with invalid value and no padding +BOOST_AUTO_TEST_CASE(invalid_single_unpadded) { // Parameters std::size_t N = 64; // bit length @@ -62,9 +115,14 @@ BOOST_AUTO_TEST_CASE(invalid_single) H.randomize(); std::vector Gi, Hi; - Gi.resize(N*M); - Hi.resize(N*M); - for (std::size_t i = 0; i < N*M; i++) { + std::size_t gens_needed = N*M; + if (!is_nonzero_power_of_2(gens_needed)) { + gens_needed = 1 << (log2(N*M) + 1); + } + BOOST_CHECK_EQUAL(gens_needed, N*M); + Gi.resize(gens_needed); + Hi.resize(gens_needed); + for (std::size_t i = 0; i < gens_needed; i++) { Gi[i].randomize(); Hi[i].randomize(); } @@ -91,12 +149,61 @@ BOOST_AUTO_TEST_CASE(invalid_single) BOOST_CHECK(!bpplus.verify(C, proof)); } +// A single proof with invalid value and padding +BOOST_AUTO_TEST_CASE(invalid_single_padded) +{ + // Parameters + std::size_t N = 64; // bit length + std::size_t M = 5; // aggregation + + // Generators + GroupElement G, H; + G.randomize(); + H.randomize(); + + std::vector Gi, Hi; + std::size_t gens_needed = N*M; + if (!is_nonzero_power_of_2(gens_needed)) { + gens_needed = 1 << (log2(N*M) + 1); + } + BOOST_CHECK_EQUAL(gens_needed, 8*N); // hardcoded for this test + Gi.resize(gens_needed); + Hi.resize(gens_needed); + for (std::size_t i = 0; i < gens_needed; i++) { + Gi[i].randomize(); + Hi[i].randomize(); + } + + // Commitments + std::vector v, r; + v.resize(M); + v[0] = Scalar(uint64_t(0)); + v[1] = Scalar(uint64_t(1)); + v[2] = Scalar(uint64_t(2)); + v[3] = Scalar(uint64_t(3)); + v[4] = Scalar(std::numeric_limits::max()) + Scalar(uint64_t(1)); // out of range + r.resize(M); + std::vector C; + C.resize(M); + for (std::size_t j = 0; j < M; j++) { + r[j].randomize(); + C[j] = G*v[j] + H*r[j]; + } + + BPPlus bpplus(G, H, Gi, Hi, N); + BPPlusProof proof; + bpplus.prove(v, r, C, proof); + + BOOST_CHECK(!bpplus.verify(C, proof)); +} // Generate and verify a batch of proofs with variable aggregation BOOST_AUTO_TEST_CASE(completeness_batch) { // Parameters std::size_t N = 64; // bit length - std::size_t B = 4; // number of proofs in batch + std::size_t B = 5; // number of proofs in batch + std::vector sizes = {1, 2, 3, 4, 5}; + BOOST_CHECK_EQUAL(sizes.size(), B); // Generators GroupElement G, H; @@ -104,9 +211,9 @@ BOOST_AUTO_TEST_CASE(completeness_batch) H.randomize(); std::vector Gi, Hi; - Gi.resize(N*(1 << B)); - Hi.resize(N*(1 << B)); - for (std::size_t i = 0; i < N*(1 << B); i++) { + Gi.resize(8*N); + Hi.resize(8*N); + for (std::size_t i = 0; i < 8*N; i++) { Gi[i].randomize(); Hi[i].randomize(); } @@ -119,7 +226,7 @@ BOOST_AUTO_TEST_CASE(completeness_batch) // Build each proof for (std::size_t i = 0; i < B; i++) { // Commitments - std::size_t M = 1 << i; + std::size_t M = sizes[i]; std::vector v, r; v.resize(M); r.resize(M); @@ -143,7 +250,9 @@ BOOST_AUTO_TEST_CASE(invalid_batch) { // Parameters std::size_t N = 64; // bit length - std::size_t B = 4; // number of proofs in batch + std::size_t B = 5; // number of proofs in batch + std::vector sizes = {1, 2, 3, 4, 5}; + BOOST_CHECK_EQUAL(sizes.size(), B); // Generators GroupElement G, H; @@ -151,9 +260,9 @@ BOOST_AUTO_TEST_CASE(invalid_batch) H.randomize(); std::vector Gi, Hi; - Gi.resize(N*(1 << B)); - Hi.resize(N*(1 << B)); - for (std::size_t i = 0; i < N*(1 << B); i++) { + Gi.resize(8*N); + Hi.resize(8*N); + for (std::size_t i = 0; i < 8*N; i++) { Gi[i].randomize(); Hi[i].randomize(); } @@ -166,7 +275,7 @@ BOOST_AUTO_TEST_CASE(invalid_batch) // Build each proof for (std::size_t i = 0; i < B; i++) { // Commitments - std::size_t M = 1 << i; + std::size_t M = sizes[i]; std::vector v, r; v.resize(M); r.resize(M); diff --git a/src/libspark/test/spend_transaction_test.cpp b/src/libspark/test/spend_transaction_test.cpp index 319c83791c..3719237d67 100644 --- a/src/libspark/test/spend_transaction_test.cpp +++ b/src/libspark/test/spend_transaction_test.cpp @@ -82,7 +82,7 @@ BOOST_AUTO_TEST_CASE(generate_verify) } // Generate new output coins and compute the fee - const std::size_t t = 2; + const std::size_t t = 3; std::vector out_coin_data; for (std::size_t j = 0; j < t; j++) { out_coin_data.emplace_back(); From de620af7495e61c2cc35e5800e7168f8c21da699 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Fri, 24 Jun 2022 14:23:59 -0500 Subject: [PATCH 039/197] WIP: spend verification batching --- src/libspark/spend_transaction.cpp | 287 +++++++++++-------- src/libspark/spend_transaction.h | 26 +- src/libspark/test/spend_transaction_test.cpp | 11 +- 3 files changed, 197 insertions(+), 127 deletions(-) diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index 824b41f7b5..354cdca15e 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -2,6 +2,7 @@ namespace spark { +// Generate a spend transaction that consumes existing coins and generates new ones SpendTransaction::SpendTransaction( const Params* params) { this->params = params; @@ -11,8 +12,6 @@ SpendTransaction::SpendTransaction( const Params* params, const FullViewKey& full_view_key, const SpendKey& spend_key, - const std::vector& in_coins, - const std::vector>& roots, const std::vector& inputs, const uint64_t f, const std::vector& outputs @@ -22,16 +21,13 @@ SpendTransaction::SpendTransaction( // Size parameters const std::size_t w = inputs.size(); // number of consumed coins const std::size_t t = outputs.size(); // number of generated coins - const std::size_t N = in_coins.size(); // size of cover set - - // Ensure we have enough Merkle roots - if (roots.size() != w) { - throw std::invalid_argument("Bad number of roots for spend transaction"); - } - this->roots = roots; + const std::size_t N = (std::size_t) std::pow(params->get_n_grootle(), params->get_m_grootle()); // size of cover sets // Prepare input-related vectors - this->in_coins = in_coins; // input cover set + this->cover_set_ids.reserve(w); // cover set data and metadata + this->cover_sets.reserve(w); + this->cover_set_representations.reserve(w); + this->cover_set_sizes.reserve(w); this->S1.reserve(w); // serial commitment offsets this->C1.reserve(w); // value commitment offsets this->grootle_proofs.reserve(w); // Grootle one-of-many proofs @@ -46,15 +42,6 @@ SpendTransaction::SpendTransaction( this->out_coins.reserve(t); // coins std::vector k; // nonces - // Parse out serial and value commitments from the cover set for use in proofs - std::vector S, C; - S.resize(N); - C.resize(N); - for (std::size_t i = 0; i < N; i++) { - S[i] = in_coins[i].S; - C[i] = in_coins[i].C; - } - // Prepare inputs Grootle grootle( this->params->get_H(), @@ -64,6 +51,20 @@ SpendTransaction::SpendTransaction( this->params->get_m_grootle() ); for (std::size_t u = 0; u < w; u++) { + // Parse out cover set data for this spend + this->cover_set_ids.emplace_back(inputs[u].cover_set_id); + this->cover_sets.emplace_back(inputs[u].cover_set); + this->cover_set_representations.emplace_back(inputs[u].cover_set_representation); + this->cover_set_sizes.emplace_back(inputs[u].cover_set_size); + + std::vector S, C; + S.reserve(N); + C.reserve(N); + for (std::size_t i = 0; i < N; i++) { + S.emplace_back(inputs[u].cover_set[i].S); + C.emplace_back(inputs[u].cover_set[i].C); + } + // Serial commitment offset this->S1.emplace_back( this->params->get_F()*inputs[u].s @@ -91,7 +92,7 @@ SpendTransaction::SpendTransaction( SparkUtils::hash_val(inputs[u].k) - SparkUtils::hash_val1(inputs[u].s, full_view_key.get_D()), C, this->C1.back(), - this->roots[u], + this->cover_set_representations[u], this->grootle_proofs.back() ); @@ -169,9 +170,9 @@ SpendTransaction::SpendTransaction( // Compute the binding hash Scalar mu = hash_bind( - this->roots, this->out_coins, this->f, + this->cover_set_representations, this->S1, this->C1, this->T, @@ -206,116 +207,176 @@ std::vector& SpendTransaction::getUsedLTags() { return T; } +// Convenience wrapper for verifying a single spend transaction +bool SpendTransaction::verify(const SpendTransaction& transaction) { + std::vector transactions = { transaction }; + return verify(transaction.params, transactions); +} -bool SpendTransaction::verify() { - // Size parameters - const std::size_t w = this->grootle_proofs.size(); - const std::size_t t = this->out_coins.size(); - const std::size_t N = this->in_coins.size(); - - // Semantics - if (this->S1.size() != w || this->C1.size() != w || this->T.size() != w || this->roots.size() != w) { - throw std::invalid_argument("Bad spend transaction semantics"); - } - if (N > (std::size_t)pow(this->params->get_n_grootle(), this->params->get_m_grootle())) { - throw std::invalid_argument("Bad spend transaction semantics"); - } - - // Parse out serial and value commitments from the cover set for use in proofs - std::vector S, C; - S.resize(N); - C.resize(N); - for (std::size_t i = 0; i < N; i++) { - S[i] = this->in_coins[i].S; - C[i] = this->in_coins[i].C; - } - - // Parse out value commitments from the output set for use in proofs - std::vector C_out; - C_out.resize(t); - for (std::size_t j = 0; j < t; j++) { - C_out[j] = this->out_coins[j].C; - } - - // Consumed coins - Grootle grootle( - this->params->get_H(), - this->params->get_G_grootle(), - this->params->get_H_grootle(), - this->params->get_n_grootle(), - this->params->get_m_grootle() - ); - - // Verify all Grootle proofs in a batch - std::vector sizes; - for (std::size_t u = 0; u < w; u++) { - sizes.emplace_back(N); - } - if (!grootle.verify(S, this->S1, C, this->C1, this->roots, sizes, this->grootle_proofs)) { - return false; - } - - // Compute the binding hash - Scalar mu = hash_bind( - this->roots, - this->out_coins, - this->f, - this->S1, - this->C1, - this->T, - this->grootle_proofs, - this->balance_proof, - this->range_proof - ); +// Determine if a set of spend transactions is collectively valid +// NOTE: This assumes that the relationship between a `cover_set_id` and the provided `cover_set` is already valid and canonical! +// NOTE: This assumes that validity criteria relating to chain context have been externally checked! +bool SpendTransaction::verify(const Params* params, const std::vector& transactions) { + // The idea here is to perform batching as broadly as possible + // - Grootle proofs can be batched if they share a (partial) cover set + // - Range proofs can always be batched arbitrarily + // - Other parts of the transaction can be checked separately + // - We try to verify in order of likely computational complexity, to fail early + + // Track range proofs to batch + std::vector> range_proofs_C; // commitments for all range proofs + std::vector range_proofs; // all range proofs + + // Track cover sets across Grootle proofs to batch + std::unordered_map>> grootle_buckets; + + // Process each transaction + for (std::size_t i = 0; i < transactions.size(); i++) { + SpendTransaction tx = transactions[i]; + + // Assert common parameters + if (params != tx.params) { + return false; + } + + // Size parameters for this transaction + const std::size_t w = tx.cover_set_ids.size(); // number of consumed coins + const std::size_t t = tx.out_coins.size(); // number of generated coins + const std::size_t N = (std::size_t) std::pow(params->get_n_grootle(), params->get_m_grootle()); // size of cover sets + + // Consumed coin semantics + if (tx.S1.size() != w || + tx.C1.size() != w || + tx.T.size() != w || + tx.grootle_proofs.size() != w || + tx.cover_sets.size() != w || + tx.cover_set_representations.size() != w) { + throw std::invalid_argument("Bad spend transaction semantics"); + } + + // Cover set semantics + for (std::size_t u = 0; u < w; u++) { + if (tx.cover_sets[u].size() > N) { + throw std::invalid_argument("Bad spend transaction semantics"); + } + } + + // Store range proof with commitments + range_proofs_C.emplace_back(); + for (std::size_t j = 0; j < t; j++) { + range_proofs_C.back().emplace_back(tx.out_coins[j].C); + } + range_proofs.emplace_back(tx.range_proof); + + // Sort all Grootle proofs into buckets for batching based on common input sets + for (std::size_t u = 0; u < w; u++) { + grootle_buckets[tx.cover_set_ids[u]].emplace_back(std::pair(i, u)); + } + + // Compute the binding hash + Scalar mu = hash_bind( + tx.out_coins, + tx.f, + tx.cover_set_representations, + tx.S1, + tx.C1, + tx.T, + tx.grootle_proofs, + tx.balance_proof, + tx.range_proof + ); - // Verify the authorizing Chaum proof - Chaum chaum( - this->params->get_F(), - this->params->get_G(), - this->params->get_H(), - this->params->get_U() - ); - if (!chaum.verify(mu, this->S1, this->T, this->chaum_proof)) { - return false; + // Verify the authorizing Chaum-Pedersen proof + Chaum chaum( + tx.params->get_F(), + tx.params->get_G(), + tx.params->get_H(), + tx.params->get_U() + ); + if (!chaum.verify(mu, tx.S1, tx.T, tx.chaum_proof)) { + return false; + } + + // Verify the balance proof + Schnorr schnorr(tx.params->get_H()); + GroupElement balance_statement; + for (std::size_t u = 0; u < w; u++) { + balance_statement += tx.C1[u]; + } + for (std::size_t j = 0; j < t; j++) { + balance_statement += tx.out_coins[j].C.inverse(); + } + balance_statement += tx.params->get_G()*Scalar(tx.f); + if(!schnorr.verify( + balance_statement, + tx.balance_proof + )) { + return false; + } } - // Verify the aggregated range proof + // Verify all range proofs in a batch BPPlus range( - this->params->get_G(), - this->params->get_H(), - this->params->get_G_range(), - this->params->get_H_range(), + params->get_G(), + params->get_H(), + params->get_G_range(), + params->get_H_range(), 64 ); - if (!range.verify(C_out, this->range_proof)) { + if (!range.verify(range_proofs_C, range_proofs)) { return false; } - // Verify the balance proof - Schnorr schnorr(this->params->get_H()); - GroupElement balance_statement; - for (std::size_t u = 0; u < w; u++) { - balance_statement += this->C1[u]; - } - for (std::size_t j = 0; j < t; j++) { - balance_statement += this->out_coins[j].C.inverse(); - } - balance_statement += this->params->get_G()*Scalar(this->f); - if(!schnorr.verify( - balance_statement, - this->balance_proof - )) { - return false; + // Verify all Grootle proofs in batches (based on cover set) + // TODO: Finish this + Grootle grootle( + params->get_H(), + params->get_G_grootle(), + params->get_H_grootle(), + params->get_n_grootle(), + params->get_m_grootle() + ); + for (auto grootle_bucket : grootle_buckets) { + std::size_t cover_set_id = grootle_bucket.first; + std::vector> proof_indexes = grootle_bucket.second; + + // Build the proof statement and metadata vectors from these proofs + std::vector S, S1, V, V1; + std::vector> cover_set_representations; + std::vector sizes; + std::vector proofs; + + for (auto proof_index : proof_indexes) { + // Because we assume all proofs in this list share a monotonic cover set, the largest such set is the one to use for verification + std::size_t this_cover_set_size = transactions[proof_index.first].cover_sets[proof_index.second].size(); + if (this_cover_set_size > S.size()) { + for (std::size_t i = S.size(); i < this_cover_set_size; i++) { + S.emplace_back(transactions[proof_index.first].cover_sets[proof_index.second][i].S); + V.emplace_back(transactions[proof_index.first].cover_sets[proof_index.second][i].C); + } + } + + // We always use the other elements + S1.emplace_back(transactions[proof_index.first].S1[proof_index.second]); + V1.emplace_back(transactions[proof_index.first].C1[proof_index.second]); + cover_set_representations.emplace_back(transactions[proof_index.first].cover_set_representations[proof_index.second]); + sizes.emplace_back(transactions[proof_index.first].cover_set_sizes[proof_index.second]); + proofs.emplace_back(transactions[proof_index.first].grootle_proofs[proof_index.second]); + } + + // Verify the batch + grootle.verify(S, S1, V, V1, cover_set_representations, sizes, proofs); } + // Any failures have been identified already, so the batch is valid return true; } // Hash-to-scalar function H_bind Scalar SpendTransaction::hash_bind( - const std::vector>& roots, const std::vector& out_coins, const uint64_t f, + const std::vector>& cover_set_representations, const std::vector& S1, const std::vector& C1, const std::vector& T, @@ -328,9 +389,9 @@ Scalar SpendTransaction::hash_bind( CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); // Perform the serialization and hashing - stream << roots, stream << out_coins; stream << f; + stream << cover_set_representations; stream << S1; stream << C1; stream << T; diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h index 067dc76d61..ce59280c88 100644 --- a/src/libspark/spend_transaction.h +++ b/src/libspark/spend_transaction.h @@ -12,8 +12,16 @@ namespace spark { using namespace secp_primitives; +// Note that cover sets are treated as monotonic, meaning they grow over time (up to some implementation-defined limit) +// To support efficient batching, we track which set each spend references +// If spends share a `cover_set_id`, we assume the corresponding `cover_set` vectors have a subset relationship +// This relationship _must_ be checked elsewhere, as we simply use the largest `cover_set` for each `cover_set_id`! struct InputCoinData { - std::size_t index; // index in cover set + uint64_t cover_set_id; // an identifier for the monotonically-growing set of which `cover_set` is a subset + std::vector cover_set; // set of coins used as a cover set for the spend + std::vector cover_set_representation; // a unique representation for the ordered elements of the partial `cover_set` used in the spend + std::size_t cover_set_size; // the size of the partial cover set used by the spend; should be canonical with `cover_set_representation` + std::size_t index; // index of the coin in the cover set Scalar s; // serial number GroupElement T; // tag uint64_t v; // value @@ -35,21 +43,21 @@ class SpendTransaction { const Params* params, const FullViewKey& full_view_key, const SpendKey& spend_key, - const std::vector& in_coins, - const std::vector>& roots, const std::vector& inputs, const uint64_t f, const std::vector& outputs ); - bool verify(); uint64_t getFee(); std::vector& getUsedLTags(); - + + static bool verify(const Params* params, const std::vector& transactions); + static bool verify(const SpendTransaction& transaction); + static Scalar hash_bind( - const std::vector>& roots, const std::vector& out_coins, const uint64_t f, + const std::vector>& cover_set_representations, const std::vector& S1, const std::vector& C1, const std::vector& T, @@ -60,8 +68,10 @@ class SpendTransaction { private: const Params* params; - std::vector in_coins; - std::vector> roots; + std::vector cover_set_ids; + std::vector> cover_sets; + std::vector> cover_set_representations; + std::vector cover_set_sizes; std::vector out_coins; uint64_t f; std::vector S1, C1, T; diff --git a/src/libspark/test/spend_transaction_test.cpp b/src/libspark/test/spend_transaction_test.cpp index 3719237d67..66bee82a27 100644 --- a/src/libspark/test/spend_transaction_test.cpp +++ b/src/libspark/test/spend_transaction_test.cpp @@ -63,15 +63,16 @@ BOOST_AUTO_TEST_CASE(generate_verify) // Choose coins to spend, recover them, and prepare them for spending std::vector spend_indices = { 1, 3, 5 }; std::vector spend_coin_data; - std::vector> roots; const std::size_t w = spend_indices.size(); for (std::size_t u = 0; u < w; u++) { IdentifiedCoinData identified_coin_data = in_coins[spend_indices[u]].identify(incoming_view_key); RecoveredCoinData recovered_coin_data = in_coins[spend_indices[u]].recover(full_view_key, identified_coin_data); - roots.emplace_back(random_char_vector()); - spend_coin_data.emplace_back(); + spend_coin_data.back().cover_set_id = 31415; + spend_coin_data.back().cover_set = in_coins; + spend_coin_data.back().cover_set_representation = random_char_vector(); + spend_coin_data.back().cover_set_size = N; spend_coin_data.back().index = spend_indices[u]; spend_coin_data.back().k = identified_coin_data.k; spend_coin_data.back().s = recovered_coin_data.s; @@ -110,15 +111,13 @@ BOOST_AUTO_TEST_CASE(generate_verify) params, full_view_key, spend_key, - in_coins, - roots, spend_coin_data, f, out_coin_data ); // Verify - BOOST_CHECK(transaction.verify()); + BOOST_CHECK(SpendTransaction::verify(transaction)); } BOOST_AUTO_TEST_SUITE_END() From 72e8a1a1398224f0a9271faba83d7ec081c0be35 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Tue, 9 Aug 2022 14:24:25 -0500 Subject: [PATCH 040/197] Use a key-committing AEAD construction --- src/libspark/aead.cpp | 27 +++++++----- src/libspark/aead.h | 6 ++- src/libspark/coin.cpp | 8 ++-- src/libspark/test/aead_test.cpp | 78 ++++++++++++++++++++------------- src/libspark/util.cpp | 12 +++++ src/libspark/util.h | 3 ++ 6 files changed, 86 insertions(+), 48 deletions(-) diff --git a/src/libspark/aead.cpp b/src/libspark/aead.cpp index d3686e255f..ada79bcc2c 100644 --- a/src/libspark/aead.cpp +++ b/src/libspark/aead.cpp @@ -2,16 +2,15 @@ namespace spark { -// Perform authenticated encryption with ChaCha20-Poly1305 -AEADEncryptedData AEAD::encrypt(const std::vector& key, const std::string additional_data, CDataStream& data) { - // Check key size - if (key.size() != AEAD_KEY_SIZE) { - throw std::invalid_argument("Bad AEAD key size"); - } - +// Perform authenticated encryption with ChaCha20-Poly1305 using key commitment +AEADEncryptedData AEAD::encrypt(const GroupElement& prekey, const std::string additional_data, CDataStream& data) { // Set up the result structure AEADEncryptedData result; + // Derive the key and commitment + std::vector key = SparkUtils::kdf_aead(prekey); + result.key_commitment = SparkUtils::commit_aead(prekey); + // Internal size tracker; we know the size of the data already, and can ignore int TEMP; @@ -43,11 +42,15 @@ AEADEncryptedData AEAD::encrypt(const std::vector& key, const std return result; } -// Perform authenticated decryption with ChaCha20-Poly1305 -CDataStream AEAD::decrypt_and_verify(const std::vector& key, const std::string additional_data, AEADEncryptedData& data) { - // Check key size - if (key.size() != AEAD_KEY_SIZE) { - throw std::invalid_argument("Bad AEAD key size"); +// Perform authenticated decryption with ChaCha20-Poly1305 using key commitment +CDataStream AEAD::decrypt_and_verify(const GroupElement& prekey, const std::string additional_data, AEADEncryptedData& data) { + // Derive the key and commitment + std::vector key = SparkUtils::kdf_aead(prekey); + std::vector key_commitment = SparkUtils::commit_aead(prekey); + + // Assert that the key commitment is valid + if (key_commitment != data.key_commitment) { + throw std::runtime_error("Bad AEAD key commitment"); } // Set up the result diff --git a/src/libspark/aead.h b/src/libspark/aead.h index e7af8ba926..ce8470a17d 100644 --- a/src/libspark/aead.h +++ b/src/libspark/aead.h @@ -8,6 +8,7 @@ namespace spark { struct AEADEncryptedData { std::vector ciphertext; std::vector tag; + std::vector key_commitment; ADD_SERIALIZE_METHODS; @@ -15,13 +16,14 @@ struct AEADEncryptedData { inline void SerializationOp(Stream& s, Operation ser_action) { READWRITE(ciphertext); READWRITE(tag); + READWRITE(key_commitment); } }; class AEAD { public: - static AEADEncryptedData encrypt(const std::vector& key, const std::string additional_data, CDataStream& data); - static CDataStream decrypt_and_verify(const std::vector& key, const std::string associated_data, AEADEncryptedData& data); + static AEADEncryptedData encrypt(const GroupElement& prekey, const std::string additional_data, CDataStream& data); + static CDataStream decrypt_and_verify(const GroupElement& prekey, const std::string associated_data, AEADEncryptedData& data); }; } diff --git a/src/libspark/coin.cpp b/src/libspark/coin.cpp index f1dd132d0c..a558deb280 100644 --- a/src/libspark/coin.cpp +++ b/src/libspark/coin.cpp @@ -61,7 +61,7 @@ Coin::Coin( r.memo = std::string(memo.begin(), memo.end()); CDataStream r_stream(SER_NETWORK, PROTOCOL_VERSION); r_stream << r; - this->r_ = AEAD::encrypt(SparkUtils::kdf_aead(address.get_Q1()*SparkUtils::hash_k(k)), "Mint coin data", r_stream); + this->r_ = AEAD::encrypt(address.get_Q1()*SparkUtils::hash_k(k), "Mint coin data", r_stream); } else { // Encrypt recipient data SpendCoinRecipientData r; @@ -71,7 +71,7 @@ Coin::Coin( r.memo = std::string(memo.begin(), memo.end()); CDataStream r_stream(SER_NETWORK, PROTOCOL_VERSION); r_stream << r; - this->r_ = AEAD::encrypt(SparkUtils::kdf_aead(address.get_Q1()*SparkUtils::hash_k(k)), "Spend coin data", r_stream); + this->r_ = AEAD::encrypt(address.get_Q1()*SparkUtils::hash_k(k), "Spend coin data", r_stream); } } @@ -120,7 +120,7 @@ IdentifiedCoinData Coin::identify(const IncomingViewKey& incoming_view_key) { try { // Decrypt recipient data - CDataStream stream = AEAD::decrypt_and_verify(SparkUtils::kdf_aead(this->K*incoming_view_key.get_s1()), "Mint coin data", this->r_); + CDataStream stream = AEAD::decrypt_and_verify(this->K*incoming_view_key.get_s1(), "Mint coin data", this->r_); stream >> r; } catch (...) { throw std::runtime_error("Unable to identify coin"); @@ -135,7 +135,7 @@ IdentifiedCoinData Coin::identify(const IncomingViewKey& incoming_view_key) { try { // Decrypt recipient data - CDataStream stream = AEAD::decrypt_and_verify(SparkUtils::kdf_aead(this->K*incoming_view_key.get_s1()), "Spend coin data", this->r_); + CDataStream stream = AEAD::decrypt_and_verify(this->K*incoming_view_key.get_s1(), "Spend coin data", this->r_); stream >> r; } catch (...) { throw std::runtime_error("Unable to identify coin"); diff --git a/src/libspark/test/aead_test.cpp b/src/libspark/test/aead_test.cpp index 975fa495db..2a3901326d 100644 --- a/src/libspark/test/aead_test.cpp +++ b/src/libspark/test/aead_test.cpp @@ -10,9 +10,8 @@ BOOST_FIXTURE_TEST_SUITE(spark_aead_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(complete) { // Key - std::string key_string = "Key prefix"; - std::vector key(key_string.begin(), key_string.end()); - key.resize(AEAD_KEY_SIZE); + GroupElement prekey; + prekey.randomize(); // Serialize int message = 12345; @@ -20,10 +19,10 @@ BOOST_AUTO_TEST_CASE(complete) ser << message; // Encrypt - AEADEncryptedData data = AEAD::encrypt(key, "Associated data", ser); + AEADEncryptedData data = AEAD::encrypt(prekey, "Associated data", ser); // Decrypt - ser = AEAD::decrypt_and_verify(key, "Associated data", data); + ser = AEAD::decrypt_and_verify(prekey, "Associated data", data); // Deserialize int message_; @@ -35,92 +34,111 @@ BOOST_AUTO_TEST_CASE(complete) BOOST_AUTO_TEST_CASE(bad_tag) { // Key - std::string key_string = "Key prefix"; - std::vector key(key_string.begin(), key_string.end()); - key.resize(AEAD_KEY_SIZE); + GroupElement prekey; + prekey.randomize(); // Serialize and encrypt a message int message = 12345; CDataStream ser(SER_NETWORK, PROTOCOL_VERSION); ser << message; - AEADEncryptedData data = AEAD::encrypt(key, "Associated data", ser); + AEADEncryptedData data = AEAD::encrypt(prekey, "Associated data", ser); // Serialize and encrypt an evil message ser.clear(); int evil_message = 666; ser << evil_message; - AEADEncryptedData evil_data = AEAD::encrypt(key, "Associated data", ser); + AEADEncryptedData evil_data = AEAD::encrypt(prekey, "Associated data", ser); // Replace tag data.tag = evil_data.tag; // Decrypt; this should fail - BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(key, "Associated data", data), std::runtime_error); + BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(prekey, "Associated data", data), std::runtime_error); } BOOST_AUTO_TEST_CASE(bad_ciphertext) { // Key - std::string key_string = "Key prefix"; - std::vector key(key_string.begin(), key_string.end()); - key.resize(AEAD_KEY_SIZE); + GroupElement prekey; + prekey.randomize(); // Serialize and encrypt a message int message = 12345; CDataStream ser(SER_NETWORK, PROTOCOL_VERSION); ser << message; - AEADEncryptedData data = AEAD::encrypt(key, "Associated data", ser); + AEADEncryptedData data = AEAD::encrypt(prekey, "Associated data", ser); // Serialize and encrypt an evil message ser.clear(); int evil_message = 666; ser << evil_message; - AEADEncryptedData evil_data = AEAD::encrypt(key, "Associated data", ser); + AEADEncryptedData evil_data = AEAD::encrypt(prekey, "Associated data", ser); // Replace ciphertext data.ciphertext = evil_data.ciphertext; // Decrypt; this should fail - BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(key, "Associated data", data), std::runtime_error); + BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(prekey, "Associated data", data), std::runtime_error); } BOOST_AUTO_TEST_CASE(bad_associated_data) { // Key - std::string key_string = "Key prefix"; - std::vector key(key_string.begin(), key_string.end()); - key.resize(AEAD_KEY_SIZE); + GroupElement prekey; + prekey.randomize(); // Serialize and encrypt a message int message = 12345; CDataStream ser(SER_NETWORK, PROTOCOL_VERSION); ser << message; - AEADEncryptedData data = AEAD::encrypt(key, "Associated data", ser); + AEADEncryptedData data = AEAD::encrypt(prekey, "Associated data", ser); // Decrypt; this should fail - BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(key, "Evil associated data", data), std::runtime_error); + BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(prekey, "Evil associated data", data), std::runtime_error); } BOOST_AUTO_TEST_CASE(bad_key) { // Key - std::string key_string = "Key prefix"; - std::vector key(key_string.begin(), key_string.end()); - key.resize(AEAD_KEY_SIZE); + GroupElement prekey; + prekey.randomize(); // Evil key - std::string evil_key_string = "Evil key prefix"; - std::vector evil_key(evil_key_string.begin(), evil_key_string.end()); - evil_key.resize(AEAD_KEY_SIZE); + GroupElement evil_prekey; + evil_prekey.randomize(); // Serialize and encrypt a message int message = 12345; CDataStream ser(SER_NETWORK, PROTOCOL_VERSION); ser << message; - AEADEncryptedData data = AEAD::encrypt(key, "Associated data", ser); + AEADEncryptedData data = AEAD::encrypt(prekey, "Associated data", ser); // Decrypt; this should fail - BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(evil_key, "Associated data", data), std::runtime_error); + BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(evil_prekey, "Associated data", data), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(bad_key_commitment) +{ + // Key + GroupElement prekey; + prekey.randomize(); + + // Evil key and key commitment + GroupElement evil_prekey; + evil_prekey.randomize(); + std::vector evil_key_commitment = SparkUtils::commit_aead(evil_prekey); + + // Serialize and encrypt a message + int message = 12345; + CDataStream ser(SER_NETWORK, PROTOCOL_VERSION); + ser << message; + AEADEncryptedData data = AEAD::encrypt(prekey, "Associated data", ser); + + // Replace key commitment + data.key_commitment = evil_key_commitment; + + // Decrypt; this should fail + BOOST_CHECK_THROW(ser = AEAD::decrypt_and_verify(prekey, "Associated data", data), std::runtime_error); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/libspark/util.cpp b/src/libspark/util.cpp index eecf41ce45..2e785f2879 100644 --- a/src/libspark/util.cpp +++ b/src/libspark/util.cpp @@ -149,6 +149,18 @@ std::vector SparkUtils::kdf_aead(const GroupElement& K_der) { return kdf.finalize(); } +// Derive a ChaCha20 key commitment for AEAD operations +std::vector SparkUtils::commit_aead(const GroupElement& K_der) { + // We use a KDF here because of the output size + KDF kdf(LABEL_COMMIT_AEAD, AEAD_COMMIT_SIZE); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << K_der; + kdf.include(stream); + + return kdf.finalize(); +} + // Hash-to-group function H_div GroupElement SparkUtils::hash_div(const std::vector& d) { Hash hash(LABEL_HASH_DIV); diff --git a/src/libspark/util.h b/src/libspark/util.h index 9d3d790f91..e94f0ade28 100644 --- a/src/libspark/util.h +++ b/src/libspark/util.h @@ -53,11 +53,13 @@ const std::string LABEL_HASH_BIND = "BIND"; // KDF labels const std::string LABEL_KDF_DIVERSIFIER = "DIVERSIFIER"; const std::string LABEL_KDF_AEAD = "AEAD"; +const std::string LABEL_COMMIT_AEAD = "COMMIT_AEAD"; // AEAD constants const int AEAD_IV_SIZE = 12; // byte length of the IV const int AEAD_KEY_SIZE = 32; // byte length of the key const int AEAD_TAG_SIZE = 16; // byte length of the tag +const int AEAD_COMMIT_SIZE = 32; // byte length of the key commitment class SparkUtils { public: @@ -76,6 +78,7 @@ class SparkUtils { // Key derivation functions static std::vector kdf_diversifier(const Scalar& s1); static std::vector kdf_aead(const GroupElement& K_der); + static std::vector commit_aead(const GroupElement& K_der); // Diversifier encryption/decryption static std::vector diversifier_encrypt(const std::vector& key, const uint64_t i); From 2d6ea6d69bb566f55fc7f6913aa48e3aeb1b7734 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 11 Sep 2022 04:19:24 +0400 Subject: [PATCH 041/197] Use base64 to make transferred data smaller --- src/rpc/misc.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index c67111626f..ce0aa1beef 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -930,7 +930,7 @@ UniValue getanonymityset(const JSONRPCRequest& request) "\nArguments:\n" "{\n" " \"coinGroupId\" (int)\n" - " \"startBlockHash\" (string)\n" + " \"startBlockHash\" (string)\n" // if this is empty it returns the full set "}\n" "\nResult:\n" "{\n" @@ -938,8 +938,8 @@ UniValue getanonymityset(const JSONRPCRequest& request) " \"setHash\" (string) Anonymity set hash\n" " \"mints\" (Pair>) Serialized GroupElements paired with txhash which is paired with mint tag and mint value\n" "}\n" - + HelpExampleCli("getanonymityset", "100000000 1") - + HelpExampleRpc("getanonymityset", "\"100000000\", \"1\"") + + HelpExampleCli("getanonymityset", "\"1\"" "\"f2d16ca8c1e220912f11dfe0797c88b367bcbb9b8c13aa2bc5892114da47f7b7\"") + + HelpExampleRpc("getanonymityset", "\"1\"" "\"f2d16ca8c1e220912f11dfe0797c88b367bcbb9b8c13aa2bc5892114da47f7b7\"") ); @@ -977,22 +977,22 @@ UniValue getanonymityset(const JSONRPCRequest& request) for (const auto& coin : coins) { std::vector vch = coin.first.getValue().getvch(); std::vector data; - data.push_back(HexStr(vch.begin(), vch.end())); - data.push_back(coin.second.second.GetHex()); + data.push_back(EncodeBase64(vch.data(), size_t(34))); + data.push_back(EncodeBase64(coin.second.second.begin(), coin.second.second.size())); if (coin.second.first.isJMint) { - data.push_back(HexStr(coin.second.first.encryptedValue.begin(), coin.second.first.encryptedValue.end())); + data.push_back(EncodeBase64(coin.second.first.encryptedValue.data(), coin.second.first.encryptedValue.size())); } else { data.push_back(coin.second.first.amount); } - data.push_back(txHashes[i].GetHex()); + data.push_back(EncodeBase64(txHashes[i].begin(), txHashes[i].size())); UniValue entity(UniValue::VARR); entity.push_backV(data); mints.push_back(entity); i++; } - ret.push_back(Pair("blockHash", blockHash.GetHex())); - ret.push_back(Pair("setHash", UniValue(HexStr(setHash.begin(), setHash.end())))); + ret.push_back(Pair("blockHash", EncodeBase64(blockHash.begin(), blockHash.size()))); + ret.push_back(Pair("setHash", UniValue(EncodeBase64(setHash.data(), setHash.size())))); ret.push_back(Pair("coins", mints)); return ret; @@ -1079,7 +1079,10 @@ UniValue getusedcoinserials(const JSONRPCRequest& request) for ( auto it = serials.begin(); it != serials.end(); ++it, ++i) { if (i < startNumber) continue; - serializedSerials.push_back(it->first.GetHex()); + std::vector serialized; + serialized.resize(32); + it->first.serialize(serialized.data()); + serializedSerials.push_back(EncodeBase64(serialized.data(), 32)); } UniValue ret(UniValue::VOBJ); From eb271336c5333ff625f13d10acd45e8af251a3be Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Tue, 13 Sep 2022 17:00:55 +0400 Subject: [PATCH 042/197] Spark balance proof fixed --- src/libspark/spend_transaction.cpp | 4 ++-- src/libspark/test/spend_transaction_test.cpp | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index 354cdca15e..103ec2f943 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -161,7 +161,7 @@ SpendTransaction::SpendTransaction( balance_statement += this->out_coins[j].C.inverse(); balance_witness -= SparkUtils::hash_val(k[j]); } - balance_statement += this->params->get_G()*Scalar(f); + balance_statement += (this->params->get_G()*Scalar(f)).inverse(); schnorr.prove( balance_witness, balance_statement, @@ -306,7 +306,7 @@ bool SpendTransaction::verify(const Params* params, const std::vectorget_G()*Scalar(tx.f); + balance_statement += (tx.params->get_G()*Scalar(tx.f)).inverse(); if(!schnorr.verify( balance_statement, tx.balance_proof diff --git a/src/libspark/test/spend_transaction_test.cpp b/src/libspark/test/spend_transaction_test.cpp index 66bee82a27..035b6333e0 100644 --- a/src/libspark/test/spend_transaction_test.cpp +++ b/src/libspark/test/spend_transaction_test.cpp @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(generate_verify) Scalar k; k.randomize(); - uint64_t v = 12 + i; // arbitrary value + uint64_t v = 123 + i; // arbitrary value in_coins.emplace_back(Coin( params, @@ -79,7 +79,7 @@ BOOST_AUTO_TEST_CASE(generate_verify) spend_coin_data.back().T = recovered_coin_data.T; spend_coin_data.back().v = identified_coin_data.v; - f -= identified_coin_data.v; + f += identified_coin_data.v; } // Generate new output coins and compute the fee @@ -88,20 +88,21 @@ BOOST_AUTO_TEST_CASE(generate_verify) for (std::size_t j = 0; j < t; j++) { out_coin_data.emplace_back(); out_coin_data.back().address = address; - out_coin_data.back().v = 123 + j; // arbitrary value + out_coin_data.back().v = 12 + j; // arbitrary value out_coin_data.back().memo = memo; - f += out_coin_data.back().v; + f -= out_coin_data.back().v; } // Assert the fee is correct uint64_t fee_test = f; - for (std::size_t u = 0; u < w; u++) { - fee_test += spend_coin_data[u].v; - } for (std::size_t j = 0; j < t; j++) { - fee_test -= out_coin_data[j].v; + fee_test += out_coin_data[j].v; } + for (std::size_t u = 0; u < w; u++) { + fee_test -= spend_coin_data[u].v; + } + if (fee_test != 0) { throw std::runtime_error("Bad fee assertion"); } From af049475b82b773375a83b9ac60f88f18556093e Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Wed, 14 Sep 2022 06:25:08 +0400 Subject: [PATCH 043/197] Index for mobile api optimized --- src/coin_containers.h | 2 ++ src/lelantus.cpp | 8 ++++---- src/lelantus.h | 3 +-- src/rpc/misc.cpp | 8 ++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/coin_containers.h b/src/coin_containers.h index a9ea16d630..15da1224a1 100644 --- a/src/coin_containers.h +++ b/src/coin_containers.h @@ -65,6 +65,7 @@ struct MintValueData { bool isJMint = false; uint64_t amount; std::vector encryptedValue; + uint256 txHash; ADD_SERIALIZE_METHODS; template @@ -73,6 +74,7 @@ struct MintValueData { READWRITE(isJMint); READWRITE(amount); READWRITE(encryptedValue); + READWRITE(txHash); } }; diff --git a/src/lelantus.cpp b/src/lelantus.cpp index 905fa98468..13a181e79e 100644 --- a/src/lelantus.cpp +++ b/src/lelantus.cpp @@ -1223,6 +1223,9 @@ void CLelantusState::AddMintsToStateAndBlockIndex( if (pblock->lelantusTxInfo->encryptedJmintValues.count(mint.first) > 0) { mintdata.isJMint = true; mintdata.encryptedValue = pblock->lelantusTxInfo->encryptedJmintValues[mint.first]; + COutPoint outPoint; + GetOutPointFromBlock(outPoint, mint.first.getValue(), *pblock); + mintdata.txHash = outPoint.hash; } blockMints.push_back(std::make_pair(mint.first, std::make_pair(mintdata, mint.second.second))); @@ -1498,11 +1501,9 @@ void CLelantusState::GetCoinsForRecovery( std::string start_block_hash, uint256& blockHash_out, std::vector>>& coins, - std::vector& setHash_out, - std::vector& txHashes) { + std::vector& setHash_out) { coins.clear(); - txHashes.clear(); if (coinGroups.count(coinGroupID) == 0) { return; } @@ -1548,7 +1549,6 @@ void CLelantusState::GetCoinsForRecovery( } coins.push_back(coin); - txHashes.push_back(GetTxHashFromPubcoin(coin.first)); } } } diff --git a/src/lelantus.h b/src/lelantus.h index a74e4e3518..d86aeb1c45 100644 --- a/src/lelantus.h +++ b/src/lelantus.h @@ -208,8 +208,7 @@ friend bool BuildLelantusStateFromIndex(CChain *, std::set &); std::string start_block_hash, uint256& blockHash_out, std::vector>>& coins, - std::vector& setHash_out, - std::vector& txHashes); + std::vector& setHash_out); // Return height of mint transaction and id of minted coin std::pair GetMintedCoinHeightAndId(const lelantus::PublicCoin& pubCoin); diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index ce0aa1beef..c0a7494053 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -955,7 +955,7 @@ UniValue getanonymityset(const JSONRPCRequest& request) uint256 blockHash; std::vector>> coins; std::vector setHash; - std::vector txHashes; + { LOCK(cs_main); lelantus::CLelantusState* lelantusState = lelantus::CLelantusState::GetState(); @@ -966,8 +966,7 @@ UniValue getanonymityset(const JSONRPCRequest& request) startBlockHash, blockHash, coins, - setHash, - txHashes); + setHash); } UniValue ret(UniValue::VOBJ); @@ -984,7 +983,8 @@ UniValue getanonymityset(const JSONRPCRequest& request) } else { data.push_back(coin.second.first.amount); } - data.push_back(EncodeBase64(txHashes[i].begin(), txHashes[i].size())); + data.push_back(EncodeBase64(coin.second.first.txHash.begin(), coin.second.first.txHash.size())); + UniValue entity(UniValue::VARR); entity.push_backV(data); mints.push_back(entity); From 852d73f40afde6ca17ad59016d3253d9699b129e Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Mon, 5 Sep 2022 13:59:05 -0500 Subject: [PATCH 044/197] Adds an encoding method for address scrambling --- src/Makefile.am | 4 +- src/Makefile.test.include | 2 + src/libspark/f4grumble.cpp | 156 ++++++++++++++++++++++++++ src/libspark/f4grumble.h | 32 ++++++ src/libspark/keys.cpp | 162 ++++++++++++++++++--------- src/libspark/keys.h | 8 +- src/libspark/test/address_test.cpp | 42 +++++++ src/libspark/test/f4grumble_test.cpp | 125 +++++++++++++++++++++ src/libspark/util.h | 10 ++ 9 files changed, 487 insertions(+), 54 deletions(-) create mode 100644 src/libspark/f4grumble.cpp create mode 100644 src/libspark/f4grumble.h create mode 100644 src/libspark/test/address_test.cpp create mode 100644 src/libspark/test/f4grumble_test.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 2882d44bf3..947331c360 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -668,7 +668,9 @@ libspark_a_SOURCES = \ libspark/mint_transaction.h \ libspark/mint_transaction.cpp \ libspark/spend_transaction.h \ - libspark/spend_transaction.cpp + libspark/spend_transaction.cpp \ + libspark/f4grumble.h \ + libspark/f4grumble.cpp liblelantus_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) liblelantus_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 27923c3a78..fe519a8ade 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -100,6 +100,8 @@ BITCOIN_TESTS = \ libspark/test/coin_test.cpp \ libspark/test/mint_transaction_test.cpp \ libspark/test/spend_transaction_test.cpp \ + libspark/test/f4grumble_test.cpp \ + libspark/test/address_test.cpp \ sigma/test/coin_spend_tests.cpp \ sigma/test/coin_tests.cpp \ sigma/test/primitives_tests.cpp \ diff --git a/src/libspark/f4grumble.cpp b/src/libspark/f4grumble.cpp new file mode 100644 index 0000000000..dc7e94dd50 --- /dev/null +++ b/src/libspark/f4grumble.cpp @@ -0,0 +1,156 @@ +// A design for address scrambling based on `f4jumble`: https://zips.z.cash/zip-0316#jumbling +// This design differs from `f4jumble` to account for OpenSSL limitations on Blake2b +// These limitations are unfortunate, but such is life sometimes +// +// Namely, we have the following dependency limitations on Blake2b: +// - Output is fixed at either 256 or 512 bits +// - Personalization is not supported +// +// To account for these limitations, we do the following: +// - Place extra restrictions on length to avoid XOF input encoding (and because we don't need it) +// - Replace personalization with fixed-length inputs; note that length is NOT prepended +// - Truncate outputs to the proper length +// +// Additionally, we account for the number of rounds by limiting the round counter encoding + +#include "f4grumble.h" + +namespace spark { + +using namespace secp_primitives; + +// Compute the XOR of two byte vectors +std::vector F4Grumble::vec_xor(const std::vector& x, const std::vector& y) { + if (x.size() != y.size()) { + throw std::invalid_argument("Mismatched vector sizes"); + } + + std::vector result; + result.reserve(x.size()); + for (std::size_t i = 0; i < x.size(); i++) { + result.emplace_back(x[i] ^ y[i]); + } + + return result; +} + +// Return the maximum allowed input size in bytes +std::size_t F4Grumble::get_max_size() { + return 2 * EVP_MD_size(EVP_blake2b512()); +} + +// Instantiate with a given network identifier and expected input length +F4Grumble::F4Grumble(const unsigned char network, const int l_M) { + // Assert the length is valid + if (l_M > 2 * EVP_MD_size(EVP_blake2b512())) { + throw std::invalid_argument("Bad address size"); + } + + this->network = network; + this->l_M = l_M; + this->l_L = l_M / 2; + this->l_R = l_M - l_L; +} + +// Encode the input data +std::vector F4Grumble::encode(const std::vector& input) { + // Check the input size + if (input.size() != l_M) { + throw std::invalid_argument("Bad address size"); + } + + // Split the input + std::vector a = std::vector(input.begin(), input.begin() + this->l_M / 2); + std::vector b = std::vector(input.begin() + this->l_M / 2, input.end()); + + // Perform the Feistel operations + std::vector x = vec_xor(b, G(0, a)); + std::vector y = vec_xor(a, H(0, x)); + std::vector d = vec_xor(x, G(1, y)); + std::vector c = vec_xor(y, H(1, d)); + + // Return the concatenation + std::vector result(c); + result.insert(result.end(), d.begin(), d.end()); + return result; +} + +// Decode the input data +std::vector F4Grumble::decode(const std::vector& input) { + // Check the input size + if (input.size() != l_M) { + throw std::invalid_argument("Bad address size"); + } + + // Split the input + std::vector c = std::vector(input.begin(), input.begin() + this->l_M / 2); + std::vector d = std::vector(input.begin() + this->l_M / 2, input.end()); + + // Perform the Feistel operations + std::vector y = vec_xor(c, H(1, d)); + std::vector x = vec_xor(d, G(1, y)); + std::vector a = vec_xor(y, H(0, x)); + std::vector b = vec_xor(x, G(0, a)); + + // Return the concatenation + std::vector result(a); + result.insert(result.end(), b.begin(), b.end()); + return result; +} + +// Feistel round functions +std::vector F4Grumble::G(const unsigned char i, const std::vector& u) { + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_blake2b512(), NULL); + + // Bind the domain separator and network + std::vector domain(LABEL_F4GRUMBLE_G.begin(), LABEL_F4GRUMBLE_G.end()); + EVP_DigestUpdate(ctx, domain.data(), domain.size()); + EVP_DigestUpdate(ctx, &this->network, sizeof(this->network)); + + // Include the round index + EVP_DigestUpdate(ctx, &i, sizeof(i)); + + // Include the input data + EVP_DigestUpdate(ctx, u.data(), u.size()); + + // Finalize the hash and resize + std::vector result; + result.resize(EVP_MD_size(EVP_blake2b512())); + + unsigned int TEMP; + EVP_DigestFinal_ex(ctx, result.data(), &TEMP); + EVP_MD_CTX_free(ctx); + result.resize(this->l_R); + + return result; +} + +std::vector F4Grumble::H(const unsigned char i, const std::vector& u) { + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_blake2b512(), NULL); + + // Bind the domain separator and network + std::vector domain(LABEL_F4GRUMBLE_H.begin(), LABEL_F4GRUMBLE_H.end()); + EVP_DigestUpdate(ctx, domain.data(), domain.size()); + EVP_DigestUpdate(ctx, &this->network, sizeof(this->network)); + + // Include the round index + EVP_DigestUpdate(ctx, &i, sizeof(i)); + + // Include the input data + EVP_DigestUpdate(ctx, u.data(), u.size()); + + // Finalize the hash and resize + std::vector result; + result.resize(EVP_MD_size(EVP_blake2b512())); + + unsigned int TEMP; + EVP_DigestFinal_ex(ctx, result.data(), &TEMP); + EVP_MD_CTX_free(ctx); + result.resize(this->l_L); + + return result; +} + +} diff --git a/src/libspark/f4grumble.h b/src/libspark/f4grumble.h new file mode 100644 index 0000000000..6c9fbb47bd --- /dev/null +++ b/src/libspark/f4grumble.h @@ -0,0 +1,32 @@ +#ifndef FIRO_SPARK_F4GRUMBLE_H +#define FIRO_SPARK_F4GRUMBLE_H +#include +#include "util.h" + +namespace spark { + +using namespace secp_primitives; + +class F4Grumble { +public: + F4Grumble(const unsigned char network, const int l_M); + + std::vector encode(const std::vector& input); + std::vector decode(const std::vector& input); + + static std::size_t get_max_size(); + +private: + static std::vector vec_xor(const std::vector& x, const std::vector& y); + + // The internal Feistel round functions + std::vector G(const unsigned char i, const std::vector& u); + std::vector H(const unsigned char i, const std::vector& u); + + unsigned char network; + int l_M, l_L, l_R; +}; + +} + +#endif diff --git a/src/libspark/keys.cpp b/src/libspark/keys.cpp index 29c361f89d..b41c7af0a1 100644 --- a/src/libspark/keys.cpp +++ b/src/libspark/keys.cpp @@ -1,5 +1,9 @@ #include "keys.h" +<<<<<<< HEAD #include "../hash.h" +======= +#include "f4grumble.h" +>>>>>>> 65e408e1b... Adds an encoding method for address scrambling namespace spark { @@ -151,59 +155,115 @@ const GroupElement& Address::get_Q2() const { return this->Q2; } -std::string Address::GetHex() const { - const std::size_t size = 2* GroupElement::serialize_size + AES_BLOCKSIZE; - std::vector buffer; - buffer.reserve(size); - buffer.resize(2* GroupElement::serialize_size); - unsigned char* ptr = buffer.data(); - ptr = Q1.serialize(ptr); - Q2.serialize(ptr); - buffer.insert(buffer.end(), d.begin(), d.end()); - - std::stringstream ss; - ss << std::hex; - ss << version; - - for (const auto b : buffer) { - ss << (b >> 4); - ss << (b & 0xF); - } - - std::string str = ss.str(); - uint160 checksum = Hash160(str.begin(), str.end()); - ss << checksum.GetHex(); - return ss.str(); -} - -void Address::SetHex(const std::string& str) { - const std::size_t size = 2 * GroupElement::serialize_size + AES_BLOCKSIZE; - if (str.size() != ((size + 20) * 2 + 1)) { - throw "Address: SetHex failed, invalid length"; - } - - version = *str.c_str(); - std::array buffer; - for (std::size_t i = 0; i < buffer.size(); i++) { - auto hexs = str.substr(2 * i + 1, 2); - - if (::isxdigit(hexs[0]) && ::isxdigit(hexs[1])) { - buffer[i] = strtol(hexs.c_str(), NULL, 16); +// Compute a CRC32-C checksum and convert to hex encoding +std::string Address::get_checksum(const std::string data) { + uint32_t checksum = leveldb::crc32c::Value(data.data(), data.size()); + + // Get bytes + std::vector bytes; + bytes.resize(4); + bytes[0] = checksum; + bytes[1] = checksum >> 8; + bytes[2] = checksum >> 16; + bytes[3] = checksum >> 24; + + // Hex encode + std::stringstream result; + result << std::hex; + for (const unsigned char b : bytes) { + result << (b >> 4); + result << (b & 0xF); + } + return result.str(); +} + +// Encode the address to string, given a network identifier +std::string Address::encode(const unsigned char network) const { + // Serialize the address components + std::vector raw; + raw.reserve(2 * GroupElement::serialize_size + AES_BLOCKSIZE); + + raw.insert(raw.end(), this->d.begin(), this->d.end()); + + std::vector component; + component.resize(GroupElement::serialize_size); + + this->get_Q1().serialize(component.data()); + raw.insert(raw.end(), component.begin(), component.end()); + + this->get_Q2().serialize(component.data()); + raw.insert(raw.end(), component.begin(), component.end()); + + // Apply the scramble encoding and prepend the network byte + std::vector scrambled = F4Grumble(network, raw.size()).encode(raw); + + // Encode to hex + std::stringstream encoded; + encoded << std::hex; + encoded << ADDRESS_ENCODING_PREFIX; + encoded << network; + for (const unsigned char c : scrambled) { + encoded << (c >> 4); + encoded << (c & 0xF); + } + + // Compute and apply the checksum + encoded << get_checksum(encoded.str()); + + return encoded.str(); +} + +// Decode an address (if possible) from a string, returning the network identifier +unsigned char Address::decode(const std::string& str) { + const int CHECKSUM_BYTES = 4; + + // Assert the proper address size + if (str.size() != (2 * GroupElement::serialize_size + AES_BLOCKSIZE + CHECKSUM_BYTES) * 2 + 2) { + throw std::invalid_argument("Bad address size"); + } + + // Check the encoding prefix + if (str[0] != ADDRESS_ENCODING_PREFIX) { + throw std::invalid_argument("Bad address prefix"); + } + + // Check the checksum + std::string checksum = str.substr(str.size() - 2 * CHECKSUM_BYTES); + std::string computed_checksum = get_checksum(str.substr(0, str.size() - 2 * CHECKSUM_BYTES)); + if (computed_checksum != checksum) { + throw std::invalid_argument("Bad address checksum"); + } + + // Track the network identifier + unsigned char network = str[1]; + + // Decode the scrambled data and checksum from hex + std::string scrambled_hex = str.substr(2, str.size() - CHECKSUM_BYTES); + std::vector scrambled; + scrambled.resize(2 * GroupElement::serialize_size + AES_BLOCKSIZE); + for (std::size_t i = 0; i < scrambled.size(); i++) { + std::string hexs = scrambled_hex.substr(2 * i, 2); + + if (::isxdigit(hexs[0]) && ::isxdigit(hexs[1])) { + scrambled[i] = strtol(hexs.c_str(), NULL, 16); } else { - throw "Address: SetHex failed, invalid hex"; + throw std::invalid_argument("Bad address encoding"); } - } - - const unsigned char* ptr = Q1.deserialize(buffer.data()); - Q2.deserialize(ptr); - d.insert(d.end(), buffer.begin() + 2 * GroupElement::serialize_size, buffer.end()); - - // check for checksum validity - std::string checksum = str.substr (size * 2 + 1, str.size() - 1); - // get checksum from newly deserialized address to compare with checksum form input - std::string resultChecksum= GetHex().substr (size * 2 + 1, str.size() - 1); - if (checksum != resultChecksum) - throw "Address: SetHex failed, invalid checksum"; + } + + // Apply the scramble decoding + std::vector raw = F4Grumble(network, scrambled.size()).decode(scrambled); + + // Deserialize the adddress components + this->d = std::vector(raw.begin(), raw.begin() + AES_BLOCKSIZE); + + std::vector component(raw.begin() + AES_BLOCKSIZE, raw.begin() + AES_BLOCKSIZE + GroupElement::serialize_size); + this->Q1.deserialize(component.data()); + + component = std::vector(raw.begin() + AES_BLOCKSIZE + GroupElement::serialize_size, raw.end()); + this->Q2.deserialize(component.data()); + + return network; } } diff --git a/src/libspark/keys.h b/src/libspark/keys.h index eae985c063..25222b1b8a 100644 --- a/src/libspark/keys.h +++ b/src/libspark/keys.h @@ -1,5 +1,6 @@ #ifndef FIRO_SPARK_KEYS_H #define FIRO_SPARK_KEYS_H +#include "../leveldb/util/crc32c.h" #include "params.h" #include "util.h" @@ -76,14 +77,17 @@ class Address { const std::vector& get_d() const; const GroupElement& get_Q1() const; const GroupElement& get_Q2() const; - std::string GetHex() const; - void SetHex(const std::string& str); + + std::string encode(const unsigned char network) const; + unsigned char decode(const std::string& str); private: char version = SPARK_ADDRESS_VERSION; const Params* params; std::vector d; GroupElement Q1, Q2; + + static std::string get_checksum(const std::string data); }; } diff --git a/src/libspark/test/address_test.cpp b/src/libspark/test/address_test.cpp new file mode 100644 index 0000000000..13531c616b --- /dev/null +++ b/src/libspark/test/address_test.cpp @@ -0,0 +1,42 @@ +#include "../keys.h" + +#include "../../test/test_bitcoin.h" +#include + +namespace spark { + +using namespace secp_primitives; + +BOOST_FIXTURE_TEST_SUITE(spark_address_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(encoding) +{ + // Parameters + const Params* params; + params = Params::get_test(); + + // Generate keys + SpendKey spend_key(params); + FullViewKey full_view_key(spend_key); + IncomingViewKey incoming_view_key(full_view_key); + + // Generate address + const uint64_t i = 12345; + Address address(incoming_view_key, i); + + // Encode address + std::string encoded = address.encode(ADDRESS_NETWORK_TESTNET); + + // Decode address + Address decoded; + decoded.decode(encoded); + + // Check correctness + BOOST_CHECK_EQUAL_COLLECTIONS(address.get_d().begin(), address.get_d().end(), decoded.get_d().begin(), decoded.get_d().end()); + BOOST_CHECK_EQUAL(address.get_Q1(), decoded.get_Q1()); + BOOST_CHECK_EQUAL(address.get_Q2(), decoded.get_Q2()); +} + +BOOST_AUTO_TEST_SUITE_END() + +} diff --git a/src/libspark/test/f4grumble_test.cpp b/src/libspark/test/f4grumble_test.cpp new file mode 100644 index 0000000000..15348d8749 --- /dev/null +++ b/src/libspark/test/f4grumble_test.cpp @@ -0,0 +1,125 @@ +#include "../f4grumble.h" + +#include "../../test/test_bitcoin.h" +#include + +#include + +namespace spark { + +BOOST_FIXTURE_TEST_SUITE(spark_f4grumble_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(complete) +{ + // Test all sizes of interest + const int MIN_SIZE = 0; // sure, why not + const int MAX_SIZE = 128; + + // Set up the randomizer + std::random_device rand; + std::uniform_int_distribution dist(0, 0xFF); + + for (int i = MIN_SIZE; i <= MAX_SIZE; i++) { + // Generate a random byte array + std::vector input; + input.reserve(i); + + for (int j = 0; j < i; j++) { + input.emplace_back(static_cast(dist(rand))); + } + + // Pick a network byte and set up the encoder + unsigned char network = static_cast(dist(rand)); + F4Grumble grumble(network, i); + + // Encode the byte array + std::vector scrambled = grumble.encode(input); + + // Check that the length has not changed + BOOST_CHECK_EQUAL(scrambled.size(), input.size()); + + // Decode and check correctness + std::vector unscrambled = grumble.decode(scrambled); + BOOST_CHECK_EQUAL_COLLECTIONS(unscrambled.begin(), unscrambled.end(), input.begin(), input.end()); + } +} + +BOOST_AUTO_TEST_CASE(too_long) +{ + // This size is invalid! + int size = F4Grumble::get_max_size() + 1; + + // Set up the randomizer + std::random_device rand; + std::uniform_int_distribution dist(0, 0xFF); + + // Generate a random byte array + std::vector input; + input.reserve(size); + + for (int j = 0; j < size; j++) { + input.emplace_back(static_cast(dist(rand))); + } + + // Pick a network byte + unsigned char network = static_cast(dist(rand)); + + // We can't even instantiate this! + BOOST_CHECK_THROW(F4Grumble grumble(network, size), std::invalid_argument); + + // But pretend we can + F4Grumble grumble(network, F4Grumble::get_max_size()); + + // We should not be able to encode this... + BOOST_CHECK_THROW(grumble.encode(input), std::invalid_argument); + + // ... nor decode it + BOOST_CHECK_THROW(grumble.decode(input), std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(bad_network) +{ + // Choose a large input size (such that collisions are unlikely) + int size = F4Grumble::get_max_size(); + + // Set up the randomizer + std::random_device rand; + std::uniform_int_distribution dist(0, 0xFF); + + // Generate a random byte array + std::vector input; + input.reserve(size); + + for (int j = 0; j < size; j++) { + input.emplace_back(static_cast(dist(rand))); + } + + // Pick a network byte + unsigned char network = static_cast(dist(rand)); + + // Pick an evil network byte + unsigned char evil_network = ~network; + BOOST_CHECK_NE(network, evil_network); + + // Encode with the original network + F4Grumble grumble(network, size); + std::vector scrambled = grumble.encode(input); + + // Encode with the evil network + F4Grumble evil_grumble(evil_network, size); + std::vector evil_scrambled = evil_grumble.decode(input); + + // They should be distinct + bool equal = true; + BOOST_CHECK_EQUAL(scrambled.size(), evil_scrambled.size()); + for (std::size_t i = 0; i < scrambled.size(); i++) { + if (scrambled[i] != evil_scrambled[i]) { + equal = false; + } + } + BOOST_CHECK(!equal); +} + +BOOST_AUTO_TEST_SUITE_END() + +} \ No newline at end of file diff --git a/src/libspark/util.h b/src/libspark/util.h index e94f0ade28..39fd429731 100644 --- a/src/libspark/util.h +++ b/src/libspark/util.h @@ -49,6 +49,8 @@ const std::string LABEL_HASH_VAL = "VAL"; const std::string LABEL_HASH_SER1 = "SER1"; const std::string LABEL_HASH_VAL1 = "VAL1"; const std::string LABEL_HASH_BIND = "BIND"; +const std::string LABEL_F4GRUMBLE_G = "SPARK_F4GRUMBLE_G"; +const std::string LABEL_F4GRUMBLE_H = "SPARK_F4GRUMBLE_H"; // KDF labels const std::string LABEL_KDF_DIVERSIFIER = "DIVERSIFIER"; @@ -61,6 +63,14 @@ const int AEAD_KEY_SIZE = 32; // byte length of the key const int AEAD_TAG_SIZE = 16; // byte length of the tag const int AEAD_COMMIT_SIZE = 32; // byte length of the key commitment +// Address encoding prefix +const unsigned char ADDRESS_ENCODING_PREFIX = 'p'; + +// Address encoding network identifiers +// TODO: Extend/update/replace these as needed! These are just initial examples +const unsigned char ADDRESS_NETWORK_MAINNET = 'm'; +const unsigned char ADDRESS_NETWORK_TESTNET = 't'; + class SparkUtils { public: // Protocol-level hash functions From 002eef2088ccc7e5f87a4f16155eda980d915f21 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 16 Sep 2022 18:58:05 +0400 Subject: [PATCH 045/197] Fix a typo --- src/lelantus.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lelantus.cpp b/src/lelantus.cpp index 13a181e79e..4c53bce46b 100644 --- a/src/lelantus.cpp +++ b/src/lelantus.cpp @@ -1223,11 +1223,12 @@ void CLelantusState::AddMintsToStateAndBlockIndex( if (pblock->lelantusTxInfo->encryptedJmintValues.count(mint.first) > 0) { mintdata.isJMint = true; mintdata.encryptedValue = pblock->lelantusTxInfo->encryptedJmintValues[mint.first]; - COutPoint outPoint; - GetOutPointFromBlock(outPoint, mint.first.getValue(), *pblock); - mintdata.txHash = outPoint.hash; } + COutPoint outPoint; + GetOutPointFromBlock(outPoint, mint.first.getValue(), *pblock); + mintdata.txHash = outPoint.hash; + blockMints.push_back(std::make_pair(mint.first, std::make_pair(mintdata, mint.second.second))); } From c334ccb05aff338cd6bd50b511a760eaa5cb118a Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Fri, 16 Sep 2022 13:35:06 -0500 Subject: [PATCH 046/197] Switch from hex to `bech32m` encoding --- src/Makefile.am | 4 +- src/libspark/bech32.cpp | 248 ++++++++++++++++++++++++++++++++++++++++ src/libspark/bech32.h | 63 ++++++++++ src/libspark/keys.cpp | 88 ++++---------- src/libspark/keys.h | 3 +- 5 files changed, 339 insertions(+), 67 deletions(-) create mode 100644 src/libspark/bech32.cpp create mode 100644 src/libspark/bech32.h diff --git a/src/Makefile.am b/src/Makefile.am index 947331c360..9ef5f432f4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -670,7 +670,9 @@ libspark_a_SOURCES = \ libspark/spend_transaction.h \ libspark/spend_transaction.cpp \ libspark/f4grumble.h \ - libspark/f4grumble.cpp + libspark/f4grumble.cpp \ + libspark/bech32.h \ + libspark/bech32.cpp liblelantus_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) liblelantus_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) diff --git a/src/libspark/bech32.cpp b/src/libspark/bech32.cpp new file mode 100644 index 0000000000..67b76c726b --- /dev/null +++ b/src/libspark/bech32.cpp @@ -0,0 +1,248 @@ +/* Copyright (c) 2017, 2021 Pieter Wuille + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Copyright (c) 2017 Pieter Wuille +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "bech32.h" + +#include +#include + +#include +#include + +namespace bech32 +{ + +namespace +{ + +typedef std::vector data; + +/** The Bech32 character set for encoding. */ +const char* CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +/** The Bech32 character set for decoding. */ +const int8_t CHARSET_REV[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 +}; + +/** Concatenate two byte arrays. */ +data cat(data x, const data& y) { + x.insert(x.end(), y.begin(), y.end()); + return x; +} + +/* Determine the final constant to use for the specified encoding. */ +uint32_t encoding_constant(Encoding encoding) { + assert(encoding == Encoding::BECH32 || encoding == Encoding::BECH32M); + return encoding == Encoding::BECH32 ? 1 : 0x2bc830a3; +} + +/** This function will compute what 6 5-bit values to XOR into the last 6 input values, in order to + * make the checksum 0. These 6 values are packed together in a single 30-bit integer. The higher + * bits correspond to earlier values. */ +uint32_t polymod(const data& values) +{ + // The input is interpreted as a list of coefficients of a polynomial over F = GF(32), with an + // implicit 1 in front. If the input is [v0,v1,v2,v3,v4], that polynomial is v(x) = + // 1*x^5 + v0*x^4 + v1*x^3 + v2*x^2 + v3*x + v4. The implicit 1 guarantees that + // [v0,v1,v2,...] has a distinct checksum from [0,v0,v1,v2,...]. + + // The output is a 30-bit integer whose 5-bit groups are the coefficients of the remainder of + // v(x) mod g(x), where g(x) is the Bech32 generator, + // x^6 + {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18}. g(x) is chosen in such a way + // that the resulting code is a BCH code, guaranteeing detection of up to 3 errors within a + // window of 1023 characters. Among the various possible BCH codes, one was selected to in + // fact guarantee detection of up to 4 errors within a window of 89 characters. + + // Note that the coefficients are elements of GF(32), here represented as decimal numbers + // between {}. In this finite field, addition is just XOR of the corresponding numbers. For + // example, {27} + {13} = {27 ^ 13} = {22}. Multiplication is more complicated, and requires + // treating the bits of values themselves as coefficients of a polynomial over a smaller field, + // GF(2), and multiplying those polynomials mod a^5 + a^3 + 1. For example, {5} * {26} = + // (a^2 + 1) * (a^4 + a^3 + a) = (a^4 + a^3 + a) * a^2 + (a^4 + a^3 + a) = a^6 + a^5 + a^4 + a + // = a^3 + 1 (mod a^5 + a^3 + 1) = {9}. + + // During the course of the loop below, `c` contains the bitpacked coefficients of the + // polynomial constructed from just the values of v that were processed so far, mod g(x). In + // the above example, `c` initially corresponds to 1 mod g(x), and after processing 2 inputs of + // v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value + // for `c`. + uint32_t c = 1; + for (const auto v_i : values) { + // We want to update `c` to correspond to a polynomial with one extra term. If the initial + // value of `c` consists of the coefficients of c(x) = f(x) mod g(x), we modify it to + // correspond to c'(x) = (f(x) * x + v_i) mod g(x), where v_i is the next input to + // process. Simplifying: + // c'(x) = (f(x) * x + v_i) mod g(x) + // ((f(x) mod g(x)) * x + v_i) mod g(x) + // (c(x) * x + v_i) mod g(x) + // If c(x) = c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5, we want to compute + // c'(x) = (c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5) * x + v_i mod g(x) + // = c0*x^6 + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i mod g(x) + // = c0*(x^6 mod g(x)) + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i + // If we call (x^6 mod g(x)) = k(x), this can be written as + // c'(x) = (c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i) + c0*k(x) + + // First, determine the value of c0: + uint8_t c0 = c >> 25; + + // Then compute c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i: + c = ((c & 0x1ffffff) << 5) ^ v_i; + + // Finally, for each set bit n in c0, conditionally add {2^n}k(x): + if (c0 & 1) c ^= 0x3b6a57b2; // k(x) = {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18} + if (c0 & 2) c ^= 0x26508e6d; // {2}k(x) = {19}x^5 + {5}x^4 + x^3 + {3}x^2 + {19}x + {13} + if (c0 & 4) c ^= 0x1ea119fa; // {4}k(x) = {15}x^5 + {10}x^4 + {2}x^3 + {6}x^2 + {15}x + {26} + if (c0 & 8) c ^= 0x3d4233dd; // {8}k(x) = {30}x^5 + {20}x^4 + {4}x^3 + {12}x^2 + {30}x + {29} + if (c0 & 16) c ^= 0x2a1462b3; // {16}k(x) = {21}x^5 + x^4 + {8}x^3 + {24}x^2 + {21}x + {19} + } + return c; +} + +/** Convert to lower case. */ +unsigned char lc(unsigned char c) { + return (c >= 'A' && c <= 'Z') ? (c - 'A') + 'a' : c; +} + +/** Expand a HRP for use in checksum computation. */ +data expand_hrp(const std::string& hrp) { + data ret; + ret.resize(hrp.size() * 2 + 1); + for (size_t i = 0; i < hrp.size(); ++i) { + unsigned char c = hrp[i]; + ret[i] = c >> 5; + ret[i + hrp.size() + 1] = c & 0x1f; + } + ret[hrp.size()] = 0; + return ret; +} + +/** Verify a checksum. */ +Encoding verify_checksum(const std::string& hrp, const data& values) { + // PolyMod computes what value to xor into the final values to make the checksum 0. However, + // if we required that the checksum was 0, it would be the case that appending a 0 to a valid + // list of values would result in a new valid list. For that reason, Bech32 requires the + // resulting checksum to be 1 instead. In Bech32m, this constant was amended. + uint32_t check = polymod(cat(expand_hrp(hrp), values)); + if (check == encoding_constant(Encoding::BECH32)) return Encoding::BECH32; + if (check == encoding_constant(Encoding::BECH32M)) return Encoding::BECH32M; + return Encoding::INVALID; +} + +data create_checksum(const std::string& hrp, const data& values, Encoding encoding) { + data enc = cat(expand_hrp(hrp), values); + enc.resize(enc.size() + 6); + uint32_t mod = polymod(enc) ^ encoding_constant(encoding); + data ret; + ret.resize(6); + for (size_t i = 0; i < 6; ++i) { + // Convert the 5-bit groups in mod to checksum values. + ret[i] = (mod >> (5 * (5 - i))) & 31; + } + return ret; +} + +} // namespace + +/** Encode a Bech32 or Bech32m string. */ +std::string encode(const std::string& hrp, const data& values, Encoding encoding) { + // First ensure that the HRP is all lowercase. BIP-173 requires an encoder + // to return a lowercase Bech32 string, but if given an uppercase HRP, the + // result will always be invalid. + for (const char& c : hrp) assert(c < 'A' || c > 'Z'); + data checksum = create_checksum(hrp, values, encoding); + data combined = cat(values, checksum); + std::string ret = hrp + '1'; + ret.reserve(ret.size() + combined.size()); + for (const auto c : combined) { + ret += CHARSET[c]; + } + return ret; +} + +/** Decode a Bech32 or Bech32m string. */ +DecodeResult decode(const std::string& str) { + bool lower = false, upper = false; + for (size_t i = 0; i < str.size(); ++i) { + unsigned char c = str[i]; + if (c >= 'a' && c <= 'z') lower = true; + else if (c >= 'A' && c <= 'Z') upper = true; + else if (c < 33 || c > 126) return {}; + } + if (lower && upper) return {}; + size_t pos = str.rfind('1'); + if (pos == str.npos || pos == 0 || pos + 7 > str.size()) { + return {}; + } + data values(str.size() - 1 - pos); + for (size_t i = 0; i < str.size() - 1 - pos; ++i) { + unsigned char c = str[i + pos + 1]; + int8_t rev = CHARSET_REV[c]; + + if (rev == -1) { + return {}; + } + values[i] = rev; + } + std::string hrp; + for (size_t i = 0; i < pos; ++i) { + hrp += lc(str[i]); + } + Encoding result = verify_checksum(hrp, values); + if (result == Encoding::INVALID) return {}; + return {result, std::move(hrp), data(values.begin(), values.end() - 6)}; +} + +/** Convert from one power-of-2 number base to another. */ +bool convertbits(std::vector& out, const std::vector& in, int frombits, int tobits, bool pad) { + int acc = 0; + int bits = 0; + const int maxv = (1 << tobits) - 1; + const int max_acc = (1 << (frombits + tobits - 1)) - 1; + for (size_t i = 0; i < in.size(); ++i) { + int value = in[i]; + acc = ((acc << frombits) | value) & max_acc; + bits += frombits; + while (bits >= tobits) { + bits -= tobits; + out.push_back((acc >> bits) & maxv); + } + } + if (pad) { + if (bits) out.push_back((acc << (tobits - bits)) & maxv); + } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { + return false; + } + return true; +} + +} // namespace bech32 diff --git a/src/libspark/bech32.h b/src/libspark/bech32.h new file mode 100644 index 0000000000..33dc430b59 --- /dev/null +++ b/src/libspark/bech32.h @@ -0,0 +1,63 @@ +/* Copyright (c) 2017, 2021 Pieter Wuille + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef BECH32_H_ +#define BECH32_H_ 1 + +#include +#include +#include + +#include + +namespace bech32 +{ + +enum class Encoding { + INVALID, + + BECH32, //! Bech32 encoding as defined in BIP173 + BECH32M, //! Bech32m encoding as defined in BIP350 +}; + +/** Encode a Bech32 or Bech32m string. If hrp contains uppercase characters, this will cause an + * assertion error. Encoding must be one of BECH32 or BECH32M. */ +std::string encode(const std::string& hrp, const std::vector& values, Encoding encoding); + +/** A type for the result of decoding. */ +struct DecodeResult +{ + Encoding encoding; //!< What encoding was detected in the result; Encoding::INVALID if failed. + std::string hrp; //!< The human readable part + std::vector data; //!< The payload (excluding checksum) + + DecodeResult() : encoding(Encoding::INVALID) {} + DecodeResult(Encoding enc, std::string&& h, std::vector&& d) : encoding(enc), hrp(std::move(h)), data(std::move(d)) {} +}; + +/** Decode a Bech32 or Bech32m string. */ +DecodeResult decode(const std::string& str); + +/** Convert from one power-of-2 number base to another. */ +bool convertbits(std::vector& out, const std::vector& in, int frombits, int tobits, bool pad); +} // namespace bech32 + +#endif // BECH32_H_ diff --git a/src/libspark/keys.cpp b/src/libspark/keys.cpp index b41c7af0a1..fa60f95758 100644 --- a/src/libspark/keys.cpp +++ b/src/libspark/keys.cpp @@ -1,9 +1,5 @@ #include "keys.h" -<<<<<<< HEAD #include "../hash.h" -======= -#include "f4grumble.h" ->>>>>>> 65e408e1b... Adds an encoding method for address scrambling namespace spark { @@ -155,28 +151,6 @@ const GroupElement& Address::get_Q2() const { return this->Q2; } -// Compute a CRC32-C checksum and convert to hex encoding -std::string Address::get_checksum(const std::string data) { - uint32_t checksum = leveldb::crc32c::Value(data.data(), data.size()); - - // Get bytes - std::vector bytes; - bytes.resize(4); - bytes[0] = checksum; - bytes[1] = checksum >> 8; - bytes[2] = checksum >> 16; - bytes[3] = checksum >> 24; - - // Hex encode - std::stringstream result; - result << std::hex; - for (const unsigned char b : bytes) { - result << (b >> 4); - result << (b & 0xF); - } - return result.str(); -} - // Encode the address to string, given a network identifier std::string Address::encode(const unsigned char network) const { // Serialize the address components @@ -197,58 +171,42 @@ std::string Address::encode(const unsigned char network) const { // Apply the scramble encoding and prepend the network byte std::vector scrambled = F4Grumble(network, raw.size()).encode(raw); - // Encode to hex - std::stringstream encoded; - encoded << std::hex; - encoded << ADDRESS_ENCODING_PREFIX; - encoded << network; - for (const unsigned char c : scrambled) { - encoded << (c >> 4); - encoded << (c & 0xF); - } - - // Compute and apply the checksum - encoded << get_checksum(encoded.str()); + // Encode using `bech32m` + std::string hrp; + hrp.push_back(ADDRESS_ENCODING_PREFIX); + hrp.push_back(network); - return encoded.str(); + std::vector bit_converted; + bech32::convertbits(bit_converted, scrambled, 8, 5, true); + + return bech32::encode(hrp, bit_converted, bech32::Encoding::BECH32M); } // Decode an address (if possible) from a string, returning the network identifier unsigned char Address::decode(const std::string& str) { - const int CHECKSUM_BYTES = 4; + // Decode using `bech32m` + bech32::DecodeResult decoded = bech32::decode(str); - // Assert the proper address size - if (str.size() != (2 * GroupElement::serialize_size + AES_BLOCKSIZE + CHECKSUM_BYTES) * 2 + 2) { - throw std::invalid_argument("Bad address size"); + // Check the encoding + if (decoded.encoding != bech32::Encoding::BECH32M) { + throw std::invalid_argument("Bad address encoding"); } // Check the encoding prefix - if (str[0] != ADDRESS_ENCODING_PREFIX) { + if (decoded.hrp[0] != ADDRESS_ENCODING_PREFIX) { throw std::invalid_argument("Bad address prefix"); } - // Check the checksum - std::string checksum = str.substr(str.size() - 2 * CHECKSUM_BYTES); - std::string computed_checksum = get_checksum(str.substr(0, str.size() - 2 * CHECKSUM_BYTES)); - if (computed_checksum != checksum) { - throw std::invalid_argument("Bad address checksum"); - } + // Get the network identifier + unsigned char network = decoded.hrp[1]; - // Track the network identifier - unsigned char network = str[1]; - - // Decode the scrambled data and checksum from hex - std::string scrambled_hex = str.substr(2, str.size() - CHECKSUM_BYTES); - std::vector scrambled; - scrambled.resize(2 * GroupElement::serialize_size + AES_BLOCKSIZE); - for (std::size_t i = 0; i < scrambled.size(); i++) { - std::string hexs = scrambled_hex.substr(2 * i, 2); - - if (::isxdigit(hexs[0]) && ::isxdigit(hexs[1])) { - scrambled[i] = strtol(hexs.c_str(), NULL, 16); - } else { - throw std::invalid_argument("Bad address encoding"); - } + // Convert the address components to bytes + std::vector scrambled; + bech32::convertbits(scrambled, decoded.data, 5, 8, false); + + // Assert the proper address size + if (scrambled.size() != 2 * GroupElement::serialize_size + AES_BLOCKSIZE) { + throw std::invalid_argument("Bad address size"); } // Apply the scramble decoding diff --git a/src/libspark/keys.h b/src/libspark/keys.h index 25222b1b8a..ebf3ae8ac0 100644 --- a/src/libspark/keys.h +++ b/src/libspark/keys.h @@ -1,6 +1,7 @@ #ifndef FIRO_SPARK_KEYS_H #define FIRO_SPARK_KEYS_H -#include "../leveldb/util/crc32c.h" +#include "bech32.h" +#include "f4grumble.h" #include "params.h" #include "util.h" From fb9a3a89f84c51a0a7eed77af192864171008317 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Fri, 16 Sep 2022 15:51:17 -0500 Subject: [PATCH 047/197] Add tests --- src/libspark/test/address_test.cpp | 84 +++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/src/libspark/test/address_test.cpp b/src/libspark/test/address_test.cpp index 13531c616b..8f50d910b3 100644 --- a/src/libspark/test/address_test.cpp +++ b/src/libspark/test/address_test.cpp @@ -9,7 +9,8 @@ using namespace secp_primitives; BOOST_FIXTURE_TEST_SUITE(spark_address_tests, BasicTestingSetup) -BOOST_AUTO_TEST_CASE(encoding) +// Check that correct encoding and decoding succeed +BOOST_AUTO_TEST_CASE(correctness) { // Parameters const Params* params; @@ -37,6 +38,87 @@ BOOST_AUTO_TEST_CASE(encoding) BOOST_CHECK_EQUAL(address.get_Q2(), decoded.get_Q2()); } +// Check that a bad checksum fails +BOOST_AUTO_TEST_CASE(evil_checksum) +{ + // Parameters + const Params* params; + params = Params::get_test(); + + // Generate keys + SpendKey spend_key(params); + FullViewKey full_view_key(spend_key); + IncomingViewKey incoming_view_key(full_view_key); + + // Generate address + const uint64_t i = 12345; + Address address(incoming_view_key, i); + + // Encode address + std::string encoded = address.encode(ADDRESS_NETWORK_TESTNET); + + // Malleate the checksum + encoded[encoded.size() - 1] = ~encoded[encoded.size() - 1]; + + // Decode address + Address decoded; + BOOST_CHECK_THROW(decoded.decode(encoded), std::invalid_argument); +} + +// Check that a bad prefix fails +BOOST_AUTO_TEST_CASE(evil_prefix) +{ + // Parameters + const Params* params; + params = Params::get_test(); + + // Generate keys + SpendKey spend_key(params); + FullViewKey full_view_key(spend_key); + IncomingViewKey incoming_view_key(full_view_key); + + // Generate address + const uint64_t i = 12345; + Address address(incoming_view_key, i); + + // Encode address + std::string encoded = address.encode(ADDRESS_NETWORK_TESTNET); + + // Malleate the prefix + encoded[0] = 'x'; + + // Decode address + Address decoded; + BOOST_CHECK_THROW(decoded.decode(encoded), std::invalid_argument); +} + +// Check that a bad network fails +BOOST_AUTO_TEST_CASE(evil_network) +{ + // Parameters + const Params* params; + params = Params::get_test(); + + // Generate keys + SpendKey spend_key(params); + FullViewKey full_view_key(spend_key); + IncomingViewKey incoming_view_key(full_view_key); + + // Generate address + const uint64_t i = 12345; + Address address(incoming_view_key, i); + + // Encode address + std::string encoded = address.encode(ADDRESS_NETWORK_TESTNET); + + // Malleate the network + encoded[1] = 'x'; + + // Decode address + Address decoded; + BOOST_CHECK_THROW(decoded.decode(encoded), std::invalid_argument); +} + BOOST_AUTO_TEST_SUITE_END() } From b1e209feede5d179408a42fe4a35f57e7ec44110 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 18 Sep 2022 06:04:15 +0400 Subject: [PATCH 048/197] SpendTransaction refactored and added serialization for it --- src/libspark/spend_transaction.cpp | 81 ++++++++++++-------- src/libspark/spend_transaction.h | 57 +++++++++++--- src/libspark/test/spend_transaction_test.cpp | 19 +++-- 3 files changed, 108 insertions(+), 49 deletions(-) diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index 103ec2f943..49bd05b8b7 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -13,6 +13,7 @@ SpendTransaction::SpendTransaction( const FullViewKey& full_view_key, const SpendKey& spend_key, const std::vector& inputs, + const std::unordered_map& cover_set_data, const uint64_t f, const std::vector& outputs ) { @@ -25,9 +26,7 @@ SpendTransaction::SpendTransaction( // Prepare input-related vectors this->cover_set_ids.reserve(w); // cover set data and metadata - this->cover_sets.reserve(w); - this->cover_set_representations.reserve(w); - this->cover_set_sizes.reserve(w); + this->setCoverSets(cover_set_data); this->S1.reserve(w); // serial commitment offsets this->C1.reserve(w); // value commitment offsets this->grootle_proofs.reserve(w); // Grootle one-of-many proofs @@ -52,17 +51,22 @@ SpendTransaction::SpendTransaction( ); for (std::size_t u = 0; u < w; u++) { // Parse out cover set data for this spend - this->cover_set_ids.emplace_back(inputs[u].cover_set_id); - this->cover_sets.emplace_back(inputs[u].cover_set); - this->cover_set_representations.emplace_back(inputs[u].cover_set_representation); - this->cover_set_sizes.emplace_back(inputs[u].cover_set_size); - - std::vector S, C; - S.reserve(N); - C.reserve(N); - for (std::size_t i = 0; i < N; i++) { - S.emplace_back(inputs[u].cover_set[i].S); - C.emplace_back(inputs[u].cover_set[i].C); + uint64_t set_id = inputs[u].cover_set_id; + this->cover_set_ids.emplace_back(set_id); + if (cover_set_data.count(set_id) == 0) + throw std::invalid_argument("Required set is not passed"); + + const auto& cover_set = cover_set_data.at(set_id).cover_set; + std::size_t set_size = cover_set.size(); + if (set_size > N) + throw std::invalid_argument("Wrong set size"); + + std::vector S, C; + S.reserve(set_size); + C.reserve(set_size); + for (std::size_t i = 0; i < set_size; i++) { + S.emplace_back(cover_set[i].S); + C.emplace_back(cover_set[i].C); } // Serial commitment offset @@ -92,7 +96,7 @@ SpendTransaction::SpendTransaction( SparkUtils::hash_val(inputs[u].k) - SparkUtils::hash_val1(inputs[u].s, full_view_key.get_D()), C, this->C1.back(), - this->cover_set_representations[u], + this->cover_set_representations[set_id], this->grootle_proofs.back() ); @@ -208,15 +212,20 @@ std::vector& SpendTransaction::getUsedLTags() { } // Convenience wrapper for verifying a single spend transaction -bool SpendTransaction::verify(const SpendTransaction& transaction) { +bool SpendTransaction::verify( + const SpendTransaction& transaction, + const std::unordered_map>& cover_sets) { std::vector transactions = { transaction }; - return verify(transaction.params, transactions); + return verify(transaction.params, transactions, cover_sets); } // Determine if a set of spend transactions is collectively valid // NOTE: This assumes that the relationship between a `cover_set_id` and the provided `cover_set` is already valid and canonical! // NOTE: This assumes that validity criteria relating to chain context have been externally checked! -bool SpendTransaction::verify(const Params* params, const std::vector& transactions) { +bool SpendTransaction::verify( + const Params* params, + const std::vector& transactions, + const std::unordered_map>& cover_sets) { // The idea here is to perform batching as broadly as possible // - Grootle proofs can be batched if they share a (partial) cover set // - Range proofs can always be batched arbitrarily @@ -248,15 +257,14 @@ bool SpendTransaction::verify(const Params* params, const std::vector N) { + for (const auto& set : cover_sets) { + if (set.second.size() > N) { throw std::invalid_argument("Bad spend transaction semantics"); } } @@ -347,21 +355,30 @@ bool SpendTransaction::verify(const Params* params, const std::vector proofs; for (auto proof_index : proof_indexes) { + const auto& tx = transactions[proof_index.first]; + if (!cover_sets.count(cover_set_id)) + throw std::invalid_argument("Cover set missing"); // Because we assume all proofs in this list share a monotonic cover set, the largest such set is the one to use for verification - std::size_t this_cover_set_size = transactions[proof_index.first].cover_sets[proof_index.second].size(); + if (!tx.cover_set_sizes.count(cover_set_id)) + throw std::invalid_argument("Cover set size missing"); + + std::size_t this_cover_set_size = tx.cover_set_sizes.at(cover_set_id); if (this_cover_set_size > S.size()) { for (std::size_t i = S.size(); i < this_cover_set_size; i++) { - S.emplace_back(transactions[proof_index.first].cover_sets[proof_index.second][i].S); - V.emplace_back(transactions[proof_index.first].cover_sets[proof_index.second][i].C); + S.emplace_back(cover_sets.at(cover_set_id)[i].S); + V.emplace_back(cover_sets.at(cover_set_id)[i].C); } } // We always use the other elements - S1.emplace_back(transactions[proof_index.first].S1[proof_index.second]); - V1.emplace_back(transactions[proof_index.first].C1[proof_index.second]); - cover_set_representations.emplace_back(transactions[proof_index.first].cover_set_representations[proof_index.second]); - sizes.emplace_back(transactions[proof_index.first].cover_set_sizes[proof_index.second]); - proofs.emplace_back(transactions[proof_index.first].grootle_proofs[proof_index.second]); + S1.emplace_back(tx.S1[proof_index.second]); + V1.emplace_back(tx.C1[proof_index.second]); + if (!tx.cover_set_representations.count(cover_set_id)) + throw std::invalid_argument("Cover set representation missing"); + + cover_set_representations.emplace_back(tx.cover_set_representations.at(cover_set_id)); + sizes.emplace_back(this_cover_set_size); + proofs.emplace_back(tx.grootle_proofs[proof_index.second]); } // Verify the batch @@ -376,7 +393,7 @@ bool SpendTransaction::verify(const Params* params, const std::vector& out_coins, const uint64_t f, - const std::vector>& cover_set_representations, + const std::unordered_map>& cover_set_representations, const std::vector& S1, const std::vector& C1, const std::vector& T, diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h index ce59280c88..c177327134 100644 --- a/src/libspark/spend_transaction.h +++ b/src/libspark/spend_transaction.h @@ -18,16 +18,18 @@ using namespace secp_primitives; // This relationship _must_ be checked elsewhere, as we simply use the largest `cover_set` for each `cover_set_id`! struct InputCoinData { uint64_t cover_set_id; // an identifier for the monotonically-growing set of which `cover_set` is a subset - std::vector cover_set; // set of coins used as a cover set for the spend - std::vector cover_set_representation; // a unique representation for the ordered elements of the partial `cover_set` used in the spend - std::size_t cover_set_size; // the size of the partial cover set used by the spend; should be canonical with `cover_set_representation` - std::size_t index; // index of the coin in the cover set + std::size_t index; // index of the coin in the cover set Scalar s; // serial number GroupElement T; // tag uint64_t v; // value Scalar k; // nonce }; +struct CoverSetData { + std::vector cover_set; // set of coins used as a cover set for the spend + std::vector cover_set_representation; // a unique representation for the ordered elements of the partial `cover_set` used in the spend +}; + struct OutputCoinData { Address address; uint64_t v; @@ -44,6 +46,7 @@ class SpendTransaction { const FullViewKey& full_view_key, const SpendKey& spend_key, const std::vector& inputs, + const std::unordered_map& cover_set_data, const uint64_t f, const std::vector& outputs ); @@ -51,13 +54,13 @@ class SpendTransaction { uint64_t getFee(); std::vector& getUsedLTags(); - static bool verify(const Params* params, const std::vector& transactions); - static bool verify(const SpendTransaction& transaction); - + static bool verify(const Params* params, const std::vector& transactions, const std::unordered_map>& cover_sets); + static bool verify(const SpendTransaction& transaction, const std::unordered_map>& cover_sets); + static Scalar hash_bind( const std::vector& out_coins, const uint64_t f, - const std::vector>& cover_set_representations, + const std::unordered_map>& cover_set_representations, const std::vector& S1, const std::vector& C1, const std::vector& T, @@ -66,13 +69,43 @@ class SpendTransaction { const BPPlusProof& range_proof ); + ADD_SERIALIZE_METHODS; + template + void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(cover_set_ids); + READWRITE(set_id_blockHash); + READWRITE(f); + READWRITE(S1); + READWRITE(C1); + READWRITE(T); + READWRITE(grootle_proofs); + READWRITE(chaum_proof); + READWRITE(balance_proof); + READWRITE(range_proof); + } + + void setOutCoins(const std::vector& out_coins_) { + this->out_coins = out_coins_; + } + + void setCoverSets(const std::unordered_map& cover_set_data) { + for (const auto& data : cover_set_data) { + this->cover_set_sizes[data.first] = data.second.cover_set.size(); + this->cover_set_representations[data.first] = data.second.cover_set_representation; + } + } + private: const Params* params; - std::vector cover_set_ids; - std::vector> cover_sets; - std::vector> cover_set_representations; - std::vector cover_set_sizes; + // We need to construct and pass this data before running verification + std::unordered_map cover_set_sizes; + std::unordered_map> cover_set_representations; std::vector out_coins; + + // All this data we need to serialize + std::vector> set_id_blockHash; + std::vector cover_set_ids; uint64_t f; std::vector S1, C1, T; std::vector grootle_proofs; diff --git a/src/libspark/test/spend_transaction_test.cpp b/src/libspark/test/spend_transaction_test.cpp index 035b6333e0..ce973787d0 100644 --- a/src/libspark/test/spend_transaction_test.cpp +++ b/src/libspark/test/spend_transaction_test.cpp @@ -63,16 +63,20 @@ BOOST_AUTO_TEST_CASE(generate_verify) // Choose coins to spend, recover them, and prepare them for spending std::vector spend_indices = { 1, 3, 5 }; std::vector spend_coin_data; + std::unordered_map cover_set_data; const std::size_t w = spend_indices.size(); for (std::size_t u = 0; u < w; u++) { IdentifiedCoinData identified_coin_data = in_coins[spend_indices[u]].identify(incoming_view_key); RecoveredCoinData recovered_coin_data = in_coins[spend_indices[u]].recover(full_view_key, identified_coin_data); spend_coin_data.emplace_back(); - spend_coin_data.back().cover_set_id = 31415; - spend_coin_data.back().cover_set = in_coins; - spend_coin_data.back().cover_set_representation = random_char_vector(); - spend_coin_data.back().cover_set_size = N; + uint64_t cover_set_id = 31415; + spend_coin_data.back().cover_set_id = cover_set_id; + + CoverSetData setData; + setData.cover_set = in_coins; + setData.cover_set_representation = random_char_vector(); + cover_set_data[cover_set_id] = setData; spend_coin_data.back().index = spend_indices[u]; spend_coin_data.back().k = identified_coin_data.k; spend_coin_data.back().s = recovered_coin_data.s; @@ -113,12 +117,17 @@ BOOST_AUTO_TEST_CASE(generate_verify) full_view_key, spend_key, spend_coin_data, + cover_set_data, f, out_coin_data ); // Verify - BOOST_CHECK(SpendTransaction::verify(transaction)); + transaction.setCoverSets(cover_set_data); + std::unordered_map> cover_sets; + for (const auto set_data : cover_set_data) + cover_sets[set_data.first] = set_data.second.cover_set; + BOOST_CHECK(SpendTransaction::verify(transaction, cover_sets)); } BOOST_AUTO_TEST_SUITE_END() From ca09978bef8b62a51b2c5536d2bfabfdc78194e1 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 18 Sep 2022 06:59:31 +0400 Subject: [PATCH 049/197] Review comment applied and fixed a bug in mint generation --- src/libspark/mint_transaction.cpp | 10 ++++- src/spark/wallet.cpp | 67 +++++++++++++++++++++++++++++-- src/spark/wallet.h | 6 +++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/libspark/mint_transaction.cpp b/src/libspark/mint_transaction.cpp index f7dd8c4836..30743e0f73 100644 --- a/src/libspark/mint_transaction.cpp +++ b/src/libspark/mint_transaction.cpp @@ -42,13 +42,21 @@ MintTransaction::MintTransaction( value_statement.emplace_back(this->coins[j].C + this->params->get_G().inverse()*Scalar(this->coins[j].v)); value_witness.emplace_back(SparkUtils::hash_val(k)); } else { - this->coins.emplace_back(Coin()); + Coin coin; + coin.type = 0; + coin.r_.ciphertext.resize(32); + coin.r_.key_commitment.resize(64); + coin.r_.tag.resize(16); + coin.v = 0; + this->coins.emplace_back(coin); } } // Complete the value proof if (generate) schnorr.prove(value_witness, value_statement, this->value_proof); + else + value_proof = SchnorrProof(); } bool MintTransaction::verify() { diff --git a/src/spark/wallet.cpp b/src/spark/wallet.cpp index 5b20d2bb2b..7160400fe6 100644 --- a/src/spark/wallet.cpp +++ b/src/spark/wallet.cpp @@ -54,11 +54,11 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { } void CSparkWallet::resetDiversifierFromDB(CWalletDB& walletdb) { - walletdb.writeDiversifier(lastDiversifier); + walletdb.readDiversifier(lastDiversifier); } void CSparkWallet::updatetDiversifierInDB(CWalletDB& walletdb) { - walletdb.readDiversifier(lastDiversifier); + walletdb.writeDiversifier(lastDiversifier); } CAmount CSparkWallet::getFullBalance() { @@ -642,7 +642,7 @@ bool CSparkWallet::CreateSparkMintTransactions( serialContextStream << input; } - recipients = CSparkWallet::CreateSparkMintRecipients(singleTxOutputs, std::vector(serial_context.begin(), serial_context.end()), true); + recipients = CSparkWallet::CreateSparkMintRecipients(singleTxOutputs, std::vector(serialContextStream.begin(), serialContextStream.end()), true); size_t i = 0; for (auto& recipient : recipients) { @@ -795,6 +795,67 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( wtxNew.BindWallet(pwalletMain); + // Discourage fee sniping. + // + // For a large miner the value of the transactions in the best block and + // the mempool can exceed the cost of deliberately attempting to mine two + // blocks to orphan the current best block. By setting nLockTime such that + // only the next block can include the transaction, we discourage this + // practice as the height restricted and limited blocksize gives miners + // considering fee sniping fewer options for pulling off this attack. + // + // A simple way to think about this is from the wallet's point of view we + // always want the blockchain to move forward. By setting nLockTime this + // way we're basically making the statement that we only want this + // transaction to appear in the next block; we don't want to potentially + // encourage reorgs by allowing transactions to appear at lower heights + // than the next block in forks of the best chain. + // + // Of course, the subsidy is high enough, and transaction volume low + // enough, that fee sniping isn't a problem yet, but by implementing a fix + // now we ensure code won't be written that makes assumptions about + // nLockTime that preclude a fix later. + tx.nLockTime = chainActive.Height(); + + // Secondly occasionally randomly pick a nLockTime even further back, so + // that transactions that are delayed after signing for whatever reason, + // e.g. high-latency mix networks and some CoinJoin implementations, have + // better privacy. + if (GetRandInt(10) == 0) { + tx.nLockTime = std::max(0, static_cast(tx.nLockTime) - GetRandInt(100)); + } + + assert(tx.nLockTime <= static_cast(chainActive.Height())); + assert(tx.nLockTime < LOCKTIME_THRESHOLD); + CWalletDB walletdb(strWalletFile); + std::list> coins = GetAvailableSparkCoins(walletdb, coinControl); + + std::map>> estimated = + SelectSparkCoins(vOut + mintVOut, recipientsToSubtractFee, coins, coinControl); + + { + LOCK2(cs_main, pwalletMain->cs_wallet); + { + + } + } + + + + return result; +} + +std::map>> CSparkWallet::SelectSparkCoins( + CAmount required, + bool subtractFeeFromAmount, + std::list> coins, + const CCoinControl *coinControl) { + + std::map>> result; + + while (required > 0) { + + } return result; } diff --git a/src/spark/wallet.h b/src/spark/wallet.h index 1eb6216852..149c0b4d0a 100644 --- a/src/spark/wallet.h +++ b/src/spark/wallet.h @@ -87,6 +87,12 @@ class CSparkWallet { CAmount &fee, const CCoinControl *coinControl = NULL); + std::map>> SelectSparkCoins( + CAmount required, + bool subtractFeeFromAmount, + std::list> coins, + const CCoinControl *coinControl); + // Returns the list of pairs of coins and metadata for that coin, std::list> GetAvailableSparkCoins(CWalletDB& walletdb, const CCoinControl *coinControl = NULL) const; From 3cd5bee82556047b1fce493315836b27da3817d0 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 26 Sep 2022 10:07:40 +0400 Subject: [PATCH 050/197] Spark spend creation --- src/chain.h | 13 + src/libspark/keys.cpp | 15 ++ src/libspark/keys.h | 3 + src/libspark/spend_transaction.cpp | 12 + src/libspark/spend_transaction.h | 6 +- src/script/script.h | 1 + src/spark/state.cpp | 96 ++++++- src/spark/state.h | 11 + src/spark/wallet.cpp | 414 ++++++++++++++++++++++++++++- src/spark/wallet.h | 7 +- src/wallet/wallet.cpp | 2 +- src/wallet/wallet.h | 2 +- 12 files changed, 568 insertions(+), 14 deletions(-) diff --git a/src/chain.h b/src/chain.h index 9879f19f41..96a6d0063c 100644 --- a/src/chain.h +++ b/src/chain.h @@ -15,6 +15,7 @@ #include #include #include "sigma/coin.h" +#include "libspark/coin.h" #include "evo/spork.h" #include "firo_params.h" #include "util.h" @@ -246,6 +247,10 @@ class CBlockIndex std::map>>> lelantusMintedPubCoins; //! Map id to std::map> anonymitySetHash; + //! Map id to spark coin + std::map> sparkMintedCoins; + //! Map id to + std::map> sparkSetHash; //! Values of coin serials spent in this block sigma::spend_info_container sigmaSpentSerials; @@ -288,6 +293,8 @@ class CBlockIndex sigmaMintedPubCoins.clear(); lelantusMintedPubCoins.clear(); anonymitySetHash.clear(); + sparkMintedCoins.clear(); + sparkSetHash.clear(); sigmaSpentSerials.clear(); lelantusSpentSerials.clear(); activeDisablingSporks.clear(); @@ -531,6 +538,12 @@ class CDiskBlockIndex : public CBlockIndex READWRITE(lelantusMintedPubCoins); READWRITE(lelantusSpentSerials); + if (!(s.GetType() & SER_GETHASH) + && nHeight >= params.nSparkStartBlock) { + READWRITE(sparkMintedCoins); + READWRITE(sparkSetHash); + } + if (nHeight >= params.nLelantusFixesStartBlock) READWRITE(anonymitySetHash); } diff --git a/src/libspark/keys.cpp b/src/libspark/keys.cpp index fa60f95758..c654167081 100644 --- a/src/libspark/keys.cpp +++ b/src/libspark/keys.cpp @@ -56,6 +56,21 @@ const Scalar& SpendKey::get_r() const { return this->r; } +SpendKey& SpendKey::operator=(const SpendKey& other) { + this->s1 = other.s1; + this->s2 = other.s2; + this->r = other.r; + return *this; +} + +bool SpendKey::operator==(const SpendKey& other) const { + if (this->s1 != other.s1 || + this->s2 != other.s2 || + this->r != other.r) + return false; + return true; +} + FullViewKey::FullViewKey() {} FullViewKey::FullViewKey(const Params* params) { this->params = params; diff --git a/src/libspark/keys.h b/src/libspark/keys.h index ebf3ae8ac0..3895421516 100644 --- a/src/libspark/keys.h +++ b/src/libspark/keys.h @@ -22,6 +22,9 @@ class SpendKey { const Scalar& get_s2() const; const Scalar& get_r() const; + SpendKey& operator=(const SpendKey& other); + bool operator==(const SpendKey& other) const; + private: const Params* params; Scalar s1, s2, r; diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index 49bd05b8b7..1ef41e5858 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -211,6 +211,10 @@ std::vector& SpendTransaction::getUsedLTags() { return T; } +std::vector& SpendTransaction::getOutCoins() { + return out_coins; +} + // Convenience wrapper for verifying a single spend transaction bool SpendTransaction::verify( const SpendTransaction& transaction, @@ -420,4 +424,12 @@ Scalar SpendTransaction::hash_bind( return hash.finalize_scalar(); } +void SpendTransaction::setBlockHashes(const std::map& idAndHashes) { + set_id_blockHash = idAndHashes; +} + +std::map& SpendTransaction::getBlockHashes() { + return set_id_blockHash; +} + } diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h index c177327134..6ef30fd104 100644 --- a/src/libspark/spend_transaction.h +++ b/src/libspark/spend_transaction.h @@ -53,6 +53,7 @@ class SpendTransaction { uint64_t getFee(); std::vector& getUsedLTags(); + std::vector& getOutCoins(); static bool verify(const Params* params, const std::vector& transactions, const std::unordered_map>& cover_sets); static bool verify(const SpendTransaction& transaction, const std::unordered_map>& cover_sets); @@ -96,6 +97,9 @@ class SpendTransaction { } } + void setBlockHashes(const std::map& idAndHashes); + + std::map& getBlockHashes(); private: const Params* params; // We need to construct and pass this data before running verification @@ -104,7 +108,7 @@ class SpendTransaction { std::vector out_coins; // All this data we need to serialize - std::vector> set_id_blockHash; + std::map set_id_blockHash; std::vector cover_set_ids; uint64_t f; std::vector S1, C1, T; diff --git a/src/script/script.h b/src/script/script.h index 9e0d1b7464..0c5fcb7013 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -206,6 +206,7 @@ enum opcodetype // spark params OP_SPARKMINT = 0xd1, OP_SPARKSMINT = 0xd2, + OP_SPARKSPEND = 0xd3, }; const char* GetOpName(opcodetype opcode); diff --git a/src/spark/state.cpp b/src/spark/state.cpp index 03d4982698..6469ea33b7 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -96,8 +96,20 @@ void ParseSparkMintCoin(const CScript& script, spark::Coin& txCoin) spark::SpendTransaction ParseSparkSpend(const CTransaction &tx) { - //TODO levon implement this after spark spend implementation + if (tx.vin.size() != 1 || tx.vin[0].scriptSig.size() < 1) { + throw CBadTxIn(); + } + CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); + if (tx.vin[0].scriptSig[0] == OP_SPARKSPEND && tx.nVersion >= 3 && tx.nType == TRANSACTION_SPARK) { + serialized.write((const char *)tx.vExtraPayload.data(), tx.vExtraPayload.size()); + } + else + throw CBadTxIn(); + const spark::Params* params = spark::Params::get_default(); + spark::SpendTransaction spendTransaction(params); + serialized >> spendTransaction; + return std::move(spendTransaction); } @@ -272,6 +284,33 @@ bool GetOutPointFromBlock(COutPoint& outPoint, const spark::Coin& coin, const CB return false; } +/* + * Util funtions + */ +size_t CountCoinInBlock(CBlockIndex *index, int id) { + return index->sparkMintedCoins.count(id) > 0 + ? index->sparkMintedCoins[id].size() : 0; +} + +std::vector GetAnonymitySetHash(CBlockIndex *index, int group_id, bool generation = false) { + std::vector out_hash; + + CSparkState::SparkCoinGroupInfo coinGroup; + if (!sparkState.GetCoinGroupInfo(group_id, coinGroup)) + return out_hash; + + if ((coinGroup.firstBlock == coinGroup.lastBlock && generation) || (coinGroup.nCoins == 0)) + return out_hash; + + while (index != coinGroup.firstBlock) { + if (index->sparkSetHash.count(group_id) > 0) { + out_hash = index->sparkSetHash[group_id]; + break; + } + index = index->pprev; + } + return out_hash; +} /******************************************************************************/ // CLelantusState @@ -430,6 +469,61 @@ CSparkState* CSparkState::GetState() { return &sparkState; } +int CSparkState::GetCoinSetForSpend( + CChain *chain, + int maxHeight, + int coinGroupID, + uint256& blockHash_out, + std::vector& coins_out, + std::vector& setHash_out) { + + coins_out.clear(); + + if (coinGroups.count(coinGroupID) == 0) { + return 0; + } + + SparkCoinGroupInfo &coinGroup = coinGroups[coinGroupID]; + + int numberOfCoins = 0; + for (CBlockIndex *block = coinGroup.lastBlock;; block = block->pprev) { + + // ignore block heigher than max height + if (block->nHeight > maxHeight) { + continue; + } + + // check coins in group coinGroupID - 1 in the case that using coins from prev group. + int id = 0; + if (CountCoinInBlock(block, coinGroupID)) { + id = coinGroupID; + } else if (CountCoinInBlock(block, coinGroupID - 1)) { + id = coinGroupID - 1; + } + + if (id) { + if (numberOfCoins == 0) { + // latest block satisfying given conditions + // remember block hash and set hash + blockHash_out = block->GetBlockHash(); + setHash_out = GetAnonymitySetHash(block, id); + } + numberOfCoins += block->sparkMintedCoins[id].size(); + if (block->sparkMintedCoins.count(id) > 0) { + for (const auto &coin : block->sparkMintedCoins[id]) { + coins_out.push_back(coin); + } + } + } + + if (block == coinGroup.firstBlock) { + break ; + } + } + + return numberOfCoins; +} + std::unordered_map const & CSparkState::GetMints() const { return mintedCoins; } diff --git a/src/spark/state.h b/src/spark/state.h index 338218e503..4f7b5d8257 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -161,6 +161,17 @@ class CSparkState { // Remove spend from the mempool (usually as the result of adding tx to the block) void RemoveSpendFromMempool(const std::vector& lTags); + // Given id returns latest anonymity set and corresponding block hash + // Do not take into account coins with height more than maxHeight + // Returns number of coins satisfying conditions + int GetCoinSetForSpend( + CChain *chain, + int maxHeight, + int id, + uint256& blockHash_out, + std::vector& coins_out, + std::vector& setHash_out); + std::unordered_map const & GetMints() const; std::unordered_map const & GetSpends() const; std::unordered_map const & GetCoinGroups() const; diff --git a/src/spark/wallet.cpp b/src/spark/wallet.cpp index 7160400fe6..eb8152aa11 100644 --- a/src/spark/wallet.cpp +++ b/src/spark/wallet.cpp @@ -1,6 +1,7 @@ #include "wallet.h" #include "../wallet/wallet.h" #include "../wallet/coincontrol.h" +#include "../wallet/walletexcept.h" #include "../hash.h" #include "../validation.h" #include "../policy/policy.h" @@ -755,9 +756,19 @@ bool CSparkWallet::CreateSparkMintTransactions( return true; } +bool getIndex(const spark::Coin& coin, const std::vector& anonymity_set, size_t& index) { + for (std::size_t j = 0; j < anonymity_set.size(); ++j) { + if (anonymity_set[j] == coin){ + index = j; + return true; + } + } + return false; +} + std::vector CSparkWallet::CreateSparkSpendTransaction( const std::vector& recipients, - const std::vector& privateRecipients, + const std::vector>& privateRecipients, CAmount &fee, const CCoinControl *coinControl) { @@ -784,11 +795,15 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( } } - for(const auto& privRecipient : privateRecipients) { - mintVOut += privRecipient.v; + for (const auto& privRecipient : privateRecipients) { + mintVOut += privRecipient.first.v; + if (privRecipient.second) { + recipientsToSubtractFee++; + } } std::vector result; + std::vector txs; CWalletTx wtxNew; CMutableTransaction tx; wtxNew.fTimeReceivedIsTxTime = true; @@ -829,32 +844,415 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( assert(tx.nLockTime < LOCKTIME_THRESHOLD); CWalletDB walletdb(strWalletFile); std::list> coins = GetAvailableSparkCoins(walletdb, coinControl); + // TODO levon check spend limit + std::vector>>> estimated = + SelectSparkCoins(vOut + mintVOut, recipientsToSubtractFee, coins, privateRecipients.size(), recipients.size(), coinControl); + + std::vector recipients_ = recipients; + std::vector> privateRecipients_ = privateRecipients; + for (auto& feeAndSpendCoins : estimated) { + bool remainderSubtracted = false; + auto& fee = feeAndSpendCoins.first; + for (size_t i = 0; i < recipients_.size(); i++) { + auto &recipient = recipients_[i]; + + if (recipient.fSubtractFeeFromAmount) { + // Subtract fee equally from each selected recipient. + recipient.nAmount -= fee / recipientsToSubtractFee; + + if (!remainderSubtracted) { + // First receiver pays the remainder not divisible by output count. + recipient.nAmount -= fee % recipientsToSubtractFee; + remainderSubtracted = true; + } + } + } + + for (size_t i = 0; i < privateRecipients_.size(); i++) { + auto &privateRecipient = privateRecipients_[i]; - std::map>> estimated = - SelectSparkCoins(vOut + mintVOut, recipientsToSubtractFee, coins, coinControl); + if (privateRecipient.second) { + // Subtract fee equally from each selected recipient. + privateRecipient.first.v -= fee / recipientsToSubtractFee; + + if (!remainderSubtracted) { + // First receiver pays the remainder not divisible by output count. + privateRecipient.first.v -= fee % recipientsToSubtractFee; + remainderSubtracted = true; + } + } + } + + } { LOCK2(cs_main, pwalletMain->cs_wallet); { + spark::CSparkState *sparkState = spark::CSparkState::GetState(); + spark::SpendKey spendKey; + try { + spendKey = std::move(generateSpendKey()); + } catch (std::exception& e) { + throw std::runtime_error(_("Unable to generate spend key.")); + } + + if (spendKey == spark::SpendKey()) + throw std::runtime_error(_("Unable to generate spend key, looks wallet locked.")); + + + for (auto& feeAndSpendCoins : estimated) { + tx.vin.clear(); + tx.vout.clear(); + wtxNew.fFromMe = true; + wtxNew.changes.clear(); + + CAmount spendInCurrentTx = 0; + for (auto& spendCoin : feeAndSpendCoins.second) + spendInCurrentTx += spendCoin.second.v; + CAmount fee = feeAndSpendCoins.first; + spendInCurrentTx -= fee; + + // fill outputs + for (size_t i = 0; i < recipients_.size(); i++) { + auto& recipient = recipients_[i]; + if (recipient.nAmount == 0) + continue; + + if (spendInCurrentTx <= 0) + break; + + CAmount recipientAmount = std::min(recipient.nAmount, spendInCurrentTx); + spendInCurrentTx -= recipientAmount; + recipient.nAmount -= recipientAmount; + CTxOut vout(recipientAmount, recipient.scriptPubKey); + + if (vout.IsDust(minRelayTxFee)) { + std::string err; + + if (recipient.fSubtractFeeFromAmount && fee > 0) { + if (vout.nValue < 0) { + err = boost::str(boost::format(_("Amount for recipient %1% is too small to pay the fee")) % i); + } else { + err = boost::str(boost::format(_("Amount for recipient %1% is too small to send after the fee has been deducted")) % i); + } + } else { + err = boost::str(boost::format(_("Amount for recipient %1% is too small")) % i); + } + + throw std::runtime_error(err); + } + + tx.vout.push_back(vout); + } + + std::vector privOutputs; + // fill outputs + for (size_t i = 0; i < privateRecipients_.size(); i++) { + auto& recipient = privateRecipients_[i]; + if (recipient.first.v == 0) + continue; + + if (spendInCurrentTx <= 0) + break; + + CAmount recipientAmount = recipient.first.v; + recipientAmount = std::min(recipientAmount, spendInCurrentTx); + spendInCurrentTx -= recipientAmount; + recipient.first.v -= recipientAmount; + spark::OutputCoinData output = recipient.first; + output.v = recipientAmount; + privOutputs.push_back(output); + } + + if (!privOutputs.size() || spendInCurrentTx > 0) { + spark::OutputCoinData output; + output.address = getDefaultAddress(); + output.memo = ""; + if (spendInCurrentTx > 0) + output.v = spendInCurrentTx; + else + output.v = 0; + privOutputs.push_back(output); + } + + + // fill inputs + uint32_t sequence = CTxIn::SEQUENCE_FINAL; + tx.vin.emplace_back(COutPoint(), CScript(), sequence); + + // clear vExtraPayload to calculate metadata hash correctly + tx.vExtraPayload.clear(); + + // set correct type of transaction (this affects metadata hash) + tx.nVersion = 3; + tx.nType = TRANSACTION_SPARK; + + // now every field is populated then we can sign transaction + // We will write this into cover set representation, with anonymity set hash + uint256 sig = tx.GetHash(); + + const spark::Params* params = spark::Params::get_default(); + std::vector inputs; + std::map idAndBlockHashes; + std::unordered_map cover_set_data; + for (auto& coin : feeAndSpendCoins.second) { + spark::CSparkState::SparkCoinGroupInfo nextCoinGroupInfo; + uint64_t groupId = coin.second.nId; + if (sparkState->GetLatestCoinID() > groupId && sparkState->GetCoinGroupInfo(groupId + 1, nextCoinGroupInfo)) { + if (nextCoinGroupInfo.firstBlock->nHeight <= coin.second.nHeight) + groupId += 1; + } + + if (cover_set_data.count(groupId) == 0) { + std::vector set; + uint256 blockHash; + std::vector setHash; + if (sparkState->GetCoinSetForSpend( + &chainActive, + chainActive.Height() - + (ZC_MINT_CONFIRMATIONS - 1), // required 1 confirmation for mint to spend + groupId, + blockHash, + set, + setHash) < 2) + throw std::runtime_error( + _("Has to have at least two mint coins with at least 1 confirmation in order to spend a coin")); + + spark::CoverSetData coverSetData; + coverSetData.cover_set = set; + coverSetData.cover_set_representation = setHash; + coverSetData.cover_set_representation.insert(coverSetData.cover_set_representation.end(), sig.begin(), sig.end()); + cover_set_data[groupId] = coverSetData; + idAndBlockHashes[groupId] = blockHash; + } + + + spark::InputCoinData inputCoinData; + inputCoinData.cover_set_id = groupId; + std::size_t index = 0; + if (!getIndex(coin.first, cover_set_data[groupId].cover_set, index)) + throw std::runtime_error( + _("No such coin in set")); + inputCoinData.index = index; + inputCoinData.v = coin.second.v; + inputCoinData.k = coin.second.k; + + spark::IdentifiedCoinData identifiedCoinData; + identifiedCoinData.i = coin.second.i; + identifiedCoinData.d = coin.second.d; + identifiedCoinData.v = coin.second.v; + identifiedCoinData.k = coin.second.k; + identifiedCoinData.memo = coin.second.memo; + spark::RecoveredCoinData recoveredCoinData = coin.first.recover(fullViewKey, identifiedCoinData); + + inputCoinData.T = recoveredCoinData.T; + inputCoinData.s = recoveredCoinData.s; + inputs.push_back(inputCoinData); + + } + + spark::SpendTransaction spendTransaction(params, fullViewKey, spendKey, inputs, cover_set_data, fee, privOutputs); + spendTransaction.setBlockHashes(idAndBlockHashes); + std::vector& outCoins = spendTransaction.getOutCoins(); + for (auto& outCoin : outCoins) { + // construct spend script + CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); + serialized << outCoin; + CScript script; + script << OP_SPARKSMINT; + script.insert(script.end(), serialized.begin(), serialized.end()); + tx.vout.push_back(CTxOut(0, script)); + } + + // check fee + wtxNew.SetTx(MakeTransactionRef(std::move(tx))); + + if (GetTransactionWeight(tx) >= MAX_LELANTUS_TX_WEIGHT) { + throw std::runtime_error(_("Transaction too large")); + } + + // check fee + unsigned size = GetVirtualTransactionSize(tx); + CAmount feeNeeded = CWallet::GetMinimumFee(size, nTxConfirmTarget, mempool); + // If we made it here and we aren't even able to meet the relay fee on the next pass, give up + // because we must be at the maximum allowed fee. + if (feeNeeded < minRelayTxFee.GetFee(size)) { + throw std::invalid_argument(_("Transaction too large for fee policy")); + } + + if (fee < feeNeeded) { + throw std::invalid_argument(_("Not enough fee estimated")); + } + + result.push_back(wtxNew); + } } } - + if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { + // Lastly, ensure this tx will pass the mempool's chain limits + size_t nLimitAncestors = GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); + size_t nLimitAncestorSize = GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; + size_t nLimitDescendants = GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); + size_t nLimitDescendantSize = GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; + std::string errString; + + for (auto &tx_: txs) { + LockPoints lp; + CTxMemPoolEntry entry(MakeTransactionRef(tx_), 0, 0, 0, 0, false, 0, lp); + CTxMemPool::setEntries setAncestors; + if (!mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, + nLimitDescendants, nLimitDescendantSize, errString)) { + throw std::runtime_error(_("Transaction has too long of a mempool chain")); + } + } + } return result; } -std::map>> CSparkWallet::SelectSparkCoins( +template +static CAmount CalculateBalance(Iterator begin, Iterator end) { + CAmount balance(0); + for (auto itr = begin; itr != end; itr++) { + balance += itr->second.v; + } + return balance; +} + +bool GetCoinsToSpend( + CAmount required, + std::vector>& coinsToSpend_out, + std::list> coins, + int64_t& changeToMint, + const size_t coinsToSpendLimit, + const CCoinControl *coinControl) +{ + CAmount availableBalance = CalculateBalance(coins.begin(), coins.end()); + + if (required > availableBalance) { + throw InsufficientFunds(); + } + + typedef std::pair CoinData; + + // sort by biggest amount. if it is same amount we will prefer the older block + auto comparer = [](const CoinData& a, const CoinData& b) -> bool { + return a.second.v != b.second.v ? a.second.v > b.second.v : a.second.nHeight < b.second.nHeight; + }; + coins.sort(comparer); + + CAmount spend_val(0); + + std::list coinsToSpend; + + // If coinControl, want to use all inputs + bool coinControlUsed = false; + if(coinControl != NULL) { + if(coinControl->HasSelected()) { + auto coinIt = coins.rbegin(); + for (; coinIt != coins.rend(); coinIt++) { + spend_val += coinIt->second.v; + } + coinControlUsed = true; + coinsToSpend.insert(coinsToSpend.begin(), coins.begin(), coins.end()); + } + } + + if(!coinControlUsed) { + while (spend_val < required) { + if(coins.empty()) + break; + + CoinData choosen; + CAmount need = required - spend_val; + + auto itr = coins.begin(); + if(need >= itr->second.v) { + choosen = *itr; + coins.erase(itr); + } else { + for (auto coinIt = coins.rbegin(); coinIt != coins.rend(); coinIt++) { + auto nextItr = coinIt; + nextItr++; + + if (coinIt->second.v >= need && (nextItr == coins.rend() || nextItr->second.v != coinIt->second.v)) { + choosen = *coinIt; + coins.erase(std::next(coinIt).base()); + break; + } + } + } + + spend_val += choosen.second.v; + coinsToSpend.push_back(choosen); + + if (coinsToSpend.size() == coinsToSpendLimit) // if we pass input number limit, we stop and try to spend remaining part with another transaction + break; + } + } + + // sort by group id ay ascending order. it is mandatory for creting proper joinsplit + auto idComparer = [](const CoinData& a, const CoinData& b) -> bool { + return a.second.nId < b.second.nId; + }; + coinsToSpend.sort(idComparer); + + changeToMint = spend_val - required; + coinsToSpend_out.insert(coinsToSpend_out.begin(), coinsToSpend.begin(), coinsToSpend.end()); + + return true; +} + +std::vector>>> CSparkWallet::SelectSparkCoins( CAmount required, bool subtractFeeFromAmount, std::list> coins, + std::size_t mintNum, + std::size_t utxoNum, const CCoinControl *coinControl) { - std::map>> result; + std::vector>>> result; while (required > 0) { + CAmount fee; + unsigned size; + int64_t changeToMint = 0; // this value can be negative, that means we need to spend remaining part of required value with another transaction (nMaxInputPerTransaction exceeded) + + std::vector> spendCoins; + for (fee = payTxFee.GetFeePerK();;) { + CAmount currentRequired = required; + + if (!subtractFeeFromAmount) + currentRequired += fee; + spendCoins.clear(); + const auto &consensusParams = Params().GetConsensus(); + if (!GetCoinsToSpend(currentRequired, spendCoins, coins, changeToMint, + consensusParams.nMaxLelantusInputPerTransaction, coinControl)) { + throw std::invalid_argument(_("Unable to select cons for spend")); + } + + // 924 is constant part, mainly Schnorr and Range proofs, 2535 is for each grootle proof/aux data + // 213 for each private output, 144 other parts of tx, + size = 924 + 2535 * (spendCoins.size()) + 213 * mintNum + 144; //TODO (levon) take in account also utxoNum + CAmount feeNeeded = CWallet::GetMinimumFee(size, nTxConfirmTarget, mempool); + + if (fee >= feeNeeded) { + break; + } + + fee = feeNeeded; + + if(subtractFeeFromAmount) + break; + } + result.push_back({fee, spendCoins}); + if (changeToMint < 0) + required = - changeToMint; + else + required = 0; } return result; } diff --git a/src/spark/wallet.h b/src/spark/wallet.h index 149c0b4d0a..8e2b3732f9 100644 --- a/src/spark/wallet.h +++ b/src/spark/wallet.h @@ -8,6 +8,7 @@ #include "primitives.h" #include "../libspark/keys.h" #include "../libspark/mint_transaction.h" +#include "../libspark/spend_transaction.h" #include "../wallet/walletdb.h" class CRecipient; @@ -83,14 +84,16 @@ class CSparkWallet { std::vector CreateSparkSpendTransaction( const std::vector& recipients, - const std::vector& privateRecipients, + const std::vector>& privateRecipients, CAmount &fee, const CCoinControl *coinControl = NULL); - std::map>> SelectSparkCoins( + std::vector>>> SelectSparkCoins( CAmount required, bool subtractFeeFromAmount, std::list> coins, + std::size_t mintNum, + std::size_t utxoNum, const CCoinControl *coinControl); // Returns the list of pairs of coins and metadata for that coin, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5b4b3a163a..cd2d181c72 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5523,7 +5523,7 @@ CWalletTx CWallet::CreateLelantusJoinSplitTransaction( std::vector CWallet::CreateSparkSpendTransaction( const std::vector& recipients, - const std::vector& privateRecipients, + const std::vector>& privateRecipients, CAmount &fee, const CCoinControl *coinControl) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 80df282bd9..979690201d 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1067,7 +1067,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::vector CreateSparkSpendTransaction( const std::vector& recipients, - const std::vector& privateRecipients, + const std::vector>& privateRecipients, CAmount &fee, const CCoinControl *coinControl = NULL); From be0469aaa089eff8d7dce56858642ce3cd46c817 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 3 Oct 2022 13:37:15 +0400 Subject: [PATCH 051/197] Spark spend verification --- src/batchproof_container.cpp | 21 ++ src/batchproof_container.h | 9 +- src/libspark/spend_transaction.cpp | 10 +- src/libspark/spend_transaction.h | 7 +- src/script/script.cpp | 9 + src/script/script.h | 2 + src/spark/state.cpp | 429 ++++++++++++++++++++++++++--- src/spark/state.h | 6 +- src/spark/wallet.cpp | 2 +- 9 files changed, 452 insertions(+), 43 deletions(-) diff --git a/src/batchproof_container.cpp b/src/batchproof_container.cpp index 75f039189b..02043e49e1 100644 --- a/src/batchproof_container.cpp +++ b/src/batchproof_container.cpp @@ -387,3 +387,24 @@ void BatchProofContainer::batch_rangeProofs() { rangeProofs.clear(); } + +//TODO levon +void BatchProofContainer::add(const spark::SpendTransaction& tx) { + +} + +void BatchProofContainer::remove(const spark::SpendTransaction& tx) { + +} + +void BatchProofContainer::batch_spark() { + if (!sparkTransactions.empty()){ + LogPrintf("Spark batch verification started.\n"); + uiInterface.UpdateProgressBarLabel("Batch verifying Spark Proofs..."); + } + + + if (!sparkTransactions.empty()) + LogPrintf("Spark batch verification finished successfully.\n"); + sparkTransactions.clear(); +} \ No newline at end of file diff --git a/src/batchproof_container.h b/src/batchproof_container.h index a4c7ff6211..4780c1c32e 100644 --- a/src/batchproof_container.h +++ b/src/batchproof_container.h @@ -5,6 +5,7 @@ #include "chain.h" #include "sigma/coinspend.h" #include "liblelantus/joinsplit.h" +#include "libspark/spend_transaction.h" extern CChain chainActive; @@ -73,6 +74,9 @@ class BatchProofContainer { void batch_lelantus(); void batch_rangeProofs(); + void add(const spark::SpendTransaction& tx); + void remove(const spark::SpendTransaction& tx); + void batch_spark(); public: bool fCollectProofs = 0; @@ -85,12 +89,15 @@ class BatchProofContainer { std::map, bool>, std::vector> tempLelantusSigmaProofs; // map (version to (Range proof, Pubcoins)) std::map>>> tempRangeProofs; + // temp spark transaction proofs + std::vector tempSparkTransactions; // containers to keep proofs for batching std::map>, std::vector> sigmaProofs; std::map, bool>, std::vector> lelantusSigmaProofs; std::map>>> rangeProofs; - + // spark transaction proofs + std::vector sparkTransactions; }; #endif //FIRO_BATCHPROOF_CONTAINER_H diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index 1ef41e5858..b24aa29809 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -207,11 +207,15 @@ uint64_t SpendTransaction::getFee() { return f; } -std::vector& SpendTransaction::getUsedLTags() { +const std::vector& SpendTransaction::getUsedLTags() { return T; } -std::vector& SpendTransaction::getOutCoins() { +const std::vector& SpendTransaction::getCoinGroupIds() { + return cover_set_ids; +} + +const std::vector& SpendTransaction::getOutCoins() { return out_coins; } @@ -428,7 +432,7 @@ void SpendTransaction::setBlockHashes(const std::map& idAndHa set_id_blockHash = idAndHashes; } -std::map& SpendTransaction::getBlockHashes() { +const std::map& SpendTransaction::getBlockHashes() { return set_id_blockHash; } diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h index 6ef30fd104..fd0d116207 100644 --- a/src/libspark/spend_transaction.h +++ b/src/libspark/spend_transaction.h @@ -52,8 +52,9 @@ class SpendTransaction { ); uint64_t getFee(); - std::vector& getUsedLTags(); - std::vector& getOutCoins(); + const std::vector& getUsedLTags(); + const std::vector& getOutCoins(); + const std::vector& getCoinGroupIds(); static bool verify(const Params* params, const std::vector& transactions, const std::unordered_map>& cover_sets); static bool verify(const SpendTransaction& transaction, const std::unordered_map>& cover_sets); @@ -99,7 +100,7 @@ class SpendTransaction { void setBlockHashes(const std::map& idAndHashes); - std::map& getBlockHashes(); + const std::map& getBlockHashes(); private: const Params* params; // We need to construct and pass this data before running verification diff --git a/src/script/script.cpp b/src/script/script.cpp index be5f9e1b6f..56c4962d5d 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -152,6 +152,10 @@ const char* GetOpName(opcodetype opcode) case OP_LELANTUSJMINT : return "OP_LELANTUSJMINT"; case OP_LELANTUSJOINSPLIT : return "OP_LELANTUSJOINSPLIT"; case OP_LELANTUSJOINSPLITPAYLOAD: return "OP_LELANTUSJOINSPLITPAYLOAD"; + // Spark + case OP_SPARKMINT : return "OP_SPARKMINT"; + case OP_SPARKSMINT : return "OP_SPARKSMINT"; + case OP_SPARKSPEND : return "OP_SPARKSPEND"; // Note: // The template matching params OP_SMALLINTEGER/etc are defined in opcodetype enum @@ -336,6 +340,11 @@ bool CScript::IsSparkSMint() const { (*this)[0] == OP_SPARKSMINT); } +bool CScript::IsSparkSpend() const { + return (this->size() > 0 && + (*this)[0] == OP_SPARKSPEND); +} + bool CScript::IsMint() const { return IsZerocoinMint() || IsSigmaMint() || IsZerocoinRemint() || IsLelantusMint() || IsLelantusJMint(); } diff --git a/src/script/script.h b/src/script/script.h index 0c5fcb7013..3f5465271a 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -682,6 +682,8 @@ class CScript : public CScriptBase bool IsSparkSMint() const; + bool IsSparkSpend() const; + bool IsZerocoinRemint() const; bool IsMint() const; diff --git a/src/spark/state.cpp b/src/spark/state.cpp index 6469ea33b7..a9c6d2e072 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -1,5 +1,6 @@ #include "state.h" #include "../validation.h" +#include "../batchproof_container.h" namespace spark { @@ -54,6 +55,35 @@ bool IsSparkAllowed(int height) return height >= ::Params().GetConsensus().nSparkStartBlock; } + +/* + * Util funtions + */ +size_t CountCoinInBlock(CBlockIndex *index, int id) { + return index->sparkMintedCoins.count(id) > 0 + ? index->sparkMintedCoins[id].size() : 0; +} + +std::vector GetAnonymitySetHash(CBlockIndex *index, int group_id, bool generation = false) { + std::vector out_hash; + + CSparkState::SparkCoinGroupInfo coinGroup; + if (!sparkState.GetCoinGroupInfo(group_id, coinGroup)) + return out_hash; + + if ((coinGroup.firstBlock == coinGroup.lastBlock && generation) || (coinGroup.nCoins == 0)) + return out_hash; + + while (index != coinGroup.firstBlock) { + if (index->sparkSetHash.count(group_id) > 0) { + out_hash = index->sparkSetHash[group_id]; + break; + } + index = index->pprev; + } + return out_hash; +} + void ParseSparkMintTransaction(const std::vector& scripts, MintTransaction& mintTransaction) { std::vector serializedCoins; @@ -113,7 +143,7 @@ spark::SpendTransaction ParseSparkSpend(const CTransaction &tx) } -std::vector GetSparkUsedTags(const CTransaction &tx) +std::vector GetSparkUsedTags(const CTransaction &tx) { const spark::Params* params = spark::Params::get_default(); @@ -129,7 +159,90 @@ std::vector GetSparkUsedTags(const CTransaction &tx) std::vector>> GetSparkMintCoins(const CTransaction &tx) { - //TODO levon implement this after spark spend implementation + std::vector>> result; + + if (tx.IsSparkTransaction()) { + CDataStream serialContextStream(SER_NETWORK, PROTOCOL_VERSION); + if (tx.IsSparkSpend()) { + try { + spark::SpendTransaction spend = ParseSparkSpend(tx); + serialContextStream << spend.getUsedLTags(); + } catch (...) { + return result; + } + } else { + for (auto &input: tx.vin) { + serialContextStream << input; + } + } + std::vector serial_context(serialContextStream.begin(), serialContextStream.end()); + for (const auto& vout : tx.vout) { + const auto& script = vout.scriptPubKey; + if (script.IsSparkMint() || script.IsSparkSMint()) { + try { + spark::Coin coin; + ParseSparkMintCoin(script, coin); + result.push_back({coin, serial_context}); + } catch (...) { + //Continue + } + } + } + } + + return result; +} + +size_t GetSpendInputs(const CTransaction &tx) { + return tx.IsSparkSpend() ? + GetSparkUsedTags(tx).size() : 0; +} + +CAmount GetSpendTransparentAmount(const CTransaction& tx) { + CAmount result = 0; + if(!tx.IsSparkSpend()) + return 0; + + for (const CTxOut &txout : tx.vout) + result += txout.nValue; + return result; +} + +bool CheckSparkBlock(CValidationState &state, const CBlock& block) { + auto& consensus = ::Params().GetConsensus(); + + size_t blockSpendsAmount = 0; + CAmount blockSpendsValue(0); + + for (const auto& tx : block.vtx) { + auto txSpendsValue = GetSpendTransparentAmount(*tx); + size_t txSpendNumber = GetSpendInputs(*tx); + + if (txSpendNumber > consensus.nMaxLelantusInputPerTransaction) { //TODO levon define spark limits and refactor here + return state.DoS(100, false, REJECT_INVALID, + "bad-txns-spark-spend-invalid"); + } + + if (txSpendsValue > consensus.nMaxValueLelantusSpendPerTransaction) { + return state.DoS(100, false, REJECT_INVALID, + "bad-txns-spark-spend-invalid"); + } + + blockSpendsAmount += txSpendNumber; + blockSpendsValue += txSpendsValue; + } + + if (blockSpendsAmount > consensus.nMaxLelantusInputPerBlock) { + return state.DoS(100, false, REJECT_INVALID, + "bad-txns-spark-spend-invalid"); + } + + if (blockSpendsValue > consensus.nMaxValueLelantusSpendPerBlock) { + return state.DoS(100, false, REJECT_INVALID, + "bad-txns-spark-spend-invalid"); + } + + return true; } @@ -162,7 +275,7 @@ bool CheckSparkMintTransaction( std::vector coins; mintTransaction.getCoins(coins); - + bool hasCoin = false; for (auto& coin : coins) { if (coin.v > ::Params().GetConsensus().nMaxValueLelantusMint) return state.DoS(100, @@ -170,11 +283,68 @@ bool CheckSparkMintTransaction( REJECT_INVALID, "CTransaction::CheckTransaction() : Spark Mint is out of limit."); - bool hasCoin = sparkState.HasCoin(coin); - if (hasCoin) + hasCoin = sparkState.HasCoin(coin); + + if (!hasCoin && sparkTxInfo != NULL && !sparkTxInfo->fInfoIsComplete) { + for (const auto& mint : sparkTxInfo->mints) { + if (mint == coin) { + hasCoin = true; + break; + } + } + + // Update coin list in the info + sparkTxInfo->mints.push_back(coin); + sparkTxInfo->spTransactions.insert(hashTx); + } + + if (hasCoin && fStatefulSigmaCheck) break; + } - if (sparkTxInfo != NULL && !sparkTxInfo->fInfoIsComplete) { + if (hasCoin && fStatefulSigmaCheck) { + LogPrintf("CheckSparkMintTransaction: double mint, tx=%s\n", hashTx.GetHex()); + return state.DoS(100, + false, + PUBCOIN_NOT_VALIDATE, + "CheckSparkMintTransaction: double mint"); + } + + return true; +} + +bool CheckSparkSMintTransaction( + const std::vector& vout, + CValidationState &state, + uint256 hashTx, + bool fStatefulSigmaCheck, + std::vector& out_coins, + CSparkTxInfo* sparkTxInfo) { + + LogPrintf("CheckSparkSMintTransaction txHash = %s\n", hashTx.ToString()); + out_coins.clear(); + for (const auto& out : vout) { + const auto& script = out.scriptPubKey; + if (script.IsSparkMint() || script.IsSparkSMint()) { + try { + spark::Coin coin; + ParseSparkMintCoin(script, coin); + out_coins.push_back(coin); + } catch (...) { + return state.DoS(100, + false, + REJECT_INVALID, + "CTransaction::CheckTransaction() : Spark Mint is invalid."); + } + } + } + + bool hasCoin = false; + for (auto& coin : out_coins) { + + hasCoin = sparkState.HasCoin(coin); + + if (!hasCoin && sparkTxInfo != NULL && !sparkTxInfo->fInfoIsComplete) { for (const auto& mint : sparkTxInfo->mints) { if (mint == coin) { hasCoin = true; @@ -187,12 +357,209 @@ bool CheckSparkMintTransaction( sparkTxInfo->spTransactions.insert(hashTx); } - if (hasCoin && fStatefulSigmaCheck) { - LogPrintf("CheckSparkMintTransaction: double mint, tx=%s\n", hashTx.GetHex()); + if (hasCoin && fStatefulSigmaCheck) + break; + + } + + if (hasCoin && fStatefulSigmaCheck) { + LogPrintf("CheckSparkMintTransaction: double mint, tx=%s\n", hashTx.GetHex()); + return state.DoS(100, + false, + PUBCOIN_NOT_VALIDATE, + "CheckSparkMintTransaction: double mint"); + } + + return true; +} + +bool CheckSparkSpendTransaction( + const CTransaction &tx, + CValidationState &state, + uint256 hashTx, + bool isVerifyDB, + int nHeight, + int realHeight, + bool isCheckWallet, + bool fStatefulSigmaCheck, + CSparkTxInfo* sparkTxInfo) { + std::unordered_set txLTags; + + if(tx.vin.size() != 1 || !tx.vin[0].scriptSig.IsSparkSpend()) { + // mixing spark spend input with non-spark inputs is prohibited + return state.DoS(100, false, + REJECT_MALFORMED, + "CheckSparkSpendTransaction: can't mix spark spend input with other tx types or have more than one spend"); + } + + Consensus::Params const & params = ::Params().GetConsensus(); + int height = nHeight == INT_MAX ? chainActive.Height()+1 : nHeight; + if (!isVerifyDB) { + if (height >= params.nSparkStartBlock) { + // data should be moved to v3 payload + if (tx.nVersion < 3 || tx.nType != TRANSACTION_LELANTUS) + return state.DoS(100, false, NSEQUENCE_INCORRECT, + "CheckSparkSpendTransaction: spark data should reside in transaction payload"); + } + } + + std::unique_ptr spend; + + try { + spend = std::make_unique(ParseSparkSpend(tx)); + } + catch (CBadTxIn&) { + return state.DoS(100, + false, + REJECT_MALFORMED, + "CheckSparkSpendTransaction: invalid spend transaction"); + } + catch (...) { + return state.DoS(100, + false, + REJECT_MALFORMED, + "CheckSparkSpendTransaction: failed to deserialize spend"); + } + + uint256 txHashForMetadata; + + // Obtain the hash of the transaction sans the zerocoin part + CMutableTransaction txTemp = tx; + txTemp.vin[0].scriptSig.clear(); + txTemp.vExtraPayload.clear(); + + txHashForMetadata = txTemp.GetHash(); + + LogPrintf("CheckSparkSpendTransaction: tx metadata hash=%s\n", txHashForMetadata.ToString()); + + if (!fStatefulSigmaCheck) { + return true; + } + + bool passVerify = false; + + uint64_t Vout = 0; + std::vector vout; + for (const CTxOut &txout : tx.vout) { + const auto& script = txout.scriptPubKey; + if (!script.empty() && script.IsSparkSMint()) { + vout.push_back(txout); + } else if(script.IsSparkMint() || + script.IsLelantusMint() || + script.IsLelantusJMint() || + script.IsSigmaMint()) { + return false; + } else { + Vout += txout.nValue; + } + + } + + std::vector out_coins; + if (!CheckSparkSMintTransaction(vout, state, hashTx, fStatefulSigmaCheck, out_coins, sparkTxInfo)) + return false; + spend->setOutCoins(out_coins); + + std::unordered_map> cover_sets; + std::unordered_map cover_set_data; + const auto idAndBlockHashes = spend->getBlockHashes(); + for (const auto& idAndHash : idAndBlockHashes) { + CSparkState::SparkCoinGroupInfo coinGroup; + if (!sparkState.GetCoinGroupInfo(idAndHash.first, coinGroup)) + return state.DoS(100, false, NO_MINT_ZEROCOIN, + "CheckSparkSpendTransaction: Error: no coins were minted with such parameters"); + + CBlockIndex *index = coinGroup.lastBlock; + // find index for block with hash of accumulatorBlockHash or set index to the coinGroup.firstBlock if not found + while (index != coinGroup.firstBlock && index->GetBlockHash() != idAndHash.second) + index = index->pprev; + + std::vector cover_set; + // Build a vector with all the public coins with given id before + // the block on which the spend occurred. + // This list of public coins is required by function "Verify" of spend. + while (true) { + if(index->sparkMintedCoins.count(idAndHash.first) > 0) { + BOOST_FOREACH( + const auto& coin, + index->sparkMintedCoins[idAndHash.first]) { + cover_set.push_back(coin); + } + } + if (index == coinGroup.firstBlock) + break; + index = index->pprev; + } + + // take the hash from last block of anonymity set + std::vector set_hash = GetAnonymitySetHash(index, idAndHash.first); + CoverSetData setData; + setData.cover_set = cover_set; + if (!set_hash.empty()) + setData.cover_set_representation = set_hash; + setData.cover_set_representation.insert(setData.cover_set_representation.end(), txHashForMetadata.begin(), txHashForMetadata.end()); + + cover_sets[idAndHash.first] = cover_set; + cover_set_data [idAndHash.first] = setData; + } + spend->setCoverSets(cover_set_data); + + BatchProofContainer* batchProofContainer = BatchProofContainer::get_instance(); + bool useBatching = batchProofContainer->fCollectProofs && !isVerifyDB && !isCheckWallet && sparkTxInfo && !sparkTxInfo->fInfoIsComplete; + + // if we are collecting proofs, skip verification and collect proofs + // add proofs into container + if(useBatching) { + passVerify = true; + batchProofContainer->add(*spend); + } else { + passVerify = spark::SpendTransaction::verify(*spend, cover_sets); + } + + if (passVerify) { + const std::vector& lTags = spend->getUsedLTags(); + const std::vector& ids = spend->getCoinGroupIds(); + + if (lTags.size() != ids.size()) { return state.DoS(100, - false, - PUBCOIN_NOT_VALIDATE, - "CheckSparkMintTransaction: double mint"); + error("CheckSparkSpendTransaction: size of lTags and group ids don't match.")); + } + + // do not check for duplicates in case we've seen exact copy of this tx in this block before + if (!(sparkTxInfo && sparkTxInfo->spTransactions.count(hashTx) > 0)) { + for (size_t i = 0; i < lTags.size(); ++i) { + if (!CheckLTag(state, sparkTxInfo, lTags[i], nHeight, false)) { + LogPrintf("CheckSparkSpendTransaction: lTAg check failed, ltag=%s\n", lTags[i]); + return false; + } + } + } + + // check duplicated linking tags in same transaction. + for (const auto &lTag : lTags) { + if (!txLTags.insert(lTag).second) { + return state.DoS(100, + error("CheckSparkSpendTransaction: two or more spends with same linking tag in the same transaction")); + } + } + + if (!isVerifyDB && !isCheckWallet) { + // add spend information to the index + if (sparkTxInfo && !sparkTxInfo->fInfoIsComplete) { + for (size_t i = 0; i < lTags.size(); i++) { + sparkTxInfo->spentLTags.insert(std::make_pair(lTags[i], ids[i])); + } + } + } + } + else { + LogPrintf("CheckSparkSpendTransaction: verification failed at block %d\n", nHeight); + return false; + } + + if(!isVerifyDB && !isCheckWallet) { + if (sparkTxInfo && !sparkTxInfo->fInfoIsComplete) { + sparkTxInfo->spTransactions.insert(hashTx); } } @@ -284,32 +651,26 @@ bool GetOutPointFromBlock(COutPoint& outPoint, const spark::Coin& coin, const CB return false; } -/* - * Util funtions - */ -size_t CountCoinInBlock(CBlockIndex *index, int id) { - return index->sparkMintedCoins.count(id) > 0 - ? index->sparkMintedCoins[id].size() : 0; -} - -std::vector GetAnonymitySetHash(CBlockIndex *index, int group_id, bool generation = false) { - std::vector out_hash; - - CSparkState::SparkCoinGroupInfo coinGroup; - if (!sparkState.GetCoinGroupInfo(group_id, coinGroup)) - return out_hash; - - if ((coinGroup.firstBlock == coinGroup.lastBlock && generation) || (coinGroup.nCoins == 0)) - return out_hash; +static bool CheckSparkSpendTAg( + CValidationState& state, + CSparkTxInfo* sparkTxInfo, + const GroupElement& tag, + int nHeight, + bool fConnectTip) { + // check for spark transaction in this block as well + if (sparkTxInfo && + !sparkTxInfo->fInfoIsComplete && + sparkTxInfo->spentLTags.find(tag) != sparkTxInfo->spentLTags.end()) + return state.DoS(0, error("CTransaction::CheckTransaction() : two or more spark spends with same tag in the same block")); - while (index != coinGroup.firstBlock) { - if (index->sparkSetHash.count(group_id) > 0) { - out_hash = index->sparkSetHash[group_id]; - break; + // check for used tags in sparkState + if (sparkState.IsUsedLTag(tag)) { + // Proceed with checks ONLY if we're accepting tx into the memory pool or connecting block to the existing blockchain + if (nHeight == INT_MAX || fConnectTip) { + return state.DoS(0, error("CTransaction::CheckTransaction() : The Spark spend tag has been used")); } - index = index->pprev; } - return out_hash; + return true; } /******************************************************************************/ diff --git a/src/spark/state.h b/src/spark/state.h index 4f7b5d8257..30b9bf8767 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -47,6 +47,10 @@ spark::SpendTransaction ParseSparkSpend(const CTransaction &tx); std::vector GetSparkUsedTags(const CTransaction &tx); std::vector>> GetSparkMintCoins(const CTransaction &tx); +size_t GetSpendInputs(const CTransaction &tx); +CAmount GetSpendTransparentAmount(const CTransaction& tx); + +bool CheckSparkBlock(CValidationState &state, const CBlock& block); bool CheckSparkTransaction( const CTransaction &tx, @@ -161,7 +165,7 @@ class CSparkState { // Remove spend from the mempool (usually as the result of adding tx to the block) void RemoveSpendFromMempool(const std::vector& lTags); - // Given id returns latest anonymity set and corresponding block hash + // Given id returns the latest anonymity set and corresponding block hash // Do not take into account coins with height more than maxHeight // Returns number of coins satisfying conditions int GetCoinSetForSpend( diff --git a/src/spark/wallet.cpp b/src/spark/wallet.cpp index eb8152aa11..8f287809e4 100644 --- a/src/spark/wallet.cpp +++ b/src/spark/wallet.cpp @@ -1053,7 +1053,7 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( spark::SpendTransaction spendTransaction(params, fullViewKey, spendKey, inputs, cover_set_data, fee, privOutputs); spendTransaction.setBlockHashes(idAndBlockHashes); - std::vector& outCoins = spendTransaction.getOutCoins(); + const std::vector& outCoins = spendTransaction.getOutCoins(); for (auto& outCoin : outCoins) { // construct spend script CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); From c4cf08c4b64427f422aa4f52d6741dc50cd5cd9f Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 17 Oct 2022 13:56:55 +0400 Subject: [PATCH 052/197] More state functionality implemented and bug fixes --- src/Makefile.test.include | 1 + src/batchproof_container.cpp | 42 ++- src/chain.h | 3 + src/libspark/coin.cpp | 21 +- src/libspark/coin.h | 8 +- src/libspark/mint_transaction.cpp | 6 +- src/libspark/spend_transaction.cpp | 2 +- src/libspark/spend_transaction.h | 2 +- src/policy/policy.cpp | 18 +- src/primitives/block.h | 7 + src/primitives/transaction.cpp | 2 +- src/script/standard.cpp | 17 ++ src/script/standard.h | 4 +- src/spark/state.cpp | 444 +++++++++++++++++++++++++++-- src/spark/state.h | 30 +- src/spark/wallet.cpp | 104 +++++-- src/spark/wallet.h | 9 +- src/test/fixtures.cpp | 86 ++++++ src/test/fixtures.h | 22 ++ src/txmempool.cpp | 28 ++ src/validation.cpp | 126 ++++++-- src/validation.h | 2 +- src/wallet/wallet.cpp | 6 +- src/wallet/wallet.h | 2 +- 24 files changed, 893 insertions(+), 99 deletions(-) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index fe519a8ade..87f5cb7e53 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -102,6 +102,7 @@ BITCOIN_TESTS = \ libspark/test/spend_transaction_test.cpp \ libspark/test/f4grumble_test.cpp \ libspark/test/address_test.cpp \ + test/spark_tests.cpp \ sigma/test/coin_spend_tests.cpp \ sigma/test/coin_tests.cpp \ sigma/test/primitives_tests.cpp \ diff --git a/src/batchproof_container.cpp b/src/batchproof_container.cpp index 02043e49e1..9b89c69b25 100644 --- a/src/batchproof_container.cpp +++ b/src/batchproof_container.cpp @@ -6,6 +6,7 @@ #include "sigma.h" #include "lelantus.h" #include "ui_interface.h" +#include "spark/state.h" std::unique_ptr BatchProofContainer::instance; @@ -22,6 +23,7 @@ void BatchProofContainer::init() { tempSigmaProofs.clear(); tempLelantusSigmaProofs.clear(); tempRangeProofs.clear(); + tempSparkTransactions.clear(); } void BatchProofContainer::finalize() { @@ -37,6 +39,8 @@ void BatchProofContainer::finalize() { for (const auto& itr : tempRangeProofs) { rangeProofs[itr.first].insert(rangeProofs[itr.first].begin(), itr.second.begin(), itr.second.end()); } + + sparkTransactions.insert(sparkTransactions.end(), tempSparkTransactions.begin(), tempSparkTransactions.end()); } fCollectProofs = false; } @@ -46,6 +50,7 @@ void BatchProofContainer::verify() { batch_sigma(); batch_lelantus(); batch_rangeProofs(); + batch_spark(); } fCollectProofs = false; } @@ -388,21 +393,52 @@ void BatchProofContainer::batch_rangeProofs() { rangeProofs.clear(); } -//TODO levon void BatchProofContainer::add(const spark::SpendTransaction& tx) { - + tempSparkTransactions.push_back(tx); } void BatchProofContainer::remove(const spark::SpendTransaction& tx) { - + sparkTransactions.erase(std::remove_if(sparkTransactions.begin(), + sparkTransactions.end(), + [tx](spark::SpendTransaction& transaction){return transaction.getUsedLTags() == tx.getUsedLTags();}), + sparkTransactions.end()); } void BatchProofContainer::batch_spark() { if (!sparkTransactions.empty()){ LogPrintf("Spark batch verification started.\n"); uiInterface.UpdateProgressBarLabel("Batch verifying Spark Proofs..."); + } else { + return; + } + + std::unordered_map> cover_sets; + spark::CSparkState* sparkState = spark::CSparkState::GetState(); + + for (auto& itr : sparkTransactions) { + auto& idAndBlockHashes = itr.getBlockHashes(); + for (const auto& idAndHash : idAndBlockHashes) { + int cover_set_id = idAndHash.first; + if (!cover_sets.count(cover_set_id)) { + std::vector cover_set; + sparkState->GetCoinSet(cover_set_id, cover_set); + cover_sets[cover_set_id] = cover_set; + } + } } + auto* params = spark::Params::get_default(); + bool passed; + try { + passed = spark::SpendTransaction::verify(params, sparkTransactions, cover_sets); + } catch (...) { + passed = false; + } + + if (!passed) { + LogPrintf("Spark batch verification failed."); + throw std::invalid_argument("Spark batch verification failed, please run Firo with -reindex -batching=0"); + } if (!sparkTransactions.empty()) LogPrintf("Spark batch verification finished successfully.\n"); diff --git a/src/chain.h b/src/chain.h index 96a6d0063c..e9d6bdab89 100644 --- a/src/chain.h +++ b/src/chain.h @@ -255,6 +255,7 @@ class CBlockIndex //! Values of coin serials spent in this block sigma::spend_info_container sigmaSpentSerials; std::unordered_map lelantusSpentSerials; + std::unordered_map spentLTags; //! list of disabling sporks active at this block height //! std::map {feature name} -> {block number when feature is re-enabled again, parameter} @@ -295,6 +296,7 @@ class CBlockIndex anonymitySetHash.clear(); sparkMintedCoins.clear(); sparkSetHash.clear(); + spentLTags.clear(); sigmaSpentSerials.clear(); lelantusSpentSerials.clear(); activeDisablingSporks.clear(); @@ -542,6 +544,7 @@ class CDiskBlockIndex : public CBlockIndex && nHeight >= params.nSparkStartBlock) { READWRITE(sparkMintedCoins); READWRITE(sparkSetHash); + READWRITE(spentLTags); } if (nHeight >= params.nLelantusFixesStartBlock) diff --git a/src/libspark/coin.cpp b/src/libspark/coin.cpp index a558deb280..34b73005c0 100644 --- a/src/libspark/coin.cpp +++ b/src/libspark/coin.cpp @@ -7,14 +7,19 @@ using namespace secp_primitives; Coin::Coin() {} +Coin::Coin(const Params* params) +{ + this->params = params; +} + Coin::Coin( const Params* params, const char type, const Scalar& k, const Address& address, - const uint64_t v, - const std::string memo, - const std::vector serial_context + const uint64_t& v, + const std::string& memo, + const std::vector& serial_context ) { this->params = params; this->serial_context = serial_context; @@ -83,19 +88,19 @@ bool Coin::validate( ) { // Check recovery key if (SparkUtils::hash_div(data.d)*SparkUtils::hash_k(data.k) != this->K) { - return false; + return false; } // Check value commitment if (this->params->get_G()*Scalar(data.v) + this->params->get_H()*SparkUtils::hash_val(data.k) != this->C) { - return false; + return false; } // Check serial commitment data.i = incoming_view_key.get_diversifier(data.d); if (this->params->get_F()*(SparkUtils::hash_ser(data.k, this->serial_context) + SparkUtils::hash_Q2(incoming_view_key.get_s1(), data.i)) + incoming_view_key.get_P2() != this->S) { - return false; + return false; } return true; @@ -174,4 +179,8 @@ uint256 Coin::getHash() const { return ::Hash(ss.begin(), ss.end()); } +void Coin::setSerialContext(const std::vector& serial_context_) { + serial_context = serial_context_; +} + } diff --git a/src/libspark/coin.h b/src/libspark/coin.h index 526e104e63..39989c669a 100644 --- a/src/libspark/coin.h +++ b/src/libspark/coin.h @@ -66,14 +66,15 @@ struct SpendCoinRecipientData { class Coin { public: Coin(); + Coin(const Params* params); Coin( const Params* params, const char type, const Scalar& k, const Address& address, - const uint64_t v, - const std::string memo, - const std::vector serial_context + const uint64_t& v, + const std::string& memo, + const std::vector& serial_context ); // Given an incoming view key, extract the coin's nonce, diversifier, value, and memo @@ -89,6 +90,7 @@ class Coin { // type and v are not included in hash uint256 getHash() const; + void setSerialContext(const std::vector& serial_context_); protected: bool validate(const IncomingViewKey& incoming_view_key, IdentifiedCoinData& data); diff --git a/src/libspark/mint_transaction.cpp b/src/libspark/mint_transaction.cpp index 30743e0f73..7915ff6050 100644 --- a/src/libspark/mint_transaction.cpp +++ b/src/libspark/mint_transaction.cpp @@ -88,10 +88,12 @@ std::vector MintTransaction::getMintedCoinsSerialized() { void MintTransaction::setMintTransaction(std::vector& serializedCoins) { bool first = true; - coins.resize(serializedCoins.size()); + coins.reserve(serializedCoins.size()); size_t i = 0; for (auto& stream : serializedCoins) { - stream >> coins[i]; + Coin coin(params); + stream >> coin; + coins.push_back(coin); i++; if (first) { stream >> value_proof; diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index b24aa29809..9295b288f8 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -207,7 +207,7 @@ uint64_t SpendTransaction::getFee() { return f; } -const std::vector& SpendTransaction::getUsedLTags() { +const std::vector& SpendTransaction::getUsedLTags() const { return T; } diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h index fd0d116207..cb5c2893b5 100644 --- a/src/libspark/spend_transaction.h +++ b/src/libspark/spend_transaction.h @@ -52,7 +52,7 @@ class SpendTransaction { ); uint64_t getFee(); - const std::vector& getUsedLTags(); + const std::vector& getUsedLTags() const; const std::vector& getOutCoins(); const std::vector& getCoinGroupIds(); diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 572fe39761..8229f49030 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -68,7 +68,7 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnes // computing signature hashes is O(ninputs*txsize). Limiting transactions // to MAX_STANDARD_TX_WEIGHT mitigates CPU exhaustion attacks. unsigned int sz = GetTransactionWeight(tx); - unsigned int szLimit = tx.IsLelantusJoinSplit() ? MAX_LELANTUS_TX_WEIGHT : MAX_STANDARD_TX_WEIGHT; + unsigned int szLimit = (tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) ? MAX_LELANTUS_TX_WEIGHT : MAX_STANDARD_TX_WEIGHT; if (sz >= szLimit) { reason = "tx-size"; return false; @@ -92,7 +92,7 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnes return false; } - if (!txin.scriptSig.IsZerocoinSpend() && !txin.scriptSig.IsSigmaSpend() && !txin.scriptSig.IsLelantusJoinSplit() && !txin.IsZerocoinRemint()) { + if (!txin.scriptSig.IsZerocoinSpend() && !txin.scriptSig.IsSigmaSpend() && !txin.scriptSig.IsLelantusJoinSplit() && !txin.scriptSig.IsSparkSpend() && !txin.IsZerocoinRemint()) { if (!txin.scriptSig.IsPushOnly()) { reason = "scriptsig-not-pushonly"; return false; @@ -134,7 +134,12 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason, const bool witnes bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) { - if (tx.IsCoinBase() || tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsLelantusJoinSplit()) + if (tx.IsCoinBase() + || tx.IsZerocoinSpend() + || tx.IsSigmaSpend() + || tx.IsZerocoinRemint() + || tx.IsLelantusJoinSplit() + || tx.IsSparkSpend()) return true; // Coinbases don't use vin normally for (unsigned int i = 0; i < tx.vin.size(); i++) @@ -168,7 +173,12 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) { - if (tx.IsCoinBase() || tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsLelantusJoinSplit()) + if (tx.IsCoinBase() + || tx.IsZerocoinSpend() + || tx.IsSigmaSpend() + || tx.IsZerocoinRemint() + || tx.IsLelantusJoinSplit() + || tx.IsSparkSpend()) return true; // Coinbases are skipped for (unsigned int i = 0; i < tx.vin.size(); i++) diff --git a/src/primitives/block.h b/src/primitives/block.h index a54adda409..d7ce6ce111 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -29,6 +29,11 @@ class CLelantusTxInfo; } // namespace lelantus +namespace spark { + class CSparkTxInfo; + +} // namespace spark + unsigned char GetNfactor(int64_t nTimestamp); /** Nodes collect new transactions into a block, hash them into a hash tree, @@ -289,6 +294,8 @@ class CBlock : public CBlockHeader mutable std::shared_ptr lelantusTxInfo; + mutable std::shared_ptr sparkTxInfo; + CBlock() { SetNull(); diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 8bd3b5270f..6167aa60cb 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -225,7 +225,7 @@ bool CTransaction::IsSparkSpend() const bool CTransaction::IsSparkMint() const { for (const CTxOut &txout: vout) { - if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) + if (txout.scriptPubKey.IsSparkMint()) return true; } return false; diff --git a/src/script/standard.cpp b/src/script/standard.cpp index cc13c55248..febb58b80e 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -116,6 +116,23 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector witnessprogram; if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) { diff --git a/src/script/standard.h b/src/script/standard.h index 64532a0128..7840a0170b 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -56,7 +56,9 @@ enum txnouttype TX_ZEROCOINMINT, TX_ZEROCOINMINTV3, TX_LELANTUSMINT, - TX_LELANTUSJMINT + TX_LELANTUSJMINT, + TX_SPARKMINT, + TX_SPARKSMINT }; class CNoDestination { diff --git a/src/spark/state.cpp b/src/spark/state.cpp index a9c6d2e072..f45d31459b 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -28,6 +28,18 @@ static bool CheckLTag( return true; } +bool BuildSparkStateFromIndex(CChain *chain) { + for (CBlockIndex *blockIndex = chain->Genesis(); blockIndex; blockIndex=chain->Next(blockIndex)) + { + sparkState.AddBlock(blockIndex); + } + // DEBUG + LogPrintf( + "Latest ID for Spark coin group %d\n", + sparkState.GetLatestCoinID()); + return true; +} + // CSparkTxInfo void CSparkTxInfo::Complete() { // We need to sort mints lexicographically by serialized value of pubCoin. That's the way old code @@ -157,9 +169,9 @@ std::vector GetSparkUsedTags(const CTransaction &tx) return spendTransaction.getUsedLTags(); } -std::vector>> GetSparkMintCoins(const CTransaction &tx) +std::vector GetSparkMintCoins(const CTransaction &tx) { - std::vector>> result; + std::vector result; if (tx.IsSparkTransaction()) { CDataStream serialContextStream(SER_NETWORK, PROTOCOL_VERSION); @@ -171,18 +183,21 @@ std::vector>> GetSparkMintCoin return result; } } else { - for (auto &input: tx.vin) { + for (auto input: tx.vin) { + input.scriptSig.clear(); serialContextStream << input; } } + std::vector serial_context(serialContextStream.begin(), serialContextStream.end()); for (const auto& vout : tx.vout) { const auto& script = vout.scriptPubKey; if (script.IsSparkMint() || script.IsSparkSMint()) { try { - spark::Coin coin; + spark::Coin coin(Params::get_default()); ParseSparkMintCoin(script, coin); - result.push_back({coin, serial_context}); + coin.setSerialContext(serial_context); + result.push_back(coin); } catch (...) { //Continue } @@ -208,6 +223,138 @@ CAmount GetSpendTransparentAmount(const CTransaction& tx) { return result; } +/** + * Connect a new ZCblock to chainActive. pblock is either NULL or a pointer to a CBlock + * corresponding to pindexNew, to bypass loading it again from disk. + */ +bool ConnectBlockSpark( + CValidationState &state, + const CChainParams &chainparams, + CBlockIndex *pindexNew, + const CBlock *pblock, + bool fJustCheck) { + // Add spark transaction information to index + if (pblock && pblock->sparkTxInfo) { + if (!fJustCheck) { + pindexNew->sparkMintedCoins.clear(); + pindexNew->spentLTags.clear(); + pindexNew->sparkSetHash.clear(); + } + + if (!CheckSparkBlock(state, *pblock)) { + return false; + } + + BOOST_FOREACH(auto& lTag, pblock->sparkTxInfo->spentLTags) { + if (!CheckLTag( + state, + pblock->sparkTxInfo.get(), + lTag.first, + pindexNew->nHeight, + true /* fConnectTip */ + )) { + return false; + } + + if (!fJustCheck) { + pindexNew->spentLTags.insert(lTag); + sparkState.AddSpend(lTag.first, lTag.second); + } + } + + if (fJustCheck) + return true; + + const auto& params = ::Params().GetConsensus(); + CHash256 hash; + bool updateHash = false; + + if (!pblock->sparkTxInfo->mints.empty()) { + sparkState.AddMintsToStateAndBlockIndex(pindexNew, pblock); + int latestCoinId = sparkState.GetLatestCoinID(); + // add coins into hasher, for generating set hash + updateHash = true; + // get previous hash of the set, if there is no such, don't write anything + std::vector prev_hash = GetAnonymitySetHash(pindexNew->pprev, latestCoinId, true); + if (!prev_hash.empty()) + hash.Write(prev_hash.data(), 32); + else { + if(latestCoinId > 1) { + prev_hash = GetAnonymitySetHash(pindexNew->pprev, latestCoinId - 1, true); + hash.Write(prev_hash.data(), 32); + } + } + + for (auto &coin : pindexNew->sparkMintedCoins[latestCoinId]) { + CDataStream serializedCoin(SER_NETWORK, 0); + serializedCoin << coin; + std::vector data(serializedCoin.begin(), serializedCoin.end()); + hash.Write(data.data(), data.size()); + } + } + + // generate hash if we need it + if (updateHash) { + unsigned char hash_result[CSHA256::OUTPUT_SIZE]; + hash.Finalize(hash_result); + auto &out_hash = pindexNew->sparkSetHash[sparkState.GetLatestCoinID()]; + out_hash.clear(); + out_hash.insert(out_hash.begin(), std::begin(hash_result), std::end(hash_result)); + } + } + else if (!fJustCheck) { + sparkState.AddBlock(pindexNew); + } + return true; +} + +void RemoveSpendReferencingBlock(CTxMemPool& pool, CBlockIndex* blockIndex) { + LOCK2(cs_main, pool.cs); + std::vector txn_to_remove; + for (CTxMemPool::txiter mi = pool.mapTx.begin(); mi != pool.mapTx.end(); ++mi) { + const CTransaction& tx = mi->GetTx(); + if (tx.IsSparkSpend()) { + // Run over all the inputs, check if their CoinGroup block hash is equal to + // block removed. If any one is equal, remove txn from mempool. + for (const CTxIn& txin : tx.vin) { + if (txin.scriptSig.IsSparkSpend()) { + std::unique_ptr sparkSpend; + + try { + sparkSpend = std::make_unique(ParseSparkSpend(tx)); + } + catch (...) { + txn_to_remove.push_back(tx); + break; + } + + const std::map& coinGroupIdAndBlockHash = sparkSpend->getBlockHashes(); + for(const auto& idAndHash : coinGroupIdAndBlockHash) { + if (idAndHash.second == blockIndex->GetBlockHash()) { + // Do not remove transaction immediately, that will invalidate iterator mi. + txn_to_remove.push_back(tx); + break; + } + } + } + } + } + } + for (const CTransaction& tx: txn_to_remove) { + // Remove txn from mempool. + pool.removeRecursive(tx); + LogPrintf("DisconnectTipSpark: removed spark spend which referenced a removed blockchain tip."); + } +} + +void DisconnectTipSpark(CBlock& block, CBlockIndex *pindexDelete) { + sparkState.RemoveBlock(pindexDelete); + + // Also remove from mempool lelantus joinsplits that reference given block hash. + RemoveSpendReferencingBlock(mempool, pindexDelete); + RemoveSpendReferencingBlock(txpools.getStemTxPool(), pindexDelete); +} + bool CheckSparkBlock(CValidationState &state, const CBlock& block) { auto& consensus = ::Params().GetConsensus(); @@ -247,7 +394,7 @@ bool CheckSparkBlock(CValidationState &state, const CBlock& block) { bool CheckSparkMintTransaction( - const std::vector& scripts, + const std::vector& txOuts, CValidationState &state, uint256 hashTx, bool fStatefulSigmaCheck, @@ -255,6 +402,10 @@ bool CheckSparkMintTransaction( LogPrintf("CheckSparkMintTransaction txHash = %s\n", hashTx.GetHex()); const spark::Params* params = spark::Params::get_default(); + std::vector scripts; + for (const auto& txOut : txOuts) { + scripts.push_back(txOut.scriptPubKey); + } MintTransaction mintTransaction(params); try { @@ -267,16 +418,32 @@ bool CheckSparkMintTransaction( } //checking whether MintTransaction is valid - if(!mintTransaction.verify()) + if(!mintTransaction.verify()) { return state.DoS(100, false, PUBCOIN_NOT_VALIDATE, "CheckSparkMintTransaction : mintTransaction verification failed"); - + } std::vector coins; mintTransaction.getCoins(coins); + + if (coins.size() != txOuts.size()) + return state.DoS(100, + false, + PUBCOIN_NOT_VALIDATE, + "CheckSparkMintTransaction : mintTransaction parsing failed"); + + + bool hasCoin = false; - for (auto& coin : coins) { + for (size_t i = 0; i < coins.size(); i++) { + auto& coin = coins[i]; + if (coin.v != txOuts[i].nValue) + return state.DoS(100, + false, + PUBCOIN_NOT_VALIDATE, + "CheckSparkMintTransaction : mintTransaction failed, wrong amount"); + if (coin.v > ::Params().GetConsensus().nMaxValueLelantusMint) return state.DoS(100, false, @@ -327,7 +494,7 @@ bool CheckSparkSMintTransaction( const auto& script = out.scriptPubKey; if (script.IsSparkMint() || script.IsSparkSMint()) { try { - spark::Coin coin; + spark::Coin coin(Params::get_default()); ParseSparkMintCoin(script, coin); out_coins.push_back(coin); } catch (...) { @@ -379,7 +546,6 @@ bool CheckSparkSpendTransaction( uint256 hashTx, bool isVerifyDB, int nHeight, - int realHeight, bool isCheckWallet, bool fStatefulSigmaCheck, CSparkTxInfo* sparkTxInfo) { @@ -479,13 +645,22 @@ bool CheckSparkSpendTransaction( // the block on which the spend occurred. // This list of public coins is required by function "Verify" of spend. while (true) { - if(index->sparkMintedCoins.count(idAndHash.first) > 0) { - BOOST_FOREACH( - const auto& coin, - index->sparkMintedCoins[idAndHash.first]) { - cover_set.push_back(coin); + int id = 0; + if (CountCoinInBlock(index, idAndHash.first)) { + id = idAndHash.first; + } else if (CountCoinInBlock(index, idAndHash.first - 1)) { + id = idAndHash.first - 1; + } + if (id) { + if(index->sparkMintedCoins.count(idAndHash.first) > 0) { + BOOST_FOREACH( + const auto& coin, + index->sparkMintedCoins[idAndHash.first]) { + cover_set.push_back(coin); + } } } + if (index == coinGroup.firstBlock) break; index = index->pprev; @@ -581,15 +756,45 @@ bool CheckSparkTransaction( bool const allowSpark = IsSparkAllowed(); // Check Spark Mint Transaction - if (allowSpark && !isVerifyDB) { + if (allowSpark && !isVerifyDB && tx.IsSparkMint()) { + std::vector txOuts; for (const CTxOut &txout : tx.vout) { - std::vector scripts; if (!txout.scriptPubKey.empty() && txout.scriptPubKey.IsSparkMint()) { - scripts.push_back(txout.scriptPubKey); + txOuts.push_back(txout); + } + } + if (!txOuts.empty()) { + if (!CheckSparkMintTransaction(txOuts, state, hashTx, fStatefulSigmaCheck, sparkTxInfo)) { + LogPrintf("CheckSparkTransaction::Mint verification failed.\n"); + return false; } - if (!scripts.empty()) { - if (!CheckSparkMintTransaction(scripts, state, hashTx, fStatefulSigmaCheck, sparkTxInfo)) - return false; + } else { + return state.DoS(100, false, + REJECT_INVALID, + "bad-txns-mint-invalid"); + } + } + + // Check Spark Spend + if(tx.IsSparkSpend()) { + // First check number of inputs does not exceed transaction limit + if (GetSpendInputs(tx) > consensus.nMaxLelantusInputPerTransaction) { //TODO levon define spark limits and refactor here + return state.DoS(100, false, + REJECT_INVALID, + "bad-txns-spend-invalid"); + } + + if (GetSpendTransparentAmount(tx) > consensus.nMaxValueLelantusSpendPerTransaction) { + return state.DoS(100, false, + REJECT_INVALID, + "bad-txns-spend-invalid"); + } + + if (!isVerifyDB) { + if (!CheckSparkSpendTransaction( + tx, state, hashTx, isVerifyDB, nHeight, + isCheckWallet, fStatefulSigmaCheck, sparkTxInfo)) { + return false; } } } @@ -618,7 +823,7 @@ bool GetOutPoint(COutPoint& outPoint, const spark::Coin& coin) bool GetOutPoint(COutPoint& outPoint, const uint256& coinHash) { - spark::Coin coin; + spark::Coin coin(Params::get_default()); spark::CSparkState *sparkState = spark::CSparkState::GetState(); if(!sparkState->HasCoinHash(coin, coinHash)) { return false; @@ -628,7 +833,7 @@ bool GetOutPoint(COutPoint& outPoint, const uint256& coinHash) } bool GetOutPointFromBlock(COutPoint& outPoint, const spark::Coin& coin, const CBlock &block) { - spark::Coin txCoin; + spark::Coin txCoin(coin.params); // cycle transaction hashes, looking for this coin for (CTransactionRef tx : block.vtx){ uint32_t nIndex = 0; @@ -773,6 +978,47 @@ void CSparkState::RemoveMint(const spark::Coin& coin) { } } +void CSparkState::AddMintsToStateAndBlockIndex( + CBlockIndex *index, + const CBlock* pblock) { + + std::vector blockMints = pblock->sparkTxInfo->mints; + + latestCoinId = std::max(1, latestCoinId); + auto &coinGroup = coinGroups[latestCoinId]; + + if (coinGroup.nCoins + blockMints.size() <= maxCoinInGroup) { + if (coinGroup.nCoins == 0) { + // first group of coins + assert(coinGroup.firstBlock == nullptr); + assert(coinGroup.lastBlock == nullptr); + + coinGroup.firstBlock = coinGroup.lastBlock = index; + } else { + assert(coinGroup.firstBlock != nullptr); + assert(coinGroup.lastBlock != nullptr); + assert(coinGroup.lastBlock->nHeight <= index->nHeight); + + coinGroup.lastBlock = index; + } + coinGroup.nCoins += blockMints.size(); + } else { + auto& newCoinGroup = coinGroups[++latestCoinId]; + + CBlockIndex *first; + auto coins = CountLastNCoins(latestCoinId - 1, startGroupSize, first); + newCoinGroup.firstBlock = first ? first : index; + newCoinGroup.lastBlock = index; + newCoinGroup.nCoins = coins + blockMints.size(); + } + + for (const auto& mint : blockMints) { + AddMint(mint, CMintedCoinInfo::make(latestCoinId, index->nHeight)); + LogPrintf("AddMintsToStateAndBlockIndex: Spark mint added id=%d\n", latestCoinId); + index->sparkMintedCoins[latestCoinId].push_back(mint); + } +} + void CSparkState::AddSpend(const GroupElement& lTag, int coinGroupId) { if (!mintMetaInfo.count(coinGroupId)) { throw std::invalid_argument("group id doesn't exist"); @@ -790,6 +1036,106 @@ void CSparkState::RemoveSpend(const GroupElement& lTag) { } } +void CSparkState::AddBlock(CBlockIndex *index) { + for (auto const& coins : index->sparkMintedCoins) { + if (coins.second.empty()) + continue; + + auto &coinGroup = coinGroups[coins.first]; + + if (coinGroup.firstBlock == nullptr) { + coinGroup.firstBlock = index; + + if (coins.first > 1) { + CBlockIndex *first; + coinGroup.nCoins = CountLastNCoins(coins.first - 1, startGroupSize, first); + coinGroup.firstBlock = first ? first : index; + } + } + coinGroup.lastBlock = index; + coinGroup.nCoins += coins.second.size(); + + latestCoinId = coins.first; + for (auto const &coin : coins.second) { + AddMint(coin, CMintedCoinInfo::make(coins.first, index->nHeight)); + } + } + + for (auto const &lTags : index->spentLTags) { + AddSpend(lTags.first, lTags.second); + } +} + +void CSparkState::RemoveBlock(CBlockIndex *index) { + // roll back coin group updates + for (auto &coins : index->sparkMintedCoins) + { + if (coinGroups.count(coins.first) == 0) { + throw std::invalid_argument("Group Id does not exist"); + } + + SparkCoinGroupInfo& coinGroup = coinGroups[coins.first]; + auto nMintsToForget = coins.second.size(); + + if (nMintsToForget == 0) + continue; + + assert(coinGroup.nCoins >= nMintsToForget); + auto isExtended = coins.first > 1; + coinGroup.nCoins -= nMintsToForget; + + // if `index` is edged block we need to erase group + auto isEdgedBlock = false; + if (isExtended) { + auto prevBlockContainMints = index; + size_t prevGroupCount = 0; + + // find block that contain some Spark mints + do { + prevBlockContainMints = prevBlockContainMints->pprev; + } while (prevBlockContainMints + && CountCoinInBlock(prevBlockContainMints, coins.first) == 0 + && (prevGroupCount = CountCoinInBlock(prevBlockContainMints, coins.first - 1)) == 0); + + isEdgedBlock = prevGroupCount > 0 && (coinGroup.nCoins - prevGroupCount) < startGroupSize; + } + + if ((!isExtended && coinGroup.nCoins == 0) || (isExtended && isEdgedBlock)) { + // all the coins of this group have been erased, remove the group altogether + coinGroups.erase(coins.first); + // decrease pubcoin id + latestCoinId--; + } else { + // roll back lastBlock to previous position + assert(coinGroup.lastBlock == index); + + do { + assert(coinGroup.lastBlock != coinGroup.firstBlock); + coinGroup.lastBlock = coinGroup.lastBlock->pprev; + } while (coinGroup.lastBlock->sparkMintedCoins.count(coins.first) == 0); + } + } + + // roll back mints + for (auto const&coins : index->sparkMintedCoins) { + for (auto const& coin : coins.second) { + auto mintCoins = GetMints().equal_range(coin); + auto coinIt = find_if( + mintCoins.first, mintCoins.second, + [&coins](const std::unordered_map::value_type& v) { + return v.second.coinGroupId == coins.first; + }); + assert(coinIt != mintCoins.second); + RemoveMint(coinIt->first); + } + } + + // roll back spends + for (auto const& lTag : index->spentLTags) { + RemoveSpend(lTag.first); + } +} + bool CSparkState::AddSpendToMempool(const std::vector& lTags, uint256 txHash) { LOCK(mempool.cs); for (const auto& lTag : lTags){ @@ -809,10 +1155,10 @@ void CSparkState::RemoveSpendFromMempool(const std::vector& lTags) } } -void CSparkState::AddMintsToMempool(const std::vector>>& coins) { +void CSparkState::AddMintsToMempool(const std::vector& coins) { LOCK(mempool.cs); for (const auto& coin : coins) { - mempool.sparkState.AddMintToMempool(coin.first); + mempool.sparkState.AddMintToMempool(coin); } } @@ -830,6 +1176,26 @@ CSparkState* CSparkState::GetState() { return &sparkState; } +void CSparkState::GetCoinSet( + int coinGroupID, + std::vector& coins_out) { + int maxHeight; + uint256 blockHash; + std::vector setHash; + { + const auto ¶ms = ::Params().GetConsensus(); + LOCK(cs_main); + int maxHeight = chainActive.Height() - (ZC_MINT_CONFIRMATIONS - 1); + } + GetCoinSetForSpend( + &chainActive, + maxHeight, + coinGroupID, + blockHash, + coins_out, + setHash); +} + int CSparkState::GetCoinSetForSpend( CChain *chain, int maxHeight, @@ -901,6 +1267,32 @@ std::unordered_map const& CSparkState:: return mempool.sparkState.GetMempoolLTags(); } +// private +size_t CSparkState::CountLastNCoins(int groupId, size_t required, CBlockIndex* &first) { + first = nullptr; + size_t coins = 0; + + if (coinGroups.count(groupId)) { + auto &group = coinGroups[groupId]; + + for (auto block = group.lastBlock + ; coins < required && block + ; block = block->pprev) { + + size_t inBlock; + if (block->sparkMintedCoins.count(groupId) + && (inBlock = block->sparkMintedCoins[groupId].size())) { + + coins += inBlock; + first = block; + } + } + } + + return coins; +} + + // CSparkMempoolState bool CSparkMempoolState::HasMint(const spark::Coin& coin) { return mempoolMints.count(coin) > 0; diff --git a/src/spark/state.h b/src/spark/state.h index 30b9bf8767..8c9eb84cf2 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -45,13 +45,25 @@ void ParseSparkMintCoin(const CScript& script, spark::Coin& txCoin); spark::SpendTransaction ParseSparkSpend(const CTransaction &tx); std::vector GetSparkUsedTags(const CTransaction &tx); -std::vector>> GetSparkMintCoins(const CTransaction &tx); +std::vector GetSparkMintCoins(const CTransaction &tx); size_t GetSpendInputs(const CTransaction &tx); CAmount GetSpendTransparentAmount(const CTransaction& tx); bool CheckSparkBlock(CValidationState &state, const CBlock& block); +//void DisconnectTipLelantus(CBlock &block, CBlockIndex *pindexDelete); + +bool ConnectBlockSpark( + CValidationState& state, + const CChainParams& chainparams, + CBlockIndex* pindexNew, + const CBlock *pblock, + bool fJustCheck=false); + +void DisconnectTipSpark(CBlock &block, CBlockIndex *pindexDelete); + + bool CheckSparkTransaction( const CTransaction &tx, CValidationState &state, @@ -66,6 +78,7 @@ bool GetOutPoint(COutPoint& outPoint, const spark::Coin& coin); bool GetOutPoint(COutPoint& outPoint, const uint256& coinHash); bool GetOutPointFromBlock(COutPoint& outPoint, const spark::Coin& coin, const CBlock &block); +bool BuildSparkStateFromIndex(CChain *chain); class CSparkMempoolState { private: @@ -148,15 +161,21 @@ class CSparkState { void AddMint(const spark::Coin& coin, const CMintedCoinInfo& coinInfo); void RemoveMint(const spark::Coin& coin); + // Add mints in block, automatically assigning id to it + void AddMintsToStateAndBlockIndex(CBlockIndex *index, const CBlock* pblock); void AddSpend(const GroupElement& lTag, int coinGroupId); void RemoveSpend(const GroupElement& lTag); + // Add everything from the block to the state + void AddBlock(CBlockIndex *index); + // Disconnect block from the chain rolling back mints and spends + void RemoveBlock(CBlockIndex *index); // Add spend into the mempool. // Check if there is a coin with such serial in either blockchain or mempool bool AddSpendToMempool(const std::vector& lTags, uint256 txHash); - void AddMintsToMempool(const std::vector>>& coins); + void AddMintsToMempool(const std::vector& coins); void RemoveMintFromMempool(const spark::Coin& coin); // Get conflicting tx hash by coin linking tag @@ -168,6 +187,10 @@ class CSparkState { // Given id returns the latest anonymity set and corresponding block hash // Do not take into account coins with height more than maxHeight // Returns number of coins satisfying conditions + void GetCoinSet( + int coinGroupID, + std::vector& coins_out); + int GetCoinSetForSpend( CChain *chain, int maxHeight, @@ -185,6 +208,9 @@ class CSparkState { std::size_t GetTotalCoins() const { return mintedCoins.size(); } +private: + size_t CountLastNCoins(int groupId, size_t required, CBlockIndex* &first); + private: // Group Limit size_t maxCoinInGroup; diff --git a/src/spark/wallet.cpp b/src/spark/wallet.cpp index 8f287809e4..00e8d14e9f 100644 --- a/src/spark/wallet.cpp +++ b/src/spark/wallet.cpp @@ -219,37 +219,69 @@ CSparkMintMeta CSparkWallet::getMintMeta(const uint256& hash) { return CSparkMintMeta(); } +void CSparkWallet::UpdateSpendState(const GroupElement& lTag, const uint256& lTagHash, const uint256& txHash, bool fUpdateMint) { + if (coinMeta.count(lTagHash)) { + auto mintMeta = coinMeta[lTagHash]; + + CSparkSpendEntry spendEntry; + spendEntry.lTag = lTag; + spendEntry.lTagHash = lTagHash; + spendEntry.hashTx = txHash; + spendEntry.amount = mintMeta.v; + + CWalletDB walletdb(strWalletFile); + walletdb.WriteSparkSpendEntry(spendEntry); + + if (fUpdateMint) { + mintMeta.isUsed = true; + addOrUpdateMint(mintMeta, lTagHash, walletdb); + } + + pwalletMain->NotifyZerocoinChanged( + pwalletMain, + lTagHash.GetHex(), + std::string("used (") + std::to_string((double)mintMeta.v / COIN) + "mint)", + CT_UPDATED); + } +} + void CSparkWallet::UpdateSpendStateFromMempool(const std::vector& lTags, const uint256& txHash, bool fUpdateMint) { for (const auto& lTag : lTags) { uint256 lTagHash = primitives::GetLTagHash(lTag); if (coinMeta.count(lTagHash)) { - auto mintMeta = coinMeta[lTagHash]; - - CSparkSpendEntry spendEntry; - spendEntry.lTag = lTag; - spendEntry.lTagHash = lTagHash; - spendEntry.hashTx = txHash; - spendEntry.amount = mintMeta.v; - - CWalletDB walletdb(strWalletFile); - walletdb.WriteSparkSpendEntry(spendEntry); + UpdateSpendState(lTag, lTagHash, txHash, fUpdateMint); + } + } +} - if (fUpdateMint) { - mintMeta.isUsed = true; - addOrUpdateMint(mintMeta, lTagHash, walletdb); +void CSparkWallet::UpdateSpendStateFromBlock(const CBlock& block) { + const auto& transactions = block.vtx; + for (const auto& tx : transactions) { + if (tx->IsSparkSpend()) { + try { + const auto& txLTags = spark::ParseSparkSpend(*tx).getUsedLTags(); + for (const auto& txLTag : txLTags) { + uint256 txHash = tx->GetHash(); + uint256 lTagHash = primitives::GetLTagHash(txLTag); + UpdateSpendState(txLTag, lTagHash, txHash); + } + } catch (...) { } } } } -void CSparkWallet::UpdateMintStateFromMempool(const std::vector>>& coins, const uint256& txHash) { +void CSparkWallet::UpdateMintState(const std::vector& coins, const uint256& txHash) { + spark::CSparkState *sparkState = spark::CSparkState::GetState(); + for (auto coin : coins) { try { - spark::IdentifiedCoinData identifiedCoinData = coin.first.identify(this->viewKey); - spark::RecoveredCoinData recoveredCoinData = coin.first.recover(this->fullViewKey, identifiedCoinData); + spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); + spark::RecoveredCoinData recoveredCoinData = coin.recover(this->fullViewKey, identifiedCoinData); CSparkMintMeta mintMeta; - mintMeta.nHeight = -1; - mintMeta.nId = -1; + auto mintedCoinHeightAndId = sparkState->GetMintedCoinHeightAndId(coin); + mintMeta.nHeight = mintedCoinHeightAndId.first; + mintMeta.nId = mintedCoinHeightAndId.second; mintMeta.isUsed = false; mintMeta.txid = txHash; mintMeta.i = identifiedCoinData.i; @@ -257,7 +289,7 @@ void CSparkWallet::UpdateMintStateFromMempool(const std::vector lTags; - lTags.push_back(recoveredCoinData.T); - UpdateSpendStateFromMempool(lTags, spendTxHash, false); + UpdateSpendState(recoveredCoinData.T, lTagHash, spendTxHash, false); } + + pwalletMain->NotifyZerocoinChanged( + pwalletMain, + lTagHash.GetHex(), + std::string("Update (") + std::to_string((double)mintMeta.v / COIN) + "mint)", + CT_UPDATED); } catch (const std::runtime_error& e) { continue; } } } +void CSparkWallet::UpdateMintStateFromMempool(const std::vector& coins, const uint256& txHash) { + UpdateMintState(coins, txHash); +} + +void CSparkWallet::UpdateMintStateFromBlock(const CBlock& block) { + const auto& transactions = block.vtx; + for (const auto& tx : transactions) { + if (tx->IsSparkTransaction()) { + auto coins = spark::GetSparkMintCoins(*tx); + for (auto& coin : coins) { + uint256 txHash = tx->GetHash(); + UpdateMintState(coins, txHash); + } + } + } +} + + std::vector CSparkWallet::listAddressCoins(const int32_t& i, bool fUnusedOnly) { std::vector listMints; @@ -334,7 +388,7 @@ std::vector CSparkWallet::CreateSparkMintRecipients( } bool CSparkWallet::CreateSparkMintTransactions( - const std::vector& outputs, + const std::vector& outputs, std::vector>& wtxAndFee, CAmount& nAllFeeRet, std::list& reservekeys, @@ -438,7 +492,7 @@ bool CSparkWallet::CreateSparkMintTransactions( while (remainingMintValue > 0){ // Create the mint data and push into vector uint64_t singleMintValue = std::min(remainingMintValue, remainingOutputs.begin()->v); - spark::MintedCoinData mintedCoinData; + spark::MintedCoinData mintedCoinData; mintedCoinData.v = singleMintValue; mintedCoinData.address = remainingOutputs.begin()->address; mintedCoinData.memo = remainingOutputs.begin()->memo; @@ -649,7 +703,7 @@ bool CSparkWallet::CreateSparkMintTransactions( for (auto& recipient : recipients) { CTxOut txout(recipient.nAmount, recipient.scriptPubKey); LogPrintf("txout: %s\n", txout.ToString()); - while (i < tx.vout.size() - 1) { + while (i < tx.vout.size()) { if (tx.vout[i].scriptPubKey.IsSparkMint()) { tx.vout[i] = txout; break; diff --git a/src/spark/wallet.h b/src/spark/wallet.h index 8e2b3732f9..cda1a989d2 100644 --- a/src/spark/wallet.h +++ b/src/spark/wallet.h @@ -58,9 +58,12 @@ class CSparkWallet { void addOrUpdateMint(const CSparkMintMeta& mint, const uint256& lTagHash, CWalletDB& walletdb); CSparkMintMeta getMintMeta(const uint256& hash); + void UpdateSpendState(const GroupElement& lTag, const uint256& lTagHash, const uint256& txHash, bool fUpdateMint = true); void UpdateSpendStateFromMempool(const std::vector& lTags, const uint256& txHash, bool fUpdateMint = true); - void UpdateMintStateFromMempool(const std::vector>>& coins, const uint256& txHash); - + void UpdateSpendStateFromBlock(const CBlock& block); + void UpdateMintState(const std::vector& coins, const uint256& txHash); + void UpdateMintStateFromMempool(const std::vector& coins, const uint256& txHash); + void UpdateMintStateFromBlock(const CBlock& block); // get the vector of mint metadata for a single address std::vector listAddressCoins(const int32_t& i, bool fUnusedOnly = false); @@ -72,7 +75,7 @@ class CSparkWallet { bool generate); bool CreateSparkMintTransactions( - const std::vector& outputs, + const std::vector& outputs, std::vector>& wtxAndFee, CAmount& nAllFeeRet, diff --git a/src/test/fixtures.cpp b/src/test/fixtures.cpp index a405f5a82c..eec38958a4 100644 --- a/src/test/fixtures.cpp +++ b/src/test/fixtures.cpp @@ -39,6 +39,7 @@ #include "sigma.h" #include "lelantus.h" +#include "../libspark/coin.h" ZerocoinTestingSetupBase::ZerocoinTestingSetupBase(): @@ -309,3 +310,88 @@ CPubKey LelantusTestingSetup::GenerateAddress() { LelantusTestingSetup::~LelantusTestingSetup() { lelantus::CLelantusState::GetState()->Reset(); } + +// SparkTestingSetup +SparkTestingSetup::SparkTestingSetup() : params(spark::Params::get_default()) { + CPubKey key; + { + LOCK(pwalletMain->cs_wallet); + key = pwalletMain->GenerateNewKey(); + } + script = GetScriptForDestination(key.GetID()); +} + +CBlockIndex* SparkTestingSetup::GenerateBlock(std::vector const &txns, CScript *script) { + auto last = chainActive.Tip(); + CreateAndProcessBlock(txns, script ? *script : this->script); + auto block = chainActive.Tip(); + if (block != last) { + pwalletMain->ScanForWalletTransactions(block, true); + } + + return block != last ? block : nullptr; +} + +void SparkTestingSetup::GenerateBlocks(size_t blocks, CScript *script) { + while (blocks--) { + GenerateBlock({}, script); + } +} + +CPubKey SparkTestingSetup::GenerateAddress() { + LOCK(pwalletMain->cs_wallet); + return pwalletMain->GenerateNewKey(); +} + +std::vector SparkTestingSetup::GenerateMints( + std::vector const &amounts, + std::vector &txs) { + + CWalletDB walletdb(pwalletMain->strWalletFile); + std::vector mints; + // Parameters + const spark::Params* params; + params = spark::Params::get_default(); + + // Generate address + spark::Address address = pwalletMain->sparkWallet->getDefaultAddress(); + + std::vector> wtxAndFeeAll; + + for (auto& a : amounts) { + std::vector outputs; + std::vector> wtxAndFee; + spark::MintedCoinData data; + data.v = a; + data.memo = "memo"; + data.address = address; + outputs.push_back(data); + + auto result = pwalletMain->MintAndStoreSpark(outputs, wtxAndFee); + + if (result != "") { + throw std::runtime_error(_("Fail to generate mints, ") + result); + } + + for (auto itr: wtxAndFee) { + wtxAndFeeAll.push_back(itr); + txs.emplace_back(itr.first); + } + + } + std::vector walletMints = pwalletMain->sparkWallet->ListSparkMints(); + + for (int i = 0; i < walletMints.size(); ++i) { + for (int j = 0; j < wtxAndFeeAll.size(); ++j) { + if (walletMints[i].txid == wtxAndFeeAll[j].first.GetHash()) { + mints.push_back(walletMints[i]); + } + } + } + + return mints; +} + +SparkTestingSetup::~SparkTestingSetup() +{ +} \ No newline at end of file diff --git a/src/test/fixtures.h b/src/test/fixtures.h index da5f9b13f9..0d47013319 100644 --- a/src/test/fixtures.h +++ b/src/test/fixtures.h @@ -99,6 +99,28 @@ struct LelantusTestingSetup : public TestChain100Setup { CScript script; }; +struct SparkTestingSetup : public TestChain100Setup +{ +public: + SparkTestingSetup(); + +public: + CBlockIndex* GenerateBlock(std::vector const &txns = {}, CScript *script = nullptr); + void GenerateBlocks(size_t blocks, CScript *script = nullptr); + CPubKey GenerateAddress(); + + std::vector GenerateMints( + std::vector const &amounts, + std::vector &txs); + + ~SparkTestingSetup(); + +public: + spark::Params const *params; + CScript script; + +}; + // for the duration of the test set network type to testnet class FakeTestnet { Consensus::Params ¶ms; diff --git a/src/txmempool.cpp b/src/txmempool.cpp index f265cefb19..b52f859a0d 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -612,6 +612,34 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) } } + else if (it->GetTx().IsSparkTransaction()) { + // Remove mints and spends from spark mempool state + const CTransaction &tx = it->GetTx(); + if (tx.IsSparkSpend()) { + std::vector lTags; + try { + lTags = spark::GetSparkUsedTags(tx); + for (const auto& lTag : lTags) + sparkState.RemoveSpendFromMempool(lTag); + } + catch (CBadTxIn&) { + } + } + + BOOST_FOREACH(const CTxOut &txout, tx.vout) + { + if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) { + try { + spark::Coin txCoin; + spark::ParseSparkMintCoin(txout.scriptPubKey, txCoin); + sparkState.RemoveMintFromMempool(txCoin); + } + catch (std::invalid_argument&) { + } + } + } + } + totalTxSize -= it->GetTxSize(); cachedInnerUsage -= it->DynamicMemoryUsage(); cachedInnerUsage -= memusage::DynamicUsage(mapLinks[it].parents) + memusage::DynamicUsage(mapLinks[it].children); diff --git a/src/validation.cpp b/src/validation.cpp index d288aeefc2..30eefc2b61 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -578,7 +578,7 @@ int GetUTXOConfirmations(const COutPoint& outpoint) return (nPrevoutHeight > -1 && chainActive.Tip()) ? chainActive.Height() - nPrevoutHeight + 1 : -1; } -bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fCheckDuplicateInputs, uint256 hashTx, bool isVerifyDB, int nHeight, bool isCheckWallet, bool fStatefulZerocoinCheck, sigma::CSigmaTxInfo *sigmaTxInfo, lelantus::CLelantusTxInfo* lelantusTxInfo) +bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fCheckDuplicateInputs, uint256 hashTx, bool isVerifyDB, int nHeight, bool isCheckWallet, bool fStatefulZerocoinCheck, sigma::CSigmaTxInfo *sigmaTxInfo, lelantus::CLelantusTxInfo* lelantusTxInfo, spark::CSparkTxInfo* sparkTxInfo) { LogPrintf("CheckTransaction nHeight=%s, isVerifyDB=%s, isCheckWallet=%s, txHash=%s\n", nHeight, isVerifyDB, isCheckWallet, tx.GetHash().ToString()); @@ -661,6 +661,11 @@ bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fChe return false; } + if (tx.IsSparkTransaction()) { + if (!CheckSparkTransaction(tx, state, hashTx, isVerifyDB, nHeight, isCheckWallet, fStatefulZerocoinCheck, sparkTxInfo)) + return false; + } + const auto ¶ms = ::Params().GetConsensus(); if (tx.IsZerocoinSpend() || tx.IsZerocoinMint()) { if (!isVerifyDB && nHeight >= params.nDisableZerocoinStartBlock) @@ -822,7 +827,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C // Spark spark::CSparkState *sparkState = spark::CSparkState::GetState(); - std::vector>> sparkMintCoins; + std::vector sparkMintCoins; std::vector sparkUsedLTags; { @@ -941,7 +946,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C } } - if (tx.IsSparkSpend() || tx.IsSparkMint()) { + if (tx.IsSparkTransaction()) { try { sparkMintCoins = spark::GetSparkMintCoins(tx); } @@ -950,8 +955,8 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C } for (const auto& coin : sparkMintCoins) { - if (sparkState->HasCoin(coin.first) || pool.sparkState.HasMint(coin.first)) { - LogPrintf("AcceptToMemoryPool(): Spark mint with the same value %s is already in the mempool\n", coin.first.getHash().GetHex()); + if (sparkState->HasCoin(coin) || pool.sparkState.HasMint(coin)) { + LogPrintf("AcceptToMemoryPool(): Spark mint with the same value %s is already in the mempool\n", coin.getHash().GetHex()); return state.Invalid(false, REJECT_CONFLICT, "txn-mempool-conflict"); } } @@ -1540,7 +1545,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C pwalletMain->zwallet->GetTracker().UpdateMintStateFromMempool(zcMintPubcoinsV3); } - if(tx.IsSparkMint() && !GetBoolArg("-disablewallet", false) && pwalletMain->sparkWallet) { + if(tx.IsSparkTransaction() && !GetBoolArg("-disablewallet", false) && pwalletMain->sparkWallet) { LogPrintf("Adding Spark mints to Mempool..\n"); pwalletMain->sparkWallet->UpdateMintStateFromMempool(sparkMintCoins, hash); } @@ -2848,7 +2853,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin block.sigmaTxInfo = std::make_shared(); block.lelantusTxInfo = std::make_shared(); - + block.sparkTxInfo = std::make_shared(); for (unsigned int i = 0; i < block.vtx.size(); i++) { const CTransaction &tx = *(block.vtx[i]); @@ -2862,12 +2867,12 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin nInputs += tx.vin.size(); - if(tx.IsLelantusJoinSplit() && tx.vin.size() > 1) + if((tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) && tx.vin.size() > 1) return state.DoS(100, error("ConnectBlock(): invalid joinsplit tx"), REJECT_INVALID, "bad-txns-input-invalid"); - if (!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit()) + if (!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { if (!view.HaveInputs(tx)) return state.DoS(100, error("ConnectBlock(): inputs missing/spent"), @@ -2893,7 +2898,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin || tx.IsSigmaMint() || tx.IsZerocoinRemint() || tx.IsLelantusMint() - || tx.IsLelantusJoinSplit()) { + || tx.IsLelantusJoinSplit() + || tx.IsSparkTransaction()) { if( tx.IsSigmaSpend()) nFees += sigma::GetSigmaSpendInput(tx) - tx.GetValueOut(); @@ -2909,8 +2915,20 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin } } + if(tx.IsSparkSpend()) { + try { + nFees += spark::ParseSparkSpend(tx).getFee(); + } + catch (CBadTxIn&) { + return state.DoS(0, false, REJECT_INVALID, "unable to parse spark spend"); + } + catch (...) { + return state.DoS(0, false, REJECT_INVALID, "failed to deserialize spark spend"); + } + } + // Check transaction against signa/lelantus state - if (!CheckTransaction(tx, state, false, txHash, false, pindex->nHeight, false, true, block.sigmaTxInfo.get(), block.lelantusTxInfo.get())) + if (!CheckTransaction(tx, state, false, txHash, false, pindex->nHeight, false, true, block.sigmaTxInfo.get(), block.lelantusTxInfo.get(), block.sparkTxInfo.get())) return state.DoS(100, error("stateful zerocoin check failed"), REJECT_INVALID, "bad-txns-zerocoin"); } @@ -2928,7 +2946,12 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin REJECT_INVALID, "bad-blk-sigops"); txdata.emplace_back(tx); - if (!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit()) + if (!tx.IsCoinBase() + && !tx.IsZerocoinSpend() + && !tx.IsSigmaSpend() + && !tx.IsZerocoinRemint() + && !tx.IsLelantusJoinSplit() + && !tx.IsSparkSpend()) { nFees += view.GetValueIn(tx)-tx.GetValueOut(); @@ -2957,6 +2980,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin block.sigmaTxInfo->Complete(); block.lelantusTxInfo->Complete(); + block.sparkTxInfo->Complete(); + int64_t nTime3 = GetTimeMicros(); nTimeConnect += nTime3 - nTime2; LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime3 - nTime2), 0.001 * (nTime3 - nTime2) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime3 - nTime2) / (nInputs-1), nTimeConnect * 0.000001); @@ -3042,7 +3067,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin } if (!sigma::ConnectBlockSigma(state, chainparams, pindex, &block, fJustCheck) || - !lelantus::ConnectBlockLelantus(state, chainparams, pindex, &block, fJustCheck)) + !lelantus::ConnectBlockLelantus(state, chainparams, pindex, &block, fJustCheck) || + !spark::ConnectBlockSpark(state, chainparams, pindex, &block, fJustCheck)) return false; if (!sporkManager->IsBlockAllowed(block, pindex, state)) @@ -3127,6 +3153,7 @@ void static RemoveConflictingPrivacyTransactionsFromMempool(const CBlock &block) // Erase conflicting sigma/lelantus txs from the mempool sigma::CSigmaState *sigmaState = sigma::CSigmaState::GetState(); lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); + spark::CSparkState *sparkState = spark::CSparkState::GetState(); BOOST_FOREACH(CTransactionRef tx, block.vtx) { if (tx->IsSigmaSpend()) { BOOST_FOREACH(const CTxIn &txin, tx->vin) @@ -3174,6 +3201,33 @@ void static RemoveConflictingPrivacyTransactionsFromMempool(const CBlock &block) // In any case we need to remove serial from mempool set lelantusState->RemoveSpendFromMempool(serials); } + else if (tx->IsSparkSpend()) { + std::vector lTags; + try { + lTags = spark::GetSparkUsedTags(*tx); + } catch (CBadTxIn&) { + // nothing + } + + uint256 thisTxHash = tx->GetHash(); + uint256 conflictingTxHash; + for(const auto& lTag : lTags) { + conflictingTxHash = sparkState->GetMempoolConflictingTxHash(lTag); + if(!conflictingTxHash.IsNull()) + break; + } + if (!conflictingTxHash.IsNull() && conflictingTxHash != thisTxHash) { + std::list removed; + auto pTx = mempool.get(conflictingTxHash); + if (pTx) + mempool.removeRecursive(*pTx); + LogPrintf("ConnectBlock: removed conflicting spark spend tx %s from the mempool\n", + conflictingTxHash.ToString()); + } + + // In any case we need to remove lTags from mempool set + sparkState->RemoveSpendFromMempool(lTags); + } BOOST_FOREACH(const CTxOut &txout, tx->vout) { if (txout.scriptPubKey.IsSigmaMint()) { @@ -3194,6 +3248,16 @@ void static RemoveConflictingPrivacyTransactionsFromMempool(const CBlock &block) } lelantusState->RemoveMintFromMempool(pubCoinValue); } + + if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) { + try { + spark::Coin txCoin; + spark::ParseSparkMintCoin(txout.scriptPubKey, txCoin); + sparkState->RemoveMintFromMempool(txCoin); + } catch (std::invalid_argument&) { + // nothing + } + } } } } @@ -3405,14 +3469,16 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara // retrieve all mints block.sigmaTxInfo = std::make_shared(); block.lelantusTxInfo = std::make_shared(); + block.sparkTxInfo = std::make_shared(); + std::unordered_map lelantusSerialsToRemove; std::vector rangeProofsToRemove; sigma::spend_info_container sigmaSerialsToRemove; - + std::vector sparkTransactionsToRemove; for (CTransactionRef tx : block.vtx) { CheckTransaction(*tx, state, false, tx->GetHash(), false, pindexDelete->pprev->nHeight, - false, false, block.sigmaTxInfo.get(), block.lelantusTxInfo.get()); + false, false, block.sigmaTxInfo.get(), block.lelantusTxInfo.get(), block.sparkTxInfo.get()); if(GetBoolArg("-batching", true)) { if (tx->IsLelantusJoinSplit()) { std::unique_ptr joinsplit; @@ -3452,6 +3518,14 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara sigmaSerialsToRemove.insert(std::make_pair( serial, sigma::CSpendCoinInfo::make(spend->getDenomination(), coinGroupId))); } + } else if (tx->IsSparkSpend()) { + try { + spark::SpendTransaction spendTransaction = spark::ParseSparkSpend(*tx); + sparkTransactionsToRemove.push_back(spendTransaction); + } + catch (CBadTxIn &) { + continue; + } } } } @@ -3472,6 +3546,7 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara sigma::DisconnectTipSigma(block, pindexDelete); lelantus::DisconnectTipLelantus(block, pindexDelete); + spark::DisconnectTipSpark(block, pindexDelete); BatchProofContainer* batchProofContainer = BatchProofContainer::get_instance(); if (sigmaSerialsToRemove.size() > 0) { @@ -3486,6 +3561,9 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara batchProofContainer->remove(rangeProofsToRemove); } + for (auto& sparkTransaction : sparkTransactionsToRemove) { + batchProofContainer->remove(sparkTransaction); + } // Roll back MTP state MTPState::GetMTPState()->SetLastBlock(pindexDelete->pprev, chainparams.GetConsensus()); @@ -3704,6 +3782,16 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, LogPrintf("HDmint: UpdateSpendStateFromBlock. [height: %d]\n", GetHeight()); pwalletMain->zwallet->GetTracker().UpdateMintStateFromBlock(blockConnecting.lelantusTxInfo->mints); } + + if (blockConnecting.sparkTxInfo->spentLTags.size() > 0) { + LogPrintf("HDmint: UpdateSpendStateFromBlock. [height: %d]\n", GetHeight()); + pwalletMain->sparkWallet->UpdateSpendStateFromBlock(blockConnecting); + } + + if (blockConnecting.sparkTxInfo->mints.size() > 0) { + LogPrintf("SparkWallet: UpdateSpendStateFromBlock. [height: %d]\n", GetHeight()); + pwalletMain->sparkWallet->UpdateMintStateFromBlock(blockConnecting); + } } #endif @@ -4414,11 +4502,13 @@ bool CheckBlockHeader(const CBlockHeader& block, CValidationState& state, const } bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot, int nHeight, bool isVerifyDB) { - // CheckBlock not only checks the block, but also fills up lelantusTxInfo and sigmaTxInfo. + // CheckBlock not only checks the block, but also fills up sparkTxInfo, lelantusTxInfo and sigmaTxInfo. if (!block.sigmaTxInfo) block.sigmaTxInfo = std::make_shared(); if (!block.lelantusTxInfo) block.lelantusTxInfo = std::make_shared(); + if (!block.sparkTxInfo) + block.sparkTxInfo = std::make_shared(); LogPrintf("CheckBlock() nHeight=%s, blockHash= %s, isVerifyDB = %s\n", nHeight, block.GetHash().ToString(), isVerifyDB); @@ -4501,6 +4591,9 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P if (!lelantus::CheckLelantusBlock(state, block)) return false; + if (!spark::CheckSparkBlock(state, block)) + return false; + return true; } @@ -5324,6 +5417,7 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams) sigma::BuildSigmaStateFromIndex(&chainActive); lelantus::BuildLelantusStateFromIndex(&chainActive); + spark::BuildSparkStateFromIndex(&chainActive); // Initialize MTP state MTPState::GetMTPState()->InitializeFromChain(&chainActive, chainparams.GetConsensus()); diff --git a/src/validation.h b/src/validation.h index e8e8be17fa..33f0eefb95 100644 --- a/src/validation.h +++ b/src/validation.h @@ -426,7 +426,7 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight); /** Transaction validation functions */ /** Context-independent validity checks */ -bool CheckTransaction(const CTransaction& tx, CValidationState& state, bool fCheckDuplicateInputs, uint256 hashTx, bool isVerifyDB, int nHeight = INT_MAX, bool isCheckWallet = false, bool fStatefulZerocoinCheck = true, sigma::CSigmaTxInfo *sigmaTxInfo = NULL, lelantus::CLelantusTxInfo* lelantusTxInfo = NULL); +bool CheckTransaction(const CTransaction& tx, CValidationState& state, bool fCheckDuplicateInputs, uint256 hashTx, bool isVerifyDB, int nHeight = INT_MAX, bool isCheckWallet = false, bool fStatefulZerocoinCheck = true, sigma::CSigmaTxInfo *sigmaTxInfo = NULL, lelantus::CLelantusTxInfo* lelantusTxInfo = NULL, spark::CSparkTxInfo* sparkTxInfo = NULL); namespace Consensus { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index cd2d181c72..685b7112ae 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5320,7 +5320,7 @@ std::string CWallet::MintAndStoreLelantus(const CAmount& value, } std::string CWallet::MintAndStoreSpark( - const std::vector& outputs, + const std::vector& outputs, std::vector>& wtxAndFee, bool autoMintAll, bool fAskFee, @@ -5331,7 +5331,7 @@ std::string CWallet::MintAndStoreSpark( if (IsLocked()) { strError = _("Error: Wallet locked, unable to create transaction!"); - LogPrintf("MintLelantus() : %s", strError); + LogPrintf("MintSpark() : %s", strError); return strError; } @@ -5353,7 +5353,7 @@ std::string CWallet::MintAndStoreSpark( } if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired)){ - LogPrintf("MintLelantus: returning aborted..\n"); + LogPrintf("MintSpark: returning aborted..\n"); return "ABORTED"; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 979690201d..6d2e946bc8 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1059,7 +1059,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface const CCoinControl *coinControl = NULL); std::string MintAndStoreSpark( - const std::vector& outputs, + const std::vector& outputs, std::vector>& wtxAndFee, bool autoMintAll = false, bool fAskFee = false, From af959f11cc138d6ab24926990951060251c55d80 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 23 Oct 2022 15:29:32 +0400 Subject: [PATCH 053/197] Some bug fixes and cleanup --- src/libspark/spend_transaction.cpp | 12 +++-- src/libspark/spend_transaction.h | 6 +++ src/libspark/test/spend_transaction_test.cpp | 1 + src/spark/primitives.h | 2 + src/spark/state.cpp | 50 ++++++++++++-------- src/spark/wallet.cpp | 39 ++++++++------- src/test/fixtures.cpp | 35 +++++++++++++- src/test/fixtures.h | 7 +++ src/txdb.cpp | 4 ++ src/wallet/wallet.h | 6 +++ 10 files changed, 120 insertions(+), 42 deletions(-) diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index 9295b288f8..ed972c66ae 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -15,6 +15,7 @@ SpendTransaction::SpendTransaction( const std::vector& inputs, const std::unordered_map& cover_set_data, const uint64_t f, + const uint64_t vout, const std::vector& outputs ) { this->params = params; @@ -33,6 +34,7 @@ SpendTransaction::SpendTransaction( this->T.reserve(w); // linking tags this->f = f; // fee + this->vout = vout; // transparent output value // Prepare Chaum vectors std::vector chaum_x, chaum_y, chaum_z; @@ -165,7 +167,7 @@ SpendTransaction::SpendTransaction( balance_statement += this->out_coins[j].C.inverse(); balance_witness -= SparkUtils::hash_val(k[j]); } - balance_statement += (this->params->get_G()*Scalar(f)).inverse(); + balance_statement += (this->params->get_G()*Scalar(f + vout)).inverse(); schnorr.prove( balance_witness, balance_statement, @@ -175,7 +177,7 @@ SpendTransaction::SpendTransaction( // Compute the binding hash Scalar mu = hash_bind( this->out_coins, - this->f, + this->f + vout, this->cover_set_representations, this->S1, this->C1, @@ -292,7 +294,7 @@ bool SpendTransaction::verify( // Compute the binding hash Scalar mu = hash_bind( tx.out_coins, - tx.f, + tx.f + tx.vout, tx.cover_set_representations, tx.S1, tx.C1, @@ -322,7 +324,9 @@ bool SpendTransaction::verify( for (std::size_t j = 0; j < t; j++) { balance_statement += tx.out_coins[j].C.inverse(); } - balance_statement += (tx.params->get_G()*Scalar(tx.f)).inverse(); + + balance_statement += (tx.params->get_G()*Scalar(tx.f + tx.vout)).inverse(); + if(!schnorr.verify( balance_statement, tx.balance_proof diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h index cb5c2893b5..7a877c8a08 100644 --- a/src/libspark/spend_transaction.h +++ b/src/libspark/spend_transaction.h @@ -48,6 +48,7 @@ class SpendTransaction { const std::vector& inputs, const std::unordered_map& cover_set_data, const uint64_t f, + const uint64_t vout, const std::vector& outputs ); @@ -98,6 +99,10 @@ class SpendTransaction { } } + void setVout(const uint64_t& vout_) { + this->vout = vout_; + } + void setBlockHashes(const std::map& idAndHashes); const std::map& getBlockHashes(); @@ -112,6 +117,7 @@ class SpendTransaction { std::map set_id_blockHash; std::vector cover_set_ids; uint64_t f; + uint64_t vout; std::vector S1, C1, T; std::vector grootle_proofs; ChaumProof chaum_proof; diff --git a/src/libspark/test/spend_transaction_test.cpp b/src/libspark/test/spend_transaction_test.cpp index ce973787d0..4527b26893 100644 --- a/src/libspark/test/spend_transaction_test.cpp +++ b/src/libspark/test/spend_transaction_test.cpp @@ -119,6 +119,7 @@ BOOST_AUTO_TEST_CASE(generate_verify) spend_coin_data, cover_set_data, f, + 0, out_coin_data ); diff --git a/src/spark/primitives.h b/src/spark/primitives.h index 27b739c0eb..45468066e0 100644 --- a/src/spark/primitives.h +++ b/src/spark/primitives.h @@ -17,6 +17,7 @@ struct CSparkMintMeta Scalar k; // nonce std::string memo; // memo std::vector serial_context; + char type; mutable boost::optional nonceHash; uint256 GetNonceHash() const; @@ -36,6 +37,7 @@ struct CSparkMintMeta READWRITE(k); READWRITE(memo); READWRITE(serial_context); + READWRITE(type); }; }; diff --git a/src/spark/state.cpp b/src/spark/state.cpp index f45d31459b..a4c951ef4b 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -16,7 +16,7 @@ static bool CheckLTag( if (sparkTxInfo && !sparkTxInfo->fInfoIsComplete && sparkTxInfo->spentLTags.find(lTag) != sparkTxInfo->spentLTags.end()) - return state.DoS(0, error("CTransaction::CheckTransaction() : two or more spands with same linking tag in the same block")); + return state.DoS(0, error("CTransaction::CheckTransaction() : two or more spends with same linking tag in the same block")); // check for used linking tags in state if (sparkState.IsUsedLTag(lTag)) { @@ -117,8 +117,11 @@ void ParseSparkMintTransaction(const std::vector& scripts, MintTransact serializedCoins.push_back(stream); } - - mintTransaction.setMintTransaction(serializedCoins); + try { + mintTransaction.setMintTransaction(serializedCoins); + } catch (...) { + throw std::invalid_argument("Unable to deserialize Spark mint transaction"); + } } void ParseSparkMintCoin(const CScript& script, spark::Coin& txCoin) @@ -126,6 +129,10 @@ void ParseSparkMintCoin(const CScript& script, spark::Coin& txCoin) if (!script.IsSparkMint() && !script.IsSparkSMint()) throw std::invalid_argument("Script is not a Spark mint"); + if (script.size() < 213) { + throw std::invalid_argument("Script is not a valid Spark Mint"); + } + std::vector serialized(script.begin() + 1, script.end()); CDataStream stream( std::vector(serialized.begin(), serialized.end()), @@ -133,7 +140,11 @@ void ParseSparkMintCoin(const CScript& script, spark::Coin& txCoin) PROTOCOL_VERSION ); - stream >> txCoin; + try { + stream >> txCoin; + } catch (...) { + throw std::invalid_argument("Unable to deserialize Spark mint"); + } } spark::SpendTransaction ParseSparkSpend(const CTransaction &tx) @@ -215,7 +226,7 @@ size_t GetSpendInputs(const CTransaction &tx) { CAmount GetSpendTransparentAmount(const CTransaction& tx) { CAmount result = 0; - if(!tx.IsSparkSpend()) + if (!tx.IsSparkSpend()) return 0; for (const CTxOut &txout : tx.vout) @@ -279,7 +290,7 @@ bool ConnectBlockSpark( if (!prev_hash.empty()) hash.Write(prev_hash.data(), 32); else { - if(latestCoinId > 1) { + if (latestCoinId > 1) { prev_hash = GetAnonymitySetHash(pindexNew->pprev, latestCoinId - 1, true); hash.Write(prev_hash.data(), 32); } @@ -418,7 +429,7 @@ bool CheckSparkMintTransaction( } //checking whether MintTransaction is valid - if(!mintTransaction.verify()) { + if (!mintTransaction.verify()) { return state.DoS(100, false, PUBCOIN_NOT_VALIDATE, @@ -551,7 +562,7 @@ bool CheckSparkSpendTransaction( CSparkTxInfo* sparkTxInfo) { std::unordered_set txLTags; - if(tx.vin.size() != 1 || !tx.vin[0].scriptSig.IsSparkSpend()) { + if (tx.vin.size() != 1 || !tx.vin[0].scriptSig.IsSparkSpend()) { // mixing spark spend input with non-spark inputs is prohibited return state.DoS(100, false, REJECT_MALFORMED, @@ -610,7 +621,7 @@ bool CheckSparkSpendTransaction( const auto& script = txout.scriptPubKey; if (!script.empty() && script.IsSparkSMint()) { vout.push_back(txout); - } else if(script.IsSparkMint() || + } else if (script.IsSparkMint() || script.IsLelantusMint() || script.IsLelantusJMint() || script.IsSigmaMint()) { @@ -652,7 +663,7 @@ bool CheckSparkSpendTransaction( id = idAndHash.first - 1; } if (id) { - if(index->sparkMintedCoins.count(idAndHash.first) > 0) { + if (index->sparkMintedCoins.count(idAndHash.first) > 0) { BOOST_FOREACH( const auto& coin, index->sparkMintedCoins[idAndHash.first]) { @@ -684,7 +695,7 @@ bool CheckSparkSpendTransaction( // if we are collecting proofs, skip verification and collect proofs // add proofs into container - if(useBatching) { + if (useBatching) { passVerify = true; batchProofContainer->add(*spend); } else { @@ -732,7 +743,7 @@ bool CheckSparkSpendTransaction( return false; } - if(!isVerifyDB && !isCheckWallet) { + if (!isVerifyDB && !isCheckWallet) { if (sparkTxInfo && !sparkTxInfo->fInfoIsComplete) { sparkTxInfo->spTransactions.insert(hashTx); } @@ -776,7 +787,7 @@ bool CheckSparkTransaction( } // Check Spark Spend - if(tx.IsSparkSpend()) { + if (tx.IsSparkSpend()) { // First check number of inputs does not exceed transaction limit if (GetSpendInputs(tx) > consensus.nMaxLelantusInputPerTransaction) { //TODO levon define spark limits and refactor here return state.DoS(100, false, @@ -809,13 +820,14 @@ bool GetOutPoint(COutPoint& outPoint, const spark::Coin& coin) int mintHeight = mintedCoinHeightAndId.first; int coinId = mintedCoinHeightAndId.second; - if(mintHeight==-1 && coinId==-1) + if (mintHeight==-1 && coinId==-1) return false; // get block containing mint CBlockIndex *mintBlock = chainActive[mintHeight]; CBlock block; - if(!ReadBlockFromDisk(block, mintBlock, ::Params().GetConsensus())) + //TODO levon, try to optimize this + if (!ReadBlockFromDisk(block, mintBlock, ::Params().GetConsensus())) LogPrintf("can't read block from disk.\n"); return GetOutPointFromBlock(outPoint, coin, block); @@ -825,7 +837,7 @@ bool GetOutPoint(COutPoint& outPoint, const uint256& coinHash) { spark::Coin coin(Params::get_default()); spark::CSparkState *sparkState = spark::CSparkState::GetState(); - if(!sparkState->HasCoinHash(coin, coinHash)) { + if (!sparkState->HasCoinHash(coin, coinHash)) { return false; } @@ -845,7 +857,7 @@ bool GetOutPointFromBlock(COutPoint& outPoint, const spark::Coin& coin, const CB catch (...) { continue; } - if(coin == txCoin){ + if (coin == txCoin) { outPoint = COutPoint(tx->GetHash(), nIndex); return true; } @@ -879,7 +891,7 @@ static bool CheckSparkSpendTAg( } /******************************************************************************/ -// CLelantusState +// CSparkState /******************************************************************************/ CSparkState::CSparkState( @@ -918,7 +930,7 @@ bool CSparkState::HasCoin(const spark::Coin& coin) { bool CSparkState::HasCoinHash(spark::Coin& coin, const uint256& coinHash) { for (auto it = mintedCoins.begin(); it != mintedCoins.end(); ++it ){ const spark::Coin& coin_ = (*it).first; - if(primitives::GetSparkCoinHash(coin_) == coinHash){ + if (primitives::GetSparkCoinHash(coin_) == coinHash) { coin = coin_; return true; } diff --git a/src/spark/wallet.cpp b/src/spark/wallet.cpp index 00e8d14e9f..6f9f5d02ee 100644 --- a/src/spark/wallet.cpp +++ b/src/spark/wallet.cpp @@ -184,9 +184,7 @@ std::vector CSparkWallet::ListSparkMints(bool fUnusedOnly, bool spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta) const { const spark::Params* params = spark::Params::get_default(); spark::Address address(viewKey, meta.i); - // type we are passing 0; as we don't care about type now - char type = 0; - return spark::Coin(params, type, meta.k, address, meta.v, meta.memo, meta.serial_context); + return spark::Coin(params, meta.type, meta.k, address, meta.v, meta.memo, meta.serial_context); } void CSparkWallet::clearAllMints(CWalletDB& walletdb) { @@ -290,6 +288,7 @@ void CSparkWallet::UpdateMintState(const std::vector& coins, const mintMeta.k = identifiedCoinData.k; mintMeta.memo = identifiedCoinData.memo; mintMeta.serial_context = coin.serial_context; + mintMeta.type = coin.type; //! Check whether this mint has been spent and is considered 'pending' or 'confirmed' { LOCK(mempool.cs); @@ -724,7 +723,7 @@ bool CSparkWallet::CreateSparkMintTransactions( continue; } - if(skipCoin) + if (skipCoin) continue; if (GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { @@ -794,7 +793,7 @@ bool CSparkWallet::CreateSparkMintTransactions( } nAllFeeRet += nFeeRet; - if(!autoMintAll) { + if (!autoMintAll) { valueToMint -= mintedValue; if (valueToMint == 0) break; @@ -966,6 +965,7 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( CAmount fee = feeAndSpendCoins.first; spendInCurrentTx -= fee; + uint64_t transparentOut = 0; // fill outputs for (size_t i = 0; i < recipients_.size(); i++) { auto& recipient = recipients_[i]; @@ -996,6 +996,7 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( throw std::runtime_error(err); } + transparentOut += vout.nValue; tx.vout.push_back(vout); } @@ -1105,7 +1106,7 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( } - spark::SpendTransaction spendTransaction(params, fullViewKey, spendKey, inputs, cover_set_data, fee, privOutputs); + spark::SpendTransaction spendTransaction(params, fullViewKey, spendKey, inputs, cover_set_data, fee, transparentOut, privOutputs); spendTransaction.setBlockHashes(idAndBlockHashes); const std::vector& outCoins = spendTransaction.getOutCoins(); for (auto& outCoin : outCoins) { @@ -1203,8 +1204,8 @@ bool GetCoinsToSpend( // If coinControl, want to use all inputs bool coinControlUsed = false; - if(coinControl != NULL) { - if(coinControl->HasSelected()) { + if (coinControl != NULL) { + if (coinControl->HasSelected()) { auto coinIt = coins.rbegin(); for (; coinIt != coins.rend(); coinIt++) { spend_val += coinIt->second.v; @@ -1214,16 +1215,16 @@ bool GetCoinsToSpend( } } - if(!coinControlUsed) { + if (!coinControlUsed) { while (spend_val < required) { - if(coins.empty()) + if (coins.empty()) break; CoinData choosen; CAmount need = required - spend_val; auto itr = coins.begin(); - if(need >= itr->second.v) { + if (need >= itr->second.v) { choosen = *itr; coins.erase(itr); } else { @@ -1298,7 +1299,7 @@ std::vector> CSparkWallet::GetAvailableSpar // get all unsued coins from spark wallet std::vector vecMints = this->ListSparkMints(true, true); for (const auto& mint : vecMints) { - if(mint.v == 0) // ignore 0 mints which where created to increase privacy + if (mint.v == 0) // ignore 0 mints which where created to increase privacy continue; spark::Coin coin = this->getCoinFromMeta(mint); @@ -1335,14 +1336,14 @@ std::list> CSparkWallet::GetAvailableSpar } // ignore if coin is locked - if(lockedCoins.count(outPoint) > 0){ + if (lockedCoins.count(outPoint) > 0){ return true; } // if we are using coincontrol, filter out unselected coins - if(coinControl != NULL){ - if(coinControl->HasSelected()){ - if(!coinControl->IsSelected(outPoint)){ + if (coinControl != NULL){ + if (coinControl->HasSelected()){ + if (!coinControl->IsSelected(outPoint)){ return true; } } @@ -1352,4 +1353,6 @@ std::list> CSparkWallet::GetAvailableSpar }); return coins; -} \ No newline at end of file +} + +//TODO levon implement wallet scanning when restoring wallet, or opening wallet file witg synced chain \ No newline at end of file diff --git a/src/test/fixtures.cpp b/src/test/fixtures.cpp index eec38958a4..a196384567 100644 --- a/src/test/fixtures.cpp +++ b/src/test/fixtures.cpp @@ -323,10 +323,12 @@ SparkTestingSetup::SparkTestingSetup() : params(spark::Params::get_default()) { CBlockIndex* SparkTestingSetup::GenerateBlock(std::vector const &txns, CScript *script) { auto last = chainActive.Tip(); + CreateAndProcessBlock(txns, script ? *script : this->script); auto block = chainActive.Tip(); + if (block != last) { - pwalletMain->ScanForWalletTransactions(block, true); + pwalletMain->ScanForWalletTransactions(block, true); } return block != last ? block : nullptr; @@ -392,6 +394,37 @@ std::vector SparkTestingSetup::GenerateMints( return mints; } +std::vector SparkTestingSetup::GenerateSparkSpend( + std::vector const &outs, + std::vector const &mints, + CCoinControl const *coinControl = nullptr) { + + std::vector vecs; + for (auto const &out : outs) { + LOCK(pwalletMain->cs_wallet); + auto pub = pwalletMain->GenerateNewKey(); + + vecs.push_back( + { + GetScriptForDestination(pub.GetID()), + out, + false + }); + } + + CAmount fee; + auto txs = pwalletMain->SpendAndStoreSpark( + vecs, {}, fee, coinControl); + + std::vector result; + for (auto& itr : txs) { + result.push_back(*itr.tx); + } + + return result; +} + + SparkTestingSetup::~SparkTestingSetup() { } \ No newline at end of file diff --git a/src/test/fixtures.h b/src/test/fixtures.h index 0d47013319..57e2696ec1 100644 --- a/src/test/fixtures.h +++ b/src/test/fixtures.h @@ -7,6 +7,8 @@ #include +class CCoinControl; + struct TestDerivation { std::string pub; std::string prv; @@ -113,6 +115,11 @@ struct SparkTestingSetup : public TestChain100Setup std::vector const &amounts, std::vector &txs); + std::vector GenerateSparkSpend( + std::vector const &outs, + std::vector const &mints, + CCoinControl const *coinControl ); + ~SparkTestingSetup(); public: diff --git a/src/txdb.cpp b/src/txdb.cpp index ae51fdbed8..ea61713c24 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -398,6 +398,10 @@ bool CBlockTreeDB::LoadBlockIndexGuts(boost::functionlelantusSpentSerials = diskindex.lelantusSpentSerials; pindexNew->anonymitySetHash = diskindex.anonymitySetHash; + pindexNew->sparkMintedCoins = diskindex.sparkMintedCoins; + pindexNew->sparkSetHash = diskindex.sparkSetHash; + pindexNew->spentLTags = diskindex.spentLTags; + pindexNew->activeDisablingSporks = diskindex.activeDisablingSporks; if (!CheckProofOfWork(pindexNew->GetBlockPoWHash(), pindexNew->nBits, consensusParams)) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 6d2e946bc8..ef871bb75c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1071,6 +1071,12 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount &fee, const CCoinControl *coinControl = NULL); + std::vector SpendAndStoreSpark( + const std::vector& recipients, + const std::vector>& privateRecipients, + CAmount &fee, + const CCoinControl *coinControl = NULL); + std::vector SpendSigma(const std::vector& recipients, CWalletTx& result); std::vector SpendSigma(const std::vector& recipients, CWalletTx& result, CAmount& fee); From 5c8f1cd0cf46cdec3fb736b5d3ac1e0d2e9fc8f5 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 28 Oct 2022 03:41:07 +0400 Subject: [PATCH 054/197] Bug fixes and review comments resolved --- src/coins.cpp | 4 +- src/evo/specialtx.cpp | 4 + src/libspark/spend_transaction.cpp | 8 +- src/libspark/spend_transaction.h | 2 +- src/primitives/transaction.cpp | 2 +- src/primitives/transaction.h | 3 +- src/script/standard.cpp | 4 +- src/spark/primitives.h | 10 + src/spark/state.cpp | 28 +- src/spark/wallet.cpp | 65 ++- src/spark/wallet.h | 6 + src/test/spark_tests.cpp | 685 +++++++++++++++++++++++++++++ src/validation.cpp | 27 +- src/wallet/wallet.cpp | 31 ++ src/wallet/walletdb.cpp | 4 +- src/wallet/walletdb.h | 2 +- 16 files changed, 850 insertions(+), 35 deletions(-) create mode 100644 src/test/spark_tests.cpp diff --git a/src/coins.cpp b/src/coins.cpp index 3e1e2e77a1..130e2bef35 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -216,7 +216,7 @@ unsigned int CCoinsViewCache::GetCacheSize() const { CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const { - if (tx.IsCoinBase() || tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsLelantusJoinSplit()) + if (tx.IsCoinBase() || tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) return 0; CAmount nResult = 0; @@ -228,7 +228,7 @@ CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const { - if (!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit()) { + if (!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { for (unsigned int i = 0; i < tx.vin.size(); i++) { if (!HaveCoin(tx.vin[i].prevout)) { return false; diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp index 456403450d..dc9b261887 100644 --- a/src/evo/specialtx.cpp +++ b/src/evo/specialtx.cpp @@ -73,6 +73,8 @@ bool ProcessSpecialTx(const CTransaction& tx, const CBlockIndex* pindex, CValida return true; case TRANSACTION_LELANTUS: return true; + case TRANSACTION_SPARK: + return true; } return state.DoS(100, false, REJECT_INVALID, "bad-tx-type-proc"); @@ -98,6 +100,8 @@ bool UndoSpecialTx(const CTransaction& tx, const CBlockIndex* pindex) return true; case TRANSACTION_LELANTUS: return true; + case TRANSACTION_SPARK: + return true; } return false; diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index ed972c66ae..063c28bbad 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -394,7 +394,9 @@ bool SpendTransaction::verify( } // Verify the batch - grootle.verify(S, S1, V, V1, cover_set_representations, sizes, proofs); + if (!grootle.verify(S, S1, V, V1, cover_set_representations, sizes, proofs)) { + return false; + } } // Any failures have been identified already, so the batch is valid @@ -404,7 +406,7 @@ bool SpendTransaction::verify( // Hash-to-scalar function H_bind Scalar SpendTransaction::hash_bind( const std::vector& out_coins, - const uint64_t f, + const uint64_t f_, const std::unordered_map>& cover_set_representations, const std::vector& S1, const std::vector& C1, @@ -419,7 +421,7 @@ Scalar SpendTransaction::hash_bind( // Perform the serialization and hashing stream << out_coins; - stream << f; + stream << f_; stream << cover_set_representations; stream << S1; stream << C1; diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h index 7a877c8a08..167ff7843f 100644 --- a/src/libspark/spend_transaction.h +++ b/src/libspark/spend_transaction.h @@ -62,7 +62,7 @@ class SpendTransaction { static Scalar hash_bind( const std::vector& out_coins, - const uint64_t f, + const uint64_t f_, const std::unordered_map>& cover_set_representations, const std::vector& S1, const std::vector& C1, diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 6167aa60cb..efed5881c9 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -256,7 +256,7 @@ bool CTransaction::IsZerocoinRemint() const } bool CTransaction::HasNoRegularInputs() const { - return IsZerocoinSpend() || IsSigmaSpend() || IsZerocoinRemint() || IsLelantusJoinSplit(); + return IsZerocoinSpend() || IsSigmaSpend() || IsZerocoinRemint() || IsLelantusJoinSplit() || IsSparkSpend(); } unsigned int CTransaction::CalculateModifiedSize(unsigned int nTxSize) const diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index da12723003..f653f2dafc 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -473,7 +473,8 @@ class CTransaction || (vin[0].scriptSig[0] != OP_ZEROCOINSPEND && vin[0].scriptSig[0] != OP_ZEROCOINTOSIGMAREMINT && vin[0].scriptSig[0] != OP_LELANTUSJOINSPLIT - && vin[0].scriptSig[0] != OP_LELANTUSJOINSPLITPAYLOAD))); + && vin[0].scriptSig[0] != OP_LELANTUSJOINSPLITPAYLOAD + && vin[0].scriptSig[0] != OP_SPARKSPEND))); } friend bool operator==(const CTransaction& a, const CTransaction& b) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index febb58b80e..78321569de 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -120,7 +120,7 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vectork == other.k; + } + + bool operator!=(const CSparkMintMeta& other) + { + return this->k != other.k; + } + ADD_SERIALIZE_METHODS; template diff --git a/src/spark/state.cpp b/src/spark/state.cpp index a4c951ef4b..6e36fb045a 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -157,8 +157,9 @@ spark::SpendTransaction ParseSparkSpend(const CTransaction &tx) if (tx.vin[0].scriptSig[0] == OP_SPARKSPEND && tx.nVersion >= 3 && tx.nType == TRANSACTION_SPARK) { serialized.write((const char *)tx.vExtraPayload.data(), tx.vExtraPayload.size()); } - else + else { throw CBadTxIn(); + } const spark::Params* params = spark::Params::get_default(); spark::SpendTransaction spendTransaction(params); serialized >> spendTransaction; @@ -361,7 +362,7 @@ void RemoveSpendReferencingBlock(CTxMemPool& pool, CBlockIndex* blockIndex) { void DisconnectTipSpark(CBlock& block, CBlockIndex *pindexDelete) { sparkState.RemoveBlock(pindexDelete); - // Also remove from mempool lelantus joinsplits that reference given block hash. + // Also remove from mempool spends that reference given block hash. RemoveSpendReferencingBlock(mempool, pindexDelete); RemoveSpendReferencingBlock(txpools.getStemTxPool(), pindexDelete); } @@ -574,7 +575,7 @@ bool CheckSparkSpendTransaction( if (!isVerifyDB) { if (height >= params.nSparkStartBlock) { // data should be moved to v3 payload - if (tx.nVersion < 3 || tx.nType != TRANSACTION_LELANTUS) + if (tx.nVersion < 3 || tx.nType != TRANSACTION_SPARK) return state.DoS(100, false, NSEQUENCE_INCORRECT, "CheckSparkSpendTransaction: spark data should reside in transaction payload"); } @@ -599,12 +600,15 @@ bool CheckSparkSpendTransaction( } uint256 txHashForMetadata; - - // Obtain the hash of the transaction sans the zerocoin part + // Obtain the hash of the transaction sans the Spark part CMutableTransaction txTemp = tx; - txTemp.vin[0].scriptSig.clear(); txTemp.vExtraPayload.clear(); - + for (auto itr = txTemp.vout.begin(); itr < txTemp.vout.end(); ++itr) { + if (itr->scriptPubKey.IsSparkSMint()) { + txTemp.vout.erase(itr); + --itr; + } + } txHashForMetadata = txTemp.GetHash(); LogPrintf("CheckSparkSpendTransaction: tx metadata hash=%s\n", txHashForMetadata.ToString()); @@ -636,7 +640,6 @@ bool CheckSparkSpendTransaction( if (!CheckSparkSMintTransaction(vout, state, hashTx, fStatefulSigmaCheck, out_coins, sparkTxInfo)) return false; spend->setOutCoins(out_coins); - std::unordered_map> cover_sets; std::unordered_map cover_set_data; const auto idAndBlockHashes = spend->getBlockHashes(); @@ -678,7 +681,7 @@ bool CheckSparkSpendTransaction( } // take the hash from last block of anonymity set - std::vector set_hash = GetAnonymitySetHash(index, idAndHash.first); + std::vector set_hash = GetAnonymitySetHash(coinGroup.lastBlock, idAndHash.first); CoverSetData setData; setData.cover_set = cover_set; if (!set_hash.empty()) @@ -689,6 +692,7 @@ bool CheckSparkSpendTransaction( cover_set_data [idAndHash.first] = setData; } spend->setCoverSets(cover_set_data); + spend->setVout(Vout); BatchProofContainer* batchProofContainer = BatchProofContainer::get_instance(); bool useBatching = batchProofContainer->fCollectProofs && !isVerifyDB && !isCheckWallet && sparkTxInfo && !sparkTxInfo->fInfoIsComplete; @@ -699,7 +703,11 @@ bool CheckSparkSpendTransaction( passVerify = true; batchProofContainer->add(*spend); } else { - passVerify = spark::SpendTransaction::verify(*spend, cover_sets); + try { + passVerify = spark::SpendTransaction::verify(*spend, cover_sets); + } catch (...) { + passVerify = false; + } } if (passVerify) { diff --git a/src/spark/wallet.cpp b/src/spark/wallet.cpp index 6f9f5d02ee..32514cd207 100644 --- a/src/spark/wallet.cpp +++ b/src/spark/wallet.cpp @@ -75,7 +75,7 @@ CAmount CSparkWallet::getAvailableBalance() { continue; // Not confirmed - if (!mint.nHeight) + if (mint.nHeight < 1) continue; result += mint.v; @@ -92,7 +92,7 @@ CAmount CSparkWallet::getUnconfirmedBalance() { continue; // Continue if confirmed - if (mint.nHeight) + if (mint.nHeight > 1) continue; result += mint.v; @@ -172,7 +172,7 @@ std::vector CSparkWallet::ListSparkMints(bool fUnusedOnly, bool continue; // Not confirmed - if (fMatureOnly && !mint.nHeight) + if (fMatureOnly && mint.nHeight < 1) continue; setMints.push_back(mint); @@ -202,6 +202,7 @@ void CSparkWallet::eraseMint(const uint256& hash, CWalletDB& walletdb) { walletdb.EraseSparkMint(hash); coinMeta.erase(hash); } + void CSparkWallet::addOrUpdateMint(const CSparkMintMeta& mint, const uint256& lTagHash, CWalletDB& walletdb) { if (mint.i > lastDiversifier) { lastDiversifier = mint.i; @@ -211,12 +212,30 @@ void CSparkWallet::addOrUpdateMint(const CSparkMintMeta& mint, const uint256& lT walletdb.WriteSparkMint(lTagHash, mint); } +void CSparkWallet::updateMintInMemory(const CSparkMintMeta& mint) { + for (auto& itr : coinMeta) { + if (itr.second == mint) { + coinMeta[itr.first] = mint; + break; + } + } +} + CSparkMintMeta CSparkWallet::getMintMeta(const uint256& hash) { if (coinMeta.count(hash)) return coinMeta[hash]; return CSparkMintMeta(); } +CSparkMintMeta CSparkWallet::getMintMeta(const secp_primitives::Scalar& nonce) { + for (const auto& meta : coinMeta) { + if (meta.second.k == nonce) + return meta.second; + } + + return CSparkMintMeta(); +} + void CSparkWallet::UpdateSpendState(const GroupElement& lTag, const uint256& lTagHash, const uint256& txHash, bool fUpdateMint) { if (coinMeta.count(lTagHash)) { auto mintMeta = coinMeta[lTagHash]; @@ -333,6 +352,35 @@ void CSparkWallet::UpdateMintStateFromBlock(const CBlock& block) { } } +void CSparkWallet::RemoveSparkMints(const std::vector& mints) { + for (auto coin : mints) { + try { + spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); + spark::RecoveredCoinData recoveredCoinData = coin.recover(this->fullViewKey, identifiedCoinData); + + CWalletDB walletdb(strWalletFile); + uint256 lTagHash = primitives::GetLTagHash(recoveredCoinData.T); + + eraseMint(lTagHash, walletdb); + } catch (const std::runtime_error &e) { + continue; + } + } +} + + +void CSparkWallet::RemoveSparkSpends(const std::unordered_map& spends) { + for (const auto& spend : spends) { + uint256 lTagHash = primitives::GetLTagHash(spend.first); + if (coinMeta.count(lTagHash)) { + auto mintMeta = coinMeta[lTagHash]; + mintMeta.isUsed = false; + CWalletDB walletdb(strWalletFile); + addOrUpdateMint(mintMeta, lTagHash, walletdb); + walletdb.EraseSparkSpendEntry(spend.first); + } + } +} std::vector CSparkWallet::listAddressCoins(const int32_t& i, bool fUnusedOnly) { @@ -367,7 +415,7 @@ std::vector CSparkWallet::CreateSparkMintRecipients( std::vector serializedCoins = sparkMint.getMintedCoinsSerialized(); if (outputs.size() != serializedCoins.size()) - throw std::runtime_error("Spark mit output number should be equal to required number."); + throw std::runtime_error("Spark mint output number should be equal to required number."); std::vector results; results.reserve(outputs.size()); @@ -1033,7 +1081,9 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( // fill inputs uint32_t sequence = CTxIn::SEQUENCE_FINAL; - tx.vin.emplace_back(COutPoint(), CScript(), sequence); + CScript script; + script << OP_SPARKSPEND; + tx.vin.emplace_back(COutPoint(), script, sequence); // clear vExtraPayload to calculate metadata hash correctly tx.vExtraPayload.clear(); @@ -1108,6 +1158,11 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( spark::SpendTransaction spendTransaction(params, fullViewKey, spendKey, inputs, cover_set_data, fee, transparentOut, privOutputs); spendTransaction.setBlockHashes(idAndBlockHashes); + CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); + serialized << spendTransaction; + tx.vExtraPayload.assign(serialized.begin(), serialized.end()); + + const std::vector& outCoins = spendTransaction.getOutCoins(); for (auto& outCoin : outCoins) { // construct spend script diff --git a/src/spark/wallet.h b/src/spark/wallet.h index cda1a989d2..a8a7371777 100644 --- a/src/spark/wallet.h +++ b/src/spark/wallet.h @@ -56,7 +56,11 @@ class CSparkWallet { void eraseMint(const uint256& hash, CWalletDB& walletdb); // add mint meta data to memory and to db void addOrUpdateMint(const CSparkMintMeta& mint, const uint256& lTagHash, CWalletDB& walletdb); + void updateMintInMemory(const CSparkMintMeta& mint); + // get mint meta from linking tag hash CSparkMintMeta getMintMeta(const uint256& hash); + // get mint tag from nonce + CSparkMintMeta getMintMeta(const secp_primitives::Scalar& nonce); void UpdateSpendState(const GroupElement& lTag, const uint256& lTagHash, const uint256& txHash, bool fUpdateMint = true); void UpdateSpendStateFromMempool(const std::vector& lTags, const uint256& txHash, bool fUpdateMint = true); @@ -64,6 +68,8 @@ class CSparkWallet { void UpdateMintState(const std::vector& coins, const uint256& txHash); void UpdateMintStateFromMempool(const std::vector& coins, const uint256& txHash); void UpdateMintStateFromBlock(const CBlock& block); + void RemoveSparkMints(const std::vector& mints); + void RemoveSparkSpends(const std::unordered_map& spends); // get the vector of mint metadata for a single address std::vector listAddressCoins(const int32_t& i, bool fUnusedOnly = false); diff --git a/src/test/spark_tests.cpp b/src/test/spark_tests.cpp new file mode 100644 index 0000000000..a0ad2c79bb --- /dev/null +++ b/src/test/spark_tests.cpp @@ -0,0 +1,685 @@ +#include "../chainparams.h" +#include "../script/standard.h" +#include "../validation.h" +#include "../wallet/coincontrol.h" +#include "../wallet/wallet.h" +#include "../net.h" + +#include "test_bitcoin.h" +#include "fixtures.h" +#include +#include + +namespace spark { + + // Generate a random char vector from a random scalar + static std::vector random_char_vector() { + Scalar temp; + temp.randomize(); + std::vector result; + result.resize(SCALAR_ENCODING); + temp.serialize(result.data()); + return result; + } + + +class SparkTests : public SparkTestingSetup +{ +public: + SparkTests() : + SparkTestingSetup(), + sparkState(CSparkState::GetState()), + consensus(::Params().GetConsensus()) { + } + + ~SparkTests() { + sparkState->Reset(); + } + + void ExtractSpend(CTransaction const &tx, + std::vector& coins, + std::vector& lTags) { + if (tx.vin[0].scriptSig.IsSparkSpend()) { + coins.clear(); + coins = spark::GetSparkMintCoins(tx); + lTags.clear(); + lTags = spark::GetSparkUsedTags(tx); + } + } + + CBlock GetCBlock(CBlockIndex const *blockIdx) { + CBlock block; + if (!ReadBlockFromDisk(block, blockIdx, ::Params().GetConsensus())) { + throw std::invalid_argument("No block index data"); + } + + return block; + } + + + +public: + CSparkState *sparkState; + Consensus::Params const &consensus; + +}; + +BOOST_FIXTURE_TEST_SUITE(spark_tests, SparkTests) + +BOOST_AUTO_TEST_CASE(schnorr_proof) +{ + auto params = Params::get_default(); + + MintTransaction mintTransaction(params); + BOOST_CHECK(mintTransaction.verify()); +} + +BOOST_AUTO_TEST_CASE(is_spark_allowed) +{ + auto start = ::Params().GetConsensus().nSparkStartBlock; + BOOST_CHECK(!IsSparkAllowed(0)); + BOOST_CHECK(!IsSparkAllowed(start - 1)); + BOOST_CHECK(IsSparkAllowed(start)); + BOOST_CHECK(IsSparkAllowed(start + 1)); +} + +BOOST_AUTO_TEST_CASE(parse_spark_mintscript) +{ + auto params = Params::get_default(); + + // Generate keys + const SpendKey spend_key(params); + const FullViewKey full_view_key(spend_key); + const IncomingViewKey incoming_view_key(full_view_key); + + const uint64_t i = 12345; + const uint64_t v = 1; + const std::string memo = "test memo"; + + // Generate address + const Address address(incoming_view_key, i); + + MintedCoinData mintedCoin; + mintedCoin.address = address; + mintedCoin.v = v; + mintedCoin.memo = memo; + + std::vector outputs; + outputs.push_back(mintedCoin); + + spark::MintTransaction sparkMint(params, outputs, random_char_vector()); + std::vector serializedCoins = sparkMint.getMintedCoinsSerialized(); + + CScript script; + script << OP_SPARKMINT; + script.insert(script.end(), serializedCoins[0].begin(), serializedCoins[0].end()); + + // coin parse test + spark::Coin parsedCoin(params); + ParseSparkMintCoin(script, parsedCoin); + + std::vector coins; + sparkMint.getCoins(coins); + + BOOST_CHECK(parsedCoin == coins[0]); + + // transaction parse test + + std::vector scripts; + scripts.push_back(script); + + MintTransaction mintTransaction(params); + ParseSparkMintTransaction(scripts, mintTransaction); + + BOOST_CHECK(mintTransaction.verify()); + + scripts[0].resize(script.size() - 1); + BOOST_CHECK_THROW(ParseSparkMintTransaction(scripts, mintTransaction), std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(parse_spark_smint) +{ + auto params = Params::get_default(); + + // Generate keys + const SpendKey spend_key(params); + const FullViewKey full_view_key(spend_key); + const IncomingViewKey incoming_view_key(full_view_key); + + const uint64_t i = 12345; + const uint64_t v = 1; + const std::string memo = "test memo"; + + // Generate address + const Address address(incoming_view_key, i); + + spark::Coin coin(params, 0, (Scalar().randomize()), address, v, memo, random_char_vector()); + + CScript script(OP_SPARKSMINT); + + CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); + serialized << coin; + script.insert(script.end(), serialized.begin(), serialized.end()); + + spark::Coin parsedCoin(params); + ParseSparkMintCoin(script, parsedCoin); + + BOOST_CHECK_NO_THROW(coin.identify(incoming_view_key)); + + BOOST_CHECK(coin == parsedCoin); + + parsedCoin.S.randomize(); + BOOST_CHECK_THROW(parsedCoin.identify(incoming_view_key), std::runtime_error); + + spark::Coin parsedCoin2(params); + ParseSparkMintCoin(script, parsedCoin2); + + BOOST_CHECK(coin == parsedCoin2); + + // parse invalid + script.resize(script.size() - 1); + BOOST_CHECK_THROW(ParseSparkMintCoin(script, coin), std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(get_outpoint) +{ + pwalletMain->SetBroadcastTransactions(true); + GenerateBlocks(1100); + + std::vector amounts{2, 10}; + std::vector txs; + auto mints = GenerateMints(amounts, txs); + + BOOST_CHECK_EQUAL(mints.size(), amounts.size()); + + auto mint = mints[1]; + auto nonCommitted = mints[0]; + auto tx = txs[0]; + size_t mintIdx = 0; + + for (; mintIdx < tx.vout.size(); mintIdx++) { + if (tx.vout[mintIdx].scriptPubKey.IsSparkMint()) { + break; + } + } + + auto prevHeight = chainActive.Tip()->nHeight; + mempool.clear(); + auto blockIdx = GenerateBlock({txs[0]}); + + BOOST_CHECK_EQUAL(prevHeight + 1, chainActive.Tip()->nHeight); + + CBlock block; + BOOST_CHECK(ReadBlockFromDisk(block, blockIdx, ::Params().GetConsensus())); + + // verify + COutPoint expectedOut(tx.GetHash(), mintIdx); + + spark::Coin coin = pwalletMain->sparkWallet->getCoinFromMeta(mint); + + // GetOutPointFromBlock + COutPoint out; + BOOST_CHECK(GetOutPointFromBlock(out, coin, block)); + BOOST_CHECK(expectedOut == out); + + spark::Coin nonCommittedCoin = pwalletMain->sparkWallet->getCoinFromMeta(nonCommitted); + + BOOST_CHECK(!GetOutPointFromBlock(out, nonCommittedCoin, block)); + + // GetOutPoint + // by coin + out = COutPoint(); + BOOST_CHECK(GetOutPoint(out, coin)); + BOOST_CHECK(expectedOut == out); + BOOST_CHECK(!GetOutPoint(out, nonCommittedCoin)); + + // by coin hash + out = COutPoint(); + uint256 coin_hash = primitives::GetSparkCoinHash(coin); + BOOST_CHECK(GetOutPoint(out, coin_hash)); + BOOST_CHECK(expectedOut == out); + + uint256 non_commited_coin_hash = primitives::GetSparkCoinHash(nonCommittedCoin); + BOOST_CHECK(!GetOutPoint(out, non_commited_coin_hash)); + + sparkState->Reset(); +} + +BOOST_AUTO_TEST_CASE(build_spark_state) +{ + pwalletMain->SetBroadcastTransactions(true); + + GenerateBlocks(1100); + // generate mints + std::vector txs; + auto mints = GenerateMints({1 * COIN, 2 * COIN, 3 * COIN, 4 * COIN}, txs); + mempool.clear(); + GenerateBlock({txs[0], txs[1]}); + auto blockIdx1 = chainActive.Tip(); + auto block1 = GetCBlock(blockIdx1); + + GenerateBlock({txs[2], txs[3]}); + auto blockIdx2 = chainActive.Tip(); + auto block2 = GetCBlock(blockIdx2); + + BOOST_CHECK(BuildSparkStateFromIndex(&chainActive)); + BOOST_CHECK(sparkState->HasCoin(pwalletMain->sparkWallet->getCoinFromMeta(mints[0]))); + BOOST_CHECK(sparkState->HasCoin(pwalletMain->sparkWallet->getCoinFromMeta(mints[1]))); + BOOST_CHECK(sparkState->HasCoin(pwalletMain->sparkWallet->getCoinFromMeta(mints[2]))); + BOOST_CHECK(sparkState->HasCoin(pwalletMain->sparkWallet->getCoinFromMeta(mints[3]))); + + sparkState->Reset(); +} + +BOOST_AUTO_TEST_CASE(connect_and_disconnect_block) +{ + // util function + auto reconnect = [](CBlock const &block) { + LOCK(cs_main); + + std::shared_ptr sharedBlock = + std::make_shared(block); + + CValidationState state; + ActivateBestChain(state, ::Params(), sharedBlock); + }; + + pwalletMain->SetBroadcastTransactions(true); + GenerateBlocks(1100); + + std::vector mintTxs; + auto mints = GenerateMints({3 * COIN, 2 * COIN}, mintTxs); + std::vector mintTxs2; + auto mints2 = GenerateMints({3 * COIN }, mintTxs2); + + struct { + // expected state. + std::vector coins; + std::vector lTags; + + // first group + CBlockIndex *first = nullptr; + CBlockIndex *last = nullptr; + + int lastId = 0; + + // real state + CSparkState *state; + + void Verify() const { + auto const &spends = state->GetSpends(); + BOOST_CHECK_EQUAL(lTags.size(), spends.size()); + for (auto const &lTag : lTags) { + BOOST_CHECK_MESSAGE(spends.count(lTag), "lTag is not found on state"); + } + + auto const &mints = state->GetMints(); + BOOST_CHECK_EQUAL(coins.size(), mints.size()); + for (auto const &c : coins) + BOOST_CHECK_MESSAGE(mints.count(c), "public is not found on state"); + + auto retrievedId = state->GetLatestCoinID(); + + CSparkState::SparkCoinGroupInfo group; + state->GetCoinGroupInfo(retrievedId, group); + BOOST_CHECK_EQUAL(lastId, retrievedId); + BOOST_CHECK_EQUAL(first, group.firstBlock); + BOOST_CHECK_EQUAL(last, group.lastBlock); + BOOST_CHECK_EQUAL(coins.size(), group.nCoins); + } + } checker; + + checker.state = sparkState; + + // Cache empty checker + auto emptyChecker = checker; + + mempool.clear(); + // Generate some txs which contain mints + auto blockIdx1 = GenerateBlock({mintTxs[0], mintTxs[1]}); + BOOST_CHECK(blockIdx1); + auto block1 = GetCBlock(blockIdx1); + + checker.coins.push_back(pwalletMain->sparkWallet->getCoinFromMeta(mints[0])); + checker.coins.push_back(pwalletMain->sparkWallet->getCoinFromMeta(mints[1])); + checker.first = blockIdx1; + checker.last = blockIdx1; + checker.lastId = 1; + checker.Verify(); + + // Generate empty blocks should not affect state + GenerateBlocks(10); + checker.Verify(); + + // Add spend tx + + // Create two txs which contains same serial. + CCoinControl coinControl; + + { + auto tx = mintTxs[0]; + auto it = std::find_if(tx.vout.begin(), tx.vout.end(), [](CTxOut const &out) -> bool { + return out.scriptPubKey.IsSparkMint(); + }); + BOOST_CHECK(it != tx.vout.end()); + + coinControl.Select(COutPoint(tx.GetHash(), std::distance(tx.vout.begin(), it))); + } + + auto sTx1 = GenerateSparkSpend({1 * COIN}, {}, &coinControl); + // Update isused status + { + CSparkMintMeta meta = pwalletMain->sparkWallet->getMintMeta(mints[1].k); + + BOOST_CHECK(meta != CSparkMintMeta()); + BOOST_CHECK(meta.isUsed); + + meta.isUsed = false; + pwalletMain->sparkWallet->updateMintInMemory(meta); + meta = CSparkMintMeta(); + meta = pwalletMain->sparkWallet->getMintMeta(mints[1].k); + BOOST_CHECK(!meta.isUsed); + } + + std::size_t old_size = mempool.size(); + // Create duplicated serial tx and test this at the bottom + auto dupTx1 = GenerateSparkSpend({1 * COIN}, {}, &coinControl); + + // check that it is not accepted into mempool + BOOST_CHECK(old_size == mempool.size()); + + std::vector dupNewCoins1; + std::vector dupTags1; + ExtractSpend(dupTx1[0], dupNewCoins1, dupTags1); + + std::vector newCoins1; + std::vector tags1; + ExtractSpend(sTx1[0], newCoins1, tags1); + BOOST_CHECK_EQUAL(1, newCoins1.size()); + BOOST_CHECK_EQUAL(1, tags1.size()); + BOOST_CHECK(dupTags1[0] == tags1[0]); + + mempool.clear(); + auto blockIdx2 = GenerateBlock({sTx1[0]}); + BOOST_CHECK(blockIdx2); + + auto block2 = GetCBlock(blockIdx2); + + auto cacheChecker = checker; + checker.coins.push_back(newCoins1.front()); + checker.lTags.push_back(tags1.front()); + checker.last = blockIdx2; + + checker.Verify(); + + // state should be rolled back + BOOST_CHECK(DisconnectBlocks(1)); + BOOST_CHECK_EQUAL(chainActive.Tip()->nHeight, blockIdx2->nHeight - 1); + cacheChecker.Verify(); + + // reconnect + reconnect(block2); + checker.Verify(); + + // add more block contain both mint and serial + auto sTx2 = GenerateSparkSpend({1 * COIN}, {}, nullptr); + + std::vector newCoins2; + std::vector tags2; + ExtractSpend(sTx2[0], newCoins2, tags2); + BOOST_CHECK_EQUAL(1, newCoins2.size()); + BOOST_CHECK_EQUAL(1, tags2.size()); + + BOOST_CHECK(mempool.size() == 1); + mempool.clear(); + std::vector blockTX; + auto blockIdx3 = GenerateBlock({mintTxs2[0], sTx2[0]}); + BOOST_CHECK(blockIdx3); + auto block3 = GetCBlock(blockIdx3); + + checker.coins.insert(checker.coins.end(), newCoins2.begin(), newCoins2.end()); + checker.coins.push_back(pwalletMain->sparkWallet->getCoinFromMeta(mints2[0])); + checker.lTags.push_back(tags2[0]); + checker.last = blockIdx3; + + checker.Verify(); + + // Clear state and rebuild + sparkState->Reset(); + emptyChecker.Verify(); + + BuildSparkStateFromIndex(&chainActive); + checker.Verify(); + + // Disconnect all and reconnect + std::vector blocks; + while (chainActive.Tip() != chainActive.Genesis()) { + blocks.push_back(GetCBlock(chainActive.Tip())); + DisconnectBlocks(1); + } + + emptyChecker.Verify(); + + for (auto const &block : blocks) { + reconnect(block); + } + + checker.Verify(); + + // double spend + auto currentBlock = chainActive.Tip()->nHeight; + BOOST_CHECK(!GenerateBlock({dupTx1[0]})); + BOOST_CHECK_EQUAL(currentBlock, chainActive.Tip()->nHeight); + mempool.clear(); + sparkState->Reset(); +} + +BOOST_AUTO_TEST_CASE(checktransaction) +{ + GenerateBlocks(1100); + + // mints + std::vector txs; + GenerateMints({110 * COIN}, txs); + auto &tx = txs[0]; + + CValidationState state; + CSparkTxInfo info; + BOOST_CHECK(CheckSparkTransaction( + txs[0], state, tx.GetHash(), false, chainActive.Height(), true, true, &info)); + + std::vector expectedCoins = spark::GetSparkMintCoins(tx); + BOOST_CHECK(expectedCoins == info.mints); + + // spend + txs.clear(); + pwalletMain->SetBroadcastTransactions(true); + auto mints = GenerateMints({10 * COIN, 1 * COIN}, txs); + mempool.clear(); + + auto currentBlock = chainActive.Tip()->nHeight; + GenerateBlock(txs); + BOOST_CHECK_EQUAL(currentBlock, chainActive.Tip()->nHeight -1); + + GenerateBlocks(10); + + auto outputAmount = 1 * COIN; + auto mintAmount = 2 * CENT - CENT; // a cent as fee + CAmount fee; + CWalletTx wtx = pwalletMain->SpendAndStoreSpark({{script, outputAmount, false}}, {}, fee)[0]; + + CMutableTransaction spendTx(wtx); + auto spend = ParseSparkSpend(spendTx); + + // test get join split amounts + BOOST_CHECK_EQUAL(1, GetSpendInputs(spendTx)); + + info = CSparkTxInfo(); + + BOOST_CHECK(CheckSparkTransaction( + spendTx, state, spendTx.GetHash(), false, chainActive.Height(), false, true, &info)); + + auto &lTags = spend.getUsedLTags(); + auto &ids = spend.getCoinGroupIds(); + + for (size_t i = 0; i != lTags.size(); i++) { + bool hasLTag = false; + BOOST_CHECK_MESSAGE(hasLTag = (info.spentLTags.count(lTags[i]) > 0), "No linking tag as expected"); + if (hasLTag) { + BOOST_CHECK_MESSAGE(ids[i] == info.spentLTags[lTags[i]], "linking tag group id is invalid"); + } + } + + info = CSparkTxInfo(); + BOOST_CHECK(CheckSparkTransaction( + spendTx, state, spendTx.GetHash(), false, chainActive.Height(), false, true, &info)); + + + BOOST_CHECK(!CheckSparkTransaction( + spendTx, state, spendTx.GetHash(), false, chainActive.Height(), false, true, &info)); +} + +BOOST_AUTO_TEST_CASE(coingroup) +{ + GenerateBlocks(1100); + + // util function + auto reconnect = [](CBlock const &block) { + LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK(mempool.cs); + + std::shared_ptr sharedBlock = + std::make_shared(block); + + CValidationState state; + ActivateBestChain(state, ::Params(), sharedBlock); + }; + + struct { + // expected state. + std::vector coins; + + // first group + CBlockIndex *first = nullptr; + CBlockIndex *last = nullptr; + + int lastId = 0; + size_t lastGroupCoins = 0; + + // real state + CSparkState *state; + + void Verify(std::string stateName = "") const { + auto const &mints = state->GetMints(); + BOOST_CHECK_EQUAL(coins.size(), mints.size()); + for (auto const &c : coins) { + BOOST_CHECK_MESSAGE(mints.count(c), "Coin is not found on state : " + stateName); + } + + auto retrievedId = state->GetLatestCoinID(); + + CSparkState::SparkCoinGroupInfo group; + state->GetCoinGroupInfo(retrievedId, group); + + BOOST_CHECK_EQUAL(lastId, retrievedId); + BOOST_CHECK_EQUAL(first, group.firstBlock); + BOOST_CHECK_EQUAL(last, group.lastBlock); + BOOST_CHECK_EQUAL(lastGroupCoins, group.nCoins); + } + } checker; + checker.state = sparkState; + + sparkState->~CSparkState(); + new (sparkState) CSparkState(65, 16); + sparkState->Reset(); + + pwalletMain->SetBroadcastTransactions(true); + // logic + std::vector txs; + auto mints = GenerateMints(std::vector(10, 1), txs); + + auto txRange = [&](size_t start, size_t end) -> std::vector { + std::vector rangeTxs; + for (auto i = start; i < end && i < txs.size(); i++) { + rangeTxs.push_back(txs[i]); + } + + return rangeTxs; + }; + + auto emptyChecker = checker; + emptyChecker.Verify(); + + // add one block + mempool.clear(); + auto idx1 = GenerateBlock(txRange(0, 1)); + auto block1 = GetCBlock(idx1); + checker.coins.push_back(pwalletMain->sparkWallet->getCoinFromMeta(mints[mints.size()-1])); + checker.lastId = 1; + checker.first = idx1; + checker.last = idx1; + checker.lastGroupCoins = 1; + checker.Verify(); + + // add more + auto idx2 = GenerateBlock(txRange(1, 10)); + auto block2 = GetCBlock(idx2); + for (size_t i = 0; i < (mints.size() - 1); ++i) + checker.coins.push_back(pwalletMain->sparkWallet->getCoinFromMeta(mints[i])); + + checker.last = idx2; + checker.lastGroupCoins = 10; + checker.Verify(); + + auto cacheIdx2Checker = checker; + + + // add more to fill group + txs.clear(); + mints = GenerateMints(std::vector(55, 1), txs); + mempool.clear(); + auto idx3 = GenerateBlock(txRange(0, 22)); + auto block3 = GetCBlock(idx3); + auto idx4 = GenerateBlock(txRange(22, 55)); + auto block4 = GetCBlock(idx4); + for (size_t i = 0; i < mints.size(); ++i) + checker.coins.push_back(pwalletMain->sparkWallet->getCoinFromMeta(mints[i])); + checker.last = idx4; + checker.lastGroupCoins = 65; + checker.Verify(); + + auto cacheIdx3Checker = checker; + txs.clear(); + mints = GenerateMints(std::vector(1, 1), txs); + mempool.clear(); + + // add one more to create new group + auto idx5 = GenerateBlock(txRange(0, 1)); + auto block5 = GetCBlock(idx5); + checker.coins.push_back(pwalletMain->sparkWallet->getCoinFromMeta(mints[mints.size()-1])); + checker.lastId = 2; + checker.lastGroupCoins = 34; + checker.first = idx4; + checker.last = idx5; + checker.Verify(); + + // remove last block check coingroup + DisconnectBlocks(1); + cacheIdx3Checker.Verify(); + + // remove one more block + DisconnectBlocks(2); + cacheIdx2Checker.Verify(); + + // reconnect them all and check state + reconnect(block3); + reconnect(block4); + reconnect(block5); + checker.Verify(); + + sparkState->Reset(); +} + +} // end of namespace spark + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/validation.cpp b/src/validation.cpp index 30eefc2b61..1d90942282 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -538,7 +538,7 @@ int64_t GetTransactionSigOpCost(const CTransaction &tx, const CCoinsViewCache &i { int64_t nSigOps = GetLegacySigOpCount(tx) * WITNESS_SCALE_FACTOR; - if (tx.IsCoinBase() || tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsLelantusJoinSplit()) + if (tx.IsCoinBase() || tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) return nSigOps; if (flags & SCRIPT_VERIFY_P2SH) { @@ -648,7 +648,8 @@ bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fChe for (const auto& txin : tx.vin) if (txin.prevout.IsNull() && !(txin.scriptSig.IsZerocoinSpend() || txin.IsZerocoinRemint() - || txin.IsLelantusJoinSplit() )) + || txin.IsLelantusJoinSplit() + || tx.IsSparkSpend())) return state.DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null"); if (tx.IsZerocoinV3SigmaTransaction()) { @@ -2523,6 +2524,14 @@ static DisconnectResult DisconnectBlock(const CBlock& block, CValidationState& s // do nothing } } + else if (tx.IsSparkSpend()) { + try { + nFees = spark::ParseSparkSpend(tx).getFee(); + } + catch (...) { + // do nothing + } + } dbIndexHelper.DisconnectTransactionInputs(tx, pindex->nHeight, i, view); } @@ -2871,7 +2880,6 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return state.DoS(100, error("ConnectBlock(): invalid joinsplit tx"), REJECT_INVALID, "bad-txns-input-invalid"); - if (!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { if (!view.HaveInputs(tx)) @@ -2982,7 +2990,6 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin block.lelantusTxInfo->Complete(); block.sparkTxInfo->Complete(); - int64_t nTime3 = GetTimeMicros(); nTimeConnect += nTime3 - nTime2; LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime3 - nTime2), 0.001 * (nTime3 - nTime2) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime3 - nTime2) / (nInputs-1), nTimeConnect * 0.000001); @@ -3117,7 +3124,6 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if (!pblocktree->UpdateSpentIndex(dbIndexHelper.getSpentIndex())) return AbortNode(state, "Failed to write transaction index"); - if (fTimestampIndex) if (!pblocktree->WriteTimestampIndex(CTimestampIndexKey(pindex->nTime, pindex->GetBlockHash()))) return AbortNode(state, "Failed to write timestamp index"); @@ -3471,7 +3477,6 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara block.lelantusTxInfo = std::make_shared(); block.sparkTxInfo = std::make_shared(); - std::unordered_map lelantusSerialsToRemove; std::vector rangeProofsToRemove; sigma::spend_info_container sigmaSerialsToRemove; @@ -3629,6 +3634,14 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara if (block.lelantusTxInfo->mints.size() > 0) { pwalletMain->zwallet->GetTracker().UpdateMintStateFromBlock(block.lelantusTxInfo->mints); } + + if (block.sparkTxInfo->spentLTags.size() > 0) { + pwalletMain->sparkWallet->RemoveSparkSpends(block.sparkTxInfo->spentLTags); + } + + if (block.sparkTxInfo->mints.size() > 0) { + pwalletMain->sparkWallet->RemoveSparkMints(block.sparkTxInfo->mints); + } } #endif @@ -3784,7 +3797,7 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, } if (blockConnecting.sparkTxInfo->spentLTags.size() > 0) { - LogPrintf("HDmint: UpdateSpendStateFromBlock. [height: %d]\n", GetHeight()); + LogPrintf("SparkWallet: UpdateSpendStateFromBlock. [height: %d]\n", GetHeight()); pwalletMain->sparkWallet->UpdateSpendStateFromBlock(blockConnecting); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 685b7112ae..0980cb5622 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5536,6 +5536,37 @@ std::vector CWallet::CreateSparkSpendTransaction( return sparkWallet->CreateSparkSpendTransaction(recipients, privateRecipients, fee, coinControl); } + +std::vector CWallet::SpendAndStoreSpark( + const std::vector& recipients, + const std::vector>& privateRecipients, + CAmount &fee, + const CCoinControl *coinControl) +{ + // create transaction + auto result = CreateSparkSpendTransaction(recipients, privateRecipients, fee, coinControl); + + // commit + for (auto& wtxNew : result) { + try { + CValidationState state; + CReserveKey reserveKey(this); + CommitTransaction(wtxNew, reserveKey, g_connman.get(), state); + } catch (...) { + auto error = _( + "Error: The transaction was rejected! This might happen if some of " + "the coins in your wallet were already spent, such as if you used " + "a copy of wallet.dat and coins were spent in the copy but not " + "marked as spent here." + ); + + std::throw_with_nested(std::runtime_error(error)); + } + } + + return result; +} + std::pair CWallet::EstimateJoinSplitFee( CAmount required, bool subtractFeeFromAmount, diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 5446d137e5..4911f33338 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1811,8 +1811,8 @@ bool CWalletDB::HasSparkSpendEntry(const secp_primitives::GroupElement& lTag) { return Exists(std::make_pair(std::string("spark_spend"), lTag)); } -bool CWalletDB::EraseSparkSpendEntry(const CSparkSpendEntry& sparkSpend) { - return Erase(std::make_pair(std::string("spark_spend"), sparkSpend.lTag)); +bool CWalletDB::EraseSparkSpendEntry(const secp_primitives::GroupElement& lTag) { + return Erase(std::make_pair(std::string("spark_spend"), lTag)); } /******************************************************************************/ diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index d72da6c48d..ac5093ac88 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -311,7 +311,7 @@ class CWalletDB : public CDB bool WriteSparkSpendEntry(const CSparkSpendEntry& sparkSpend); bool ReadSparkSpendEntry(const secp_primitives::GroupElement& lTag, CSparkSpendEntry& sparkSpend); bool HasSparkSpendEntry(const secp_primitives::GroupElement& lTag); - bool EraseSparkSpendEntry(const CSparkSpendEntry& sparkSpend); + bool EraseSparkSpendEntry(const secp_primitives::GroupElement& lTag); //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain); From ee72bbfdf672c5100c87a6c2b147b0113a412cde Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 28 Oct 2022 05:26:31 +0400 Subject: [PATCH 055/197] More review comments resolved --- src/Makefile.am | 4 +-- src/libspark/chaum.cpp | 2 ++ src/libspark/keys.cpp | 1 - src/libspark/keys.h | 1 - src/miner.cpp | 36 ++++++++++++++++++++- src/miner.h | 4 +++ src/spark/{wallet.cpp => sparkwallet.cpp} | 38 ++++++++++++++--------- src/spark/{wallet.h => sparkwallet.h} | 3 +- src/spark/state.cpp | 7 ----- src/wallet/wallet.h | 2 +- src/wallet/walletdb.cpp | 2 +- 11 files changed, 71 insertions(+), 29 deletions(-) rename src/spark/{wallet.cpp => sparkwallet.cpp} (98%) rename src/spark/{wallet.h => sparkwallet.h} (98%) diff --git a/src/Makefile.am b/src/Makefile.am index 9ef5f432f4..d993ffbebf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -218,7 +218,7 @@ BITCOIN_CORE_H = \ wallet/sigmaspendbuilder.h \ wallet/txbuilder.h \ wallet/lelantusjoinsplitbuilder.h \ - spark/wallet.h \ + spark/sparkwallet.h \ spark/primitives.h \ wallet/wallet.h \ wallet/walletexcept.h \ @@ -445,7 +445,7 @@ libbitcoin_wallet_a_SOURCES = \ wallet/lelantusjoinsplitbuilder.cpp \ wallet/walletexcept.cpp \ wallet/wallet.cpp \ - spark/wallet.cpp \ + spark/sparkwallet.cpp \ spark/primitives.cpp \ wallet/walletdb.cpp \ wallet/authhelper.cpp \ diff --git a/src/libspark/chaum.cpp b/src/libspark/chaum.cpp index 4fb219fa64..b2d984fb57 100644 --- a/src/libspark/chaum.cpp +++ b/src/libspark/chaum.cpp @@ -157,6 +157,8 @@ bool Chaum::verify( } secp_primitives::MultiExponent multiexp(points, scalars); + // merged equalities and doing check in one multiexponentation, + // for weighting we use random w return multiexp.get_multiple().isInfinity(); } diff --git a/src/libspark/keys.cpp b/src/libspark/keys.cpp index c654167081..165e1a463a 100644 --- a/src/libspark/keys.cpp +++ b/src/libspark/keys.cpp @@ -5,7 +5,6 @@ namespace spark { using namespace secp_primitives; -SpendKey::SpendKey() {} SpendKey::SpendKey(const Params* params) { this->params = params; this->s1.randomize(); diff --git a/src/libspark/keys.h b/src/libspark/keys.h index 3895421516..ebb81214f1 100644 --- a/src/libspark/keys.h +++ b/src/libspark/keys.h @@ -14,7 +14,6 @@ using namespace secp_primitives; class SpendKey { public: - SpendKey(); SpendKey(const Params* params); SpendKey(const Params* params, const Scalar& r_); const Params* get_params() const; diff --git a/src/miner.cpp b/src/miner.cpp index bf29aec84e..0a0ea2daf6 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -153,6 +153,9 @@ void BlockAssembler::resetBlock() nLelantusSpendAmount = 0; nLelantusSpendInputs = 0; + + nSparkSpendAmount = 0; + nSparkSpendInputs = 0; } std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx) @@ -457,6 +460,22 @@ bool BlockAssembler::TestForBlock(CTxMemPool::txiter iter) return false; } + // Check transaction against spark limits + if(tx.IsSparkSpend()) { + CAmount spendAmount = spark::GetSpendTransparentAmount(tx); + size_t spendNumber = spark::GetSpendInputs(tx); + const auto ¶ms = chainparams.GetConsensus(); + + if (spendNumber > params.nMaxLelantusInputPerTransaction || spendAmount > params.nMaxValueLelantusSpendPerTransaction) + return false; + + if (spendNumber + nSparkSpendInputs > params.nMaxLelantusInputPerBlock) + return false; + + if (spendAmount + nSparkSpendAmount > params.nMaxValueLelantusSpendPerBlock) + return false; + } + return true; } @@ -489,6 +508,21 @@ void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) return; } + if(tx.IsSparkSpend()) { + CAmount spendAmount = spark::GetSpendTransparentAmount(tx); + size_t spendNumber = spark::GetSpendInputs(tx); + const auto ¶ms = chainparams.GetConsensus(); + + if (spendAmount > params.nMaxValueLelantusSpendPerTransaction) + return; + + if ((nSparkSpendAmount += spendAmount) > params.nMaxValueLelantusSpendPerBlock) + return; + + if ((nSparkSpendInputs += spendNumber) > params.nMaxLelantusInputPerBlock) + return; + } + pblock->vtx.emplace_back(iter->GetSharedTx()); pblocktemplate->vTxFees.push_back(iter->GetFee()); pblocktemplate->vTxSigOpsCost.push_back(iter->GetSigOpCost()); @@ -914,7 +948,7 @@ void BlockAssembler::FillBlackListForBlockTemplate() { // transactions depending (directly or not) on sigma spends in the mempool cannot be included in the // same block with spend transaction - if (tx.IsSigmaSpend() || tx.IsLelantusJoinSplit()) { + if (tx.IsSigmaSpend() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) { mempool.CalculateDescendants(mi, txBlackList); // remove privacy transaction itself txBlackList.erase(mi); diff --git a/src/miner.h b/src/miner.h index aec48edb0c..b6348ab94e 100644 --- a/src/miner.h +++ b/src/miner.h @@ -174,6 +174,10 @@ class BlockAssembler CAmount nLelantusSpendAmount; size_t nLelantusSpendInputs; + // lelantus spend limits + CAmount nSparkSpendAmount; + size_t nSparkSpendInputs; + // transactions we cannot include in this block CTxMemPool::setEntries txBlackList; diff --git a/src/spark/wallet.cpp b/src/spark/sparkwallet.cpp similarity index 98% rename from src/spark/wallet.cpp rename to src/spark/sparkwallet.cpp index 32514cd207..b9b6538601 100644 --- a/src/spark/wallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -1,4 +1,4 @@ -#include "wallet.h" +#include "sparkwallet.h" #include "../wallet/wallet.h" #include "../wallet/coincontrol.h" #include "../wallet/walletexcept.h" @@ -17,6 +17,7 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { CWalletDB walletdb(strWalletFile); this->strWalletFile = strWalletFile; const spark::Params* params = spark::Params::get_default(); + fullViewKey = spark::FullViewKey(params); viewKey = spark::IncomingViewKey(params); // try to get incoming view key from db, if it fails, that means it is first start @@ -26,7 +27,7 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { return; } // Generating spark key set first time - spark::SpendKey spendKey = generateSpendKey(); + spark::SpendKey spendKey = generateSpendKey(params); fullViewKey = generateFullViewKey(spendKey); viewKey = generateIncomingViewKey(fullViewKey); @@ -106,6 +107,16 @@ spark::Address CSparkWallet::generateNextAddress() { return spark::Address(viewKey, lastDiversifier); } +spark::Address CSparkWallet::generateNewAddress() { + lastDiversifier++; + spark::Address address(viewKey, lastDiversifier); + + addresses[lastDiversifier] = address; + CWalletDB walletdb(strWalletFile); + updatetDiversifierInDB(walletdb); + return address; +} + spark::Address CSparkWallet::getDefaultAddress() { if (addresses.count(0)) return addresses[0]; @@ -113,10 +124,10 @@ spark::Address CSparkWallet::getDefaultAddress() { return spark::Address(viewKey, lastDiversifier); } -spark::SpendKey CSparkWallet::generateSpendKey() { +spark::SpendKey CSparkWallet::generateSpendKey(const spark::Params* params) { if (pwalletMain->IsLocked()) { LogPrintf("Spark spend key generation FAILED, wallet is locked\n"); - return spark::SpendKey(); + return spark::SpendKey(params); } CKey secret; @@ -138,7 +149,6 @@ spark::SpendKey CSparkWallet::generateSpendKey() { secp_primitives::Scalar r; r.memberFromSeed(hash); - const spark::Params* params = spark::Params::get_default(); spark::SpendKey key(params, r); return key; } @@ -989,15 +999,16 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( { LOCK2(cs_main, pwalletMain->cs_wallet); { + const spark::Params* params = spark::Params::get_default(); spark::CSparkState *sparkState = spark::CSparkState::GetState(); - spark::SpendKey spendKey; + spark::SpendKey spendKey(params); try { - spendKey = std::move(generateSpendKey()); + spendKey = std::move(generateSpendKey(params)); } catch (std::exception& e) { throw std::runtime_error(_("Unable to generate spend key.")); } - if (spendKey == spark::SpendKey()) + if (spendKey == spark::SpendKey(params)) throw std::runtime_error(_("Unable to generate spend key, looks wallet locked.")); @@ -1096,7 +1107,6 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( // We will write this into cover set representation, with anonymity set hash uint256 sig = tx.GetHash(); - const spark::Params* params = spark::Params::get_default(); std::vector inputs; std::map idAndBlockHashes; std::unordered_map cover_set_data; @@ -1390,11 +1400,6 @@ std::list> CSparkWallet::GetAvailableSpar return true; } - // ignore if coin is locked - if (lockedCoins.count(outPoint) > 0){ - return true; - } - // if we are using coincontrol, filter out unselected coins if (coinControl != NULL){ if (coinControl->HasSelected()){ @@ -1404,6 +1409,11 @@ std::list> CSparkWallet::GetAvailableSpar } } + // ignore if coin is locked + if (lockedCoins.count(outPoint) > 0){ + return true; + } + return false; }); diff --git a/src/spark/wallet.h b/src/spark/sparkwallet.h similarity index 98% rename from src/spark/wallet.h rename to src/spark/sparkwallet.h index a8a7371777..61b440545f 100644 --- a/src/spark/wallet.h +++ b/src/spark/sparkwallet.h @@ -25,6 +25,7 @@ class CSparkWallet { // increment diversifier and generate address for that spark::Address generateNextAddress(); + spark::Address generateNewAddress(); spark::Address getDefaultAddress(); // assign difersifier to the value from db void resetDiversifierFromDB(CWalletDB& walletdb); @@ -32,7 +33,7 @@ class CSparkWallet { void updatetDiversifierInDB(CWalletDB& walletdb); // functions for key set generation - spark::SpendKey generateSpendKey(); + spark::SpendKey generateSpendKey(const spark::Params* params); spark::FullViewKey generateFullViewKey(const spark::SpendKey& spend_key); spark::IncomingViewKey generateIncomingViewKey(const spark::FullViewKey& full_view_key); diff --git a/src/spark/state.cpp b/src/spark/state.cpp index 6e36fb045a..dffa6881e5 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -371,7 +371,6 @@ bool CheckSparkBlock(CValidationState &state, const CBlock& block) { auto& consensus = ::Params().GetConsensus(); size_t blockSpendsAmount = 0; - CAmount blockSpendsValue(0); for (const auto& tx : block.vtx) { auto txSpendsValue = GetSpendTransparentAmount(*tx); @@ -388,7 +387,6 @@ bool CheckSparkBlock(CValidationState &state, const CBlock& block) { } blockSpendsAmount += txSpendNumber; - blockSpendsValue += txSpendsValue; } if (blockSpendsAmount > consensus.nMaxLelantusInputPerBlock) { @@ -396,11 +394,6 @@ bool CheckSparkBlock(CValidationState &state, const CBlock& block) { "bad-txns-spark-spend-invalid"); } - if (blockSpendsValue > consensus.nMaxValueLelantusSpendPerBlock) { - return state.DoS(100, false, REJECT_INVALID, - "bad-txns-spark-spend-invalid"); - } - return true; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index ef871bb75c..59b4109a1c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -20,7 +20,7 @@ #include "wallet/walletdb.h" #include "wallet/rpcwallet.h" #include "wallet/mnemoniccontainer.h" -#include "../spark/wallet.h" +#include "../spark/sparkwallet.h" #include "../base58.h" #include "firo_params.h" #include "univalue.h" diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 4911f33338..c9efbdc4a9 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -13,7 +13,7 @@ #include "util.h" #include "utiltime.h" #include "wallet/wallet.h" -#include "spark/wallet.h" +#include "spark/sparkwallet.h" #include "bip47/account.h" From f054741d65e28270883f42a01748693831fb1afe Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 28 Oct 2022 07:13:57 +0400 Subject: [PATCH 056/197] Adding several rpc calls for spark --- src/libspark/util.h | 2 + src/spark/sparkwallet.cpp | 5 +- src/spark/sparkwallet.h | 2 +- src/spark/state.cpp | 10 +++ src/spark/state.h | 1 + src/wallet/rpcwallet.cpp | 133 ++++++++++++++++++++++++++++++++++++++ src/wallet/wallet.cpp | 4 +- 7 files changed, 150 insertions(+), 7 deletions(-) diff --git a/src/libspark/util.h b/src/libspark/util.h index 39fd429731..d976a03dd2 100644 --- a/src/libspark/util.h +++ b/src/libspark/util.h @@ -70,6 +70,8 @@ const unsigned char ADDRESS_ENCODING_PREFIX = 'p'; // TODO: Extend/update/replace these as needed! These are just initial examples const unsigned char ADDRESS_NETWORK_MAINNET = 'm'; const unsigned char ADDRESS_NETWORK_TESTNET = 't'; +const unsigned char ADDRESS_NETWORK_REGTEST = 'r'; +const unsigned char ADDRESS_NETWORK_DEVNET = 'd'; class SparkUtils { public: diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index b9b6538601..c6b5b5ea95 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -953,8 +953,7 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( assert(tx.nLockTime <= static_cast(chainActive.Height())); assert(tx.nLockTime < LOCKTIME_THRESHOLD); - CWalletDB walletdb(strWalletFile); - std::list> coins = GetAvailableSparkCoins(walletdb, coinControl); + std::list> coins = GetAvailableSparkCoins(coinControl); // TODO levon check spend limit std::vector>>> estimated = SelectSparkCoins(vOut + mintVOut, recipientsToSubtractFee, coins, privateRecipients.size(), recipients.size(), coinControl); @@ -1377,7 +1376,7 @@ std::vector> CSparkWallet::GetAvailableSparkCoins(CWalletDB& walletdb, const CCoinControl *coinControl) const { +std::list> CSparkWallet::GetAvailableSparkCoins(const CCoinControl *coinControl) const { std::list> coins; // get all unsued coins from spark wallet std::vector vecMints = this->ListSparkMints(true, true); diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 61b440545f..1426fd4e0b 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -107,7 +107,7 @@ class CSparkWallet { const CCoinControl *coinControl); // Returns the list of pairs of coins and metadata for that coin, - std::list> GetAvailableSparkCoins(CWalletDB& walletdb, const CCoinControl *coinControl = NULL) const; + std::list> GetAvailableSparkCoins(const CCoinControl *coinControl = NULL) const; private: std::string strWalletFile; diff --git a/src/spark/state.cpp b/src/spark/state.cpp index dffa6881e5..904e6c211d 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -67,6 +67,16 @@ bool IsSparkAllowed(int height) return height >= ::Params().GetConsensus().nSparkStartBlock; } +unsigned char GetNetworkType() { + if (::Params().GetConsensus().IsMain()) + return ADDRESS_NETWORK_MAINNET; + else if (::Params().GetConsensus().IsTestnet()) + return ADDRESS_NETWORK_TESTNET; + else if (::Params().GetConsensus().IsDevnet()) + return ADDRESS_NETWORK_DEVNET; + else + return ADDRESS_NETWORK_REGTEST; +} /* * Util funtions diff --git a/src/spark/state.h b/src/spark/state.h index 8c9eb84cf2..9a44228f2a 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -37,6 +37,7 @@ class CSparkTxInfo { // check if spark activation block is passed bool IsSparkAllowed(); bool IsSparkAllowed(int height); +unsigned char GetNetworkType(); // Pass Scripts form mint transaction and get spark MintTransaction object void ParseSparkMintTransaction(const std::vector& scripts, MintTransaction& mintTransaction); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 6904f0e71f..6ff398f90f 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -78,6 +78,13 @@ void EnsureLelantusWalletIsAvailable() } } +void EnsureSparkWalletIsAvailable() +{ + if (!pwalletMain || !pwalletMain->zwallet) { + throw JSONRPCError(RPC_WALLET_ERROR, "lelantus mint/joinsplit is not allowed for legacy wallet"); + } +} + void EnsureWalletIsUnlocked(CWallet * const pwallet) { if (pwallet->IsLocked()) { @@ -3164,6 +3171,127 @@ UniValue listunspentlelantusmints(const JSONRPCRequest& request) { return results; } +UniValue listunspentsparkmints(const JSONRPCRequest& request) { + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 0) { + throw std::runtime_error( + "listunspentsparkmints \n" + "Returns array of unspent mints coins\n" + "Results are an array of Objects, each of which has:\n" + "{txid, nHeight, scriptPubKey, amount}"); + } + + if (pwallet->IsLocked()) { + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, + "Error: Please enter the wallet passphrase with walletpassphrase first."); + } + + EnsureSparkWalletIsAvailable(); + + UniValue results(UniValue::VARR);; + assert(pwallet != NULL); + + std::list> coins = pwallet->sparkWallet->GetAvailableSparkCoins(); + LogPrintf("coins.size()=%s\n", coins.size()); + BOOST_FOREACH(const auto& coin, coins) + { + UniValue entry(UniValue::VOBJ); + entry.push_back(Pair("txid", coin.second.txid.GetHex())); + entry.push_back(Pair("nHeight", coin.second.nHeight)); + + CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); + serialized << coin.first; + CScript script; + // opcode is inserted as 1 byte according to file script/script.h + script << OP_SPARKMINT; + script.insert(script.end(), serialized.begin(), serialized.end()); + entry.push_back(Pair("scriptPubKey", HexStr(script.begin(), script.end()))); + entry.push_back(Pair("amount", ValueFromAmount(coin.second.v))); + results.push_back(entry); + } + + return results; +} + +UniValue listsparkmints(const JSONRPCRequest& request) { + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 0) { + throw std::runtime_error( + "listsparkmints \n" + "Returns array of mint coins\n" + "Results are an array of Objects, each of which has:\n" + "{txid, nHeight, nId, isUsed, scriptPubKey, amount}"); + } + + if (pwallet->IsLocked()) { + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, + "Error: Please enter the wallet passphrase with walletpassphrase first."); + } + + EnsureSparkWalletIsAvailable(); + + UniValue results(UniValue::VARR);; + assert(pwallet != NULL); + + std::vector coins = pwallet->sparkWallet->ListSparkMints(); + LogPrintf("coins.size()=%s\n", coins.size()); + BOOST_FOREACH(const auto& coin, coins) + { + UniValue entry(UniValue::VOBJ); + entry.push_back(Pair("txid", coin.txid.GetHex())); + entry.push_back(Pair("nHeight", coin.nHeight)); + entry.push_back(Pair("nId", coin.nId)); + entry.push_back(Pair("isUsed", coin.isUsed)); + + + CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); + serialized << pwallet->sparkWallet->getCoinFromMeta(coin); + CScript script; + // opcode is inserted as 1 byte according to file script/script.h + script << OP_SPARKMINT; + script.insert(script.end(), serialized.begin(), serialized.end()); + entry.push_back(Pair("scriptPubKey", HexStr(script.begin(), script.end()))); + entry.push_back(Pair("amount", ValueFromAmount(coin.v))); + results.push_back(entry); + } + + return results; +} + +UniValue getsparkdefaultaddress(const JSONRPCRequest& request) { + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 0) { + throw std::runtime_error( + "getsparkdefaultaddress \n" + "Returns array of unspent transaction outputs\n" + "Results are an array of Objects, each of which has:\n" + "{txid, nHeight, nId, isUsed, scriptPubKey, amount}"); + } + + EnsureSparkWalletIsAvailable(); + + assert(pwallet != NULL); + + spark::Address address = pwallet->sparkWallet->getDefaultAddress(); + unsigned char network = spark::GetNetworkType(); + UniValue result(UniValue::VARR); + result.push_back(address.encode(network)); + return result; +} + + UniValue mint(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -4898,6 +5026,11 @@ static const CRPCCommand commands[] = { "wallet", "listsigmaspends", &listsigmaspends, false }, { "wallet", "listlelantusjoinsplits", &listlelantusjoinsplits, false }, + //spark + { "wallet", "listunspentsparkmints", &listunspentsparkmints, false }, + { "wallet", "listsparkmints", &listsparkmints, false }, + { "wallet", "getsparkdefaultaddress", &getsparkdefaultaddress, false }, + //bip47 { "bip47", "createrapaddress", &createrapaddress, true }, { "bip47", "setupchannel", &setupchannel, true }, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0980cb5622..14e7f33e89 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2815,9 +2815,7 @@ std::list> CWallet::GetAvailableSparkCoin EnsureSparkWalletAvailable(); LOCK2(cs_main, cs_wallet); - CWalletDB walletdb(strWalletFile); - - return sparkWallet->GetAvailableSparkCoins(walletdb, coinControl); + return sparkWallet->GetAvailableSparkCoins(coinControl); } // coinsIn has to be sorted in descending order. From 0d0d9931bb165922129662b88238c4ca29bba2b7 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 30 Oct 2022 02:22:33 +0400 Subject: [PATCH 057/197] Adding more rpc calls --- src/chain.h | 15 +- src/libspark/keys.cpp | 5 + src/libspark/keys.h | 3 +- src/rpc/client.cpp | 5 + src/spark/primitives.h | 4 +- src/spark/sparkwallet.cpp | 67 ++++++ src/spark/sparkwallet.h | 8 + src/wallet/rpcwallet.cpp | 444 ++++++++++++++++++++++++++++++++++++-- 8 files changed, 526 insertions(+), 25 deletions(-) diff --git a/src/chain.h b/src/chain.h index e9d6bdab89..80e4b10a1f 100644 --- a/src/chain.h +++ b/src/chain.h @@ -540,17 +540,18 @@ class CDiskBlockIndex : public CBlockIndex READWRITE(lelantusMintedPubCoins); READWRITE(lelantusSpentSerials); - if (!(s.GetType() & SER_GETHASH) - && nHeight >= params.nSparkStartBlock) { - READWRITE(sparkMintedCoins); - READWRITE(sparkSetHash); - READWRITE(spentLTags); - } - if (nHeight >= params.nLelantusFixesStartBlock) READWRITE(anonymitySetHash); } + if (!(s.GetType() & SER_GETHASH) + && nHeight >= params.nSparkStartBlock) { + READWRITE(sparkMintedCoins); + READWRITE(sparkSetHash); + READWRITE(spentLTags); + } + + if (!(s.GetType() & SER_GETHASH) && nHeight >= params.nEvoSporkStartBlock) { if (nHeight < params.nEvoSporkStopBlock && // Workaround for late rollout of version 0.14.9.3 in which nEvoSporkStopBlock was extended diff --git a/src/libspark/keys.cpp b/src/libspark/keys.cpp index 165e1a463a..791c05a2bf 100644 --- a/src/libspark/keys.cpp +++ b/src/libspark/keys.cpp @@ -140,6 +140,11 @@ uint64_t IncomingViewKey::get_diversifier(const std::vector& d) c } Address::Address() {} + +Address::Address(const Params* params) { + this->params = params; +} + Address::Address(const IncomingViewKey& incoming_view_key, const uint64_t i) { // Encrypt the diversifier std::vector key = SparkUtils::kdf_diversifier(incoming_view_key.get_s1()); diff --git a/src/libspark/keys.h b/src/libspark/keys.h index ebb81214f1..8234e7eed4 100644 --- a/src/libspark/keys.h +++ b/src/libspark/keys.h @@ -74,7 +74,8 @@ class IncomingViewKey { class Address { public: - Address(); + Address(); + Address(const Params* params); Address(const IncomingViewKey& incoming_view_key, const uint64_t i); const Params* get_params() const; const std::vector& get_d() const; diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 0908d28ace..d627dc9f97 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -194,6 +194,11 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getusedcoinserials", 0 }, { "getlatestcoinids", 0 }, + //Lelantus + { "mintspark", 0 }, + { "spendspark", 0 }, + { "spendspark", 1 }, + /* Elysium - data retrieval calls */ { "elysium_gettradehistoryforaddress", 1 }, { "elysium_gettradehistoryforaddress", 2 }, diff --git a/src/spark/primitives.h b/src/spark/primitives.h index 703e356371..669730c672 100644 --- a/src/spark/primitives.h +++ b/src/spark/primitives.h @@ -22,12 +22,12 @@ struct CSparkMintMeta uint256 GetNonceHash() const; - bool operator==(const CSparkMintMeta& other) + bool operator==(const CSparkMintMeta& other) const { return this->k == other.k; } - bool operator!=(const CSparkMintMeta& other) + bool operator!=(const CSparkMintMeta& other) const { return this->k != other.k; } diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index c6b5b5ea95..374fb36093 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -81,6 +81,7 @@ CAmount CSparkWallet::getAvailableBalance() { result += mint.v; } + return result; } @@ -102,6 +103,52 @@ CAmount CSparkWallet::getUnconfirmedBalance() { return result; } +CAmount CSparkWallet::getAddressFullBalance(const spark::Address& address) { + return getAddressAvailableBalance(address) + getAddressUnconfirmedBalance(address); +} + +CAmount CSparkWallet::getAddressAvailableBalance(const spark::Address& address) { + CAmount result = 0; + for (auto& it : coinMeta) { + CSparkMintMeta mint = it.second; + + if (mint.isUsed) + continue; + + // Not confirmed + if (mint.nHeight < 1) + continue; + + if (address.get_d() != mint.d) + continue; + + result += mint.v; + } + + return result; +} + +CAmount CSparkWallet::getAddressUnconfirmedBalance(const spark::Address& address) { + CAmount result = 0; + for (auto& it : coinMeta) { + CSparkMintMeta mint = it.second; + + if (mint.isUsed) + continue; + + // Not confirmed + if (mint.nHeight > 1) + continue; + + if (address.get_d() != mint.d) + continue; + + result += mint.v; + } + + return result; +} + spark::Address CSparkWallet::generateNextAddress() { lastDiversifier++; return spark::Address(viewKey, lastDiversifier); @@ -191,6 +238,18 @@ std::vector CSparkWallet::ListSparkMints(bool fUnusedOnly, bool return setMints; } +std::list CSparkWallet::ListSparkSpends() const { + std::list result; + CWalletDB walletdb(strWalletFile); + walletdb.ListSparkSpends(result); + return result; +} + +std::unordered_map CSparkWallet::getMintMap() const { + return coinMeta; +} + + spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta) const { const spark::Params* params = spark::Params::get_default(); spark::Address address(viewKey, meta.i); @@ -222,6 +281,14 @@ void CSparkWallet::addOrUpdateMint(const CSparkMintMeta& mint, const uint256& lT walletdb.WriteSparkMint(lTagHash, mint); } +void CSparkWallet::updateMint(const CSparkMintMeta& mint, CWalletDB& walletdb) { + for (const auto& coin : coinMeta) { + if (mint == coin.second) { + addOrUpdateMint(mint, coin.first, walletdb); + } + } +} + void CSparkWallet::updateMintInMemory(const CSparkMintMeta& mint) { for (auto& itr : coinMeta) { if (itr.second == mint) { diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 1426fd4e0b..0c36dcd57a 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -43,6 +43,8 @@ class CSparkWallet { spark::Address getAddress(const int32_t& i); // list spark mint, mint metadata in memory and in db should be the same at this moment, so get from memory std::vector ListSparkMints(bool fUnusedOnly = false, bool fMatureOnly = false) const; + std::list ListSparkSpends() const; + std::unordered_map getMintMap() const; // generate spark Coin from meta data spark::Coin getCoinFromMeta(const CSparkMintMeta& meta) const; @@ -51,12 +53,18 @@ class CSparkWallet { CAmount getAvailableBalance(); CAmount getUnconfirmedBalance(); + CAmount getAddressFullBalance(const spark::Address& address); + CAmount getAddressAvailableBalance(const spark::Address& address); + CAmount getAddressUnconfirmedBalance(const spark::Address& address); + // function to be used for zap wallet void clearAllMints(CWalletDB& walletdb); // erase mint meta data from memory and from db void eraseMint(const uint256& hash, CWalletDB& walletdb); // add mint meta data to memory and to db void addOrUpdateMint(const CSparkMintMeta& mint, const uint256& lTagHash, CWalletDB& walletdb); + void updateMint(const CSparkMintMeta& mint, CWalletDB& walletdb); + void updateMintInMemory(const CSparkMintMeta& mint); // get mint meta from linking tag hash CSparkMintMeta getMintMeta(const uint256& hash); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 6ff398f90f..366566f860 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3202,6 +3202,7 @@ UniValue listunspentsparkmints(const JSONRPCRequest& request) { UniValue entry(UniValue::VOBJ); entry.push_back(Pair("txid", coin.second.txid.GetHex())); entry.push_back(Pair("nHeight", coin.second.nHeight)); + entry.push_back(Pair("memo", coin.second.memo)); CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); serialized << coin.first; @@ -3228,7 +3229,7 @@ UniValue listsparkmints(const JSONRPCRequest& request) { "listsparkmints \n" "Returns array of mint coins\n" "Results are an array of Objects, each of which has:\n" - "{txid, nHeight, nId, isUsed, scriptPubKey, amount}"); + "{txid, nHeight, nId, isUsed, lTagHash, scriptPubKey, amount}"); } if (pwallet->IsLocked()) { @@ -3241,25 +3242,26 @@ UniValue listsparkmints(const JSONRPCRequest& request) { UniValue results(UniValue::VARR);; assert(pwallet != NULL); - std::vector coins = pwallet->sparkWallet->ListSparkMints(); + std::unordered_map coins = pwallet->sparkWallet->getMintMap(); LogPrintf("coins.size()=%s\n", coins.size()); BOOST_FOREACH(const auto& coin, coins) { UniValue entry(UniValue::VOBJ); - entry.push_back(Pair("txid", coin.txid.GetHex())); - entry.push_back(Pair("nHeight", coin.nHeight)); - entry.push_back(Pair("nId", coin.nId)); - entry.push_back(Pair("isUsed", coin.isUsed)); - + entry.push_back(Pair("txid", coin.second.txid.GetHex())); + entry.push_back(Pair("nHeight", coin.second.nHeight)); + entry.push_back(Pair("nId", coin.second.nId)); + entry.push_back(Pair("isUsed", coin.second.isUsed)); + entry.push_back(Pair("lTagHash", coin.first.GetHex())); + entry.push_back(Pair("memo", coin.second.memo)); CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); - serialized << pwallet->sparkWallet->getCoinFromMeta(coin); + serialized << pwallet->sparkWallet->getCoinFromMeta(coin.second); CScript script; // opcode is inserted as 1 byte according to file script/script.h script << OP_SPARKMINT; script.insert(script.end(), serialized.begin(), serialized.end()); entry.push_back(Pair("scriptPubKey", HexStr(script.begin(), script.end()))); - entry.push_back(Pair("amount", ValueFromAmount(coin.v))); + entry.push_back(Pair("amount", ValueFromAmount(coin.second.v))); results.push_back(entry); } @@ -3275,9 +3277,8 @@ UniValue getsparkdefaultaddress(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 0) { throw std::runtime_error( "getsparkdefaultaddress \n" - "Returns array of unspent transaction outputs\n" - "Results are an array of Objects, each of which has:\n" - "{txid, nHeight, nId, isUsed, scriptPubKey, amount}"); + "Returns first spark address in encoded form\n" + "Result is a string object."); } EnsureSparkWalletIsAvailable(); @@ -3291,6 +3292,409 @@ UniValue getsparkdefaultaddress(const JSONRPCRequest& request) { return result; } +UniValue getnewsparkaddress(const JSONRPCRequest& request) { + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 0) { + throw std::runtime_error( + "getnewsparkaddress \n" + "Returns new spark address in encoded form\n" + "Result is a string object."); + } + + EnsureSparkWalletIsAvailable(); + + assert(pwallet != NULL); + + spark::Address address = pwallet->sparkWallet->generateNewAddress(); + unsigned char network = spark::GetNetworkType(); + UniValue result(UniValue::VARR); + result.push_back(address.encode(network)); + return result; +} + +UniValue getallsparkaddresses(const JSONRPCRequest& request) { + CWallet *const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 0) { + throw std::runtime_error( + "getallsparkaddresses \n" + "Returns array spark address in encoded form\n" + "Results are an array of Objects, each of which has:\n" + "{diversifier and address}"); + } + + EnsureSparkWalletIsAvailable(); + + assert(pwallet != NULL); + + std::unordered_map addresses = pwallet->sparkWallet->getAllAddresses(); + unsigned char network = spark::GetNetworkType(); + UniValue results(UniValue::VOBJ); + for (auto &itr : addresses) { + + results.push_back(Pair(std::to_string(itr.first), itr.second.encode(network))); + } + return results; +} + + +UniValue listsparkspends(const JSONRPCRequest& request) { + CWallet *const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 0) { + throw std::runtime_error( + "listsparkspends \n" + "Returns array spark address in encoded form\n" + "Results are an array of Objects, each of which has:\n" + "{txid, lTagHash, lTag and amount}"); + } + + EnsureSparkWalletIsAvailable(); + assert(pwallet != NULL); + + std::list spends = pwallet->sparkWallet->ListSparkSpends(); + + UniValue results(UniValue::VARR); + for (auto &itr : spends) { + UniValue entry(UniValue::VOBJ); + entry.push_back(Pair("txid", itr.hashTx.GetHex())); + entry.push_back(Pair("lTagHash", itr.lTagHash.GetHex())); + entry.push_back(Pair("lTag", itr.lTag.GetHex())); + entry.push_back(Pair("amount", itr.lTag.GetHex())); + results.push_back(entry); + } + return results; +} + +UniValue getsparkbalance(const JSONRPCRequest& request) { + CWallet *const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 0) { + throw std::runtime_error( + "getsparkbalance \n" + "Returns spark balance\n" + "Results are an array three Objects:\n" + "{availableBalance, unconfirmedBalance, and fullBalance}"); + } + + EnsureSparkWalletIsAvailable(); + assert(pwallet != NULL); + UniValue results(UniValue::VOBJ); + results.push_back(Pair("availableBalance: ",pwallet->sparkWallet->getAvailableBalance())); + results.push_back(Pair("unconfirmedBalance: ",pwallet->sparkWallet->getUnconfirmedBalance())); + results.push_back(Pair("fullBalance: ",pwallet->sparkWallet->getFullBalance())); + + return results; +} + +UniValue getsparkaddressbalance(const JSONRPCRequest& request) { + CWallet *const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) { + throw std::runtime_error( + "getsparkaddressbalance \n" + "Returns spark address balance\n" + "Results are an array three Objects:\n" + "{availableBalance, unconfirmedBalance, and fullBalance}"); + } + + EnsureSparkWalletIsAvailable(); + assert(pwallet != NULL); + std::string strAddress = request.params[0].get_str(); + const spark::Params* params = spark::Params::get_default(); + unsigned char network = spark::GetNetworkType(); + spark::Address address(params); + unsigned char coinNetwork; + try { + coinNetwork = address.decode(strAddress); + } catch (...) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Spark address: ")+strAddress); + } + + if (coinNetwork != network) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid address, wrong network type: ")+strAddress); + + + UniValue results(UniValue::VOBJ); + results.push_back(Pair("availableBalance: ",pwallet->sparkWallet->getAddressAvailableBalance(address))); + results.push_back(Pair("unconfirmedBalance: ",pwallet->sparkWallet->getAddressUnconfirmedBalance(address))); + results.push_back(Pair("fullBalance: ",pwallet->sparkWallet->getAddressFullBalance(address))); + + return results; +} + +UniValue resetsparkmints(const JSONRPCRequest& request) { + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 0) + throw std::runtime_error( + "resetsparkmints" + + HelpRequiringPassphrase(pwallet)); + + EnsureSparkWalletIsAvailable(); + + std::vector listMints; + CWalletDB walletdb(pwallet->strWalletFile); + listMints = pwallet->sparkWallet->ListSparkMints(); + + BOOST_FOREACH(CSparkMintMeta& mint, listMints) { + mint.isUsed = false; + mint.nHeight = -1; + pwallet->sparkWallet->updateMint(mint, walletdb); + } + + return NullUniValue; +} + +UniValue setsparkmintstatus(const JSONRPCRequest& request) { + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 2) + throw std::runtime_error( + "setsparkmintstatus \"lTagHash\" (true/false)\n" + "Set mintIsUsed status to True or False\n" + "Results are an array of one or no Objects, each of which has:\n" + "{id, IsUsed, denomination, value, serialNumber, nHeight, randomness}"); + + EnsureSparkWalletIsAvailable(); + + uint256 lTagHash; + lTagHash.SetHex(request.params[0].get_str()); + + bool fStatus = true; + fStatus = request.params[1].get_bool(); + + EnsureWalletIsUnlocked(pwallet); + CWalletDB walletdb(pwallet->strWalletFile); + CSparkMintMeta coinMeta = pwallet->sparkWallet->getMintMeta(lTagHash); + + if (coinMeta != CSparkMintMeta()) { + coinMeta.isUsed = fStatus; + pwallet->sparkWallet->updateMint(coinMeta, walletdb); + } + + return NullUniValue; +} + +UniValue mintspark(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "mintspark {\"address\":amount,memo...}\n" + + HelpRequiringPassphrase(pwallet) + "\n" + "\nArguments:\n" + " {\n" + " \"address\":amount (numeric or string) The Spark address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + + " is the value\n" + " ,...\n" + " }\n" + "\nResult:\n" + "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" + " the number of addresses.\n" + "\nExamples:\n" + "\nSend two amounts to two different spark addresses:\n" + + HelpExampleCli("mintspark", "\"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":[0.01, \\\"\\\"],\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":[0.01, \\\"\\\"]}\"") + + "\nSend two amounts to two different spark addresses setting memo:\n" + + HelpExampleRpc("mintspark", "\"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":[0.01, \\\"\\\"],\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":[0.01, \\\"memo\\\"]}\"") + ); + EnsureWalletIsUnlocked(pwallet); + EnsureSparkWalletIsAvailable(); + + // Ensure spark mints is already accepted by network so users will not lost their coins + // due to other nodes will treat it as garbage data. + if (!spark::IsSparkAllowed()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Spark is not activated yet"); + } + + UniValue sendTo = request.params[0].get_obj(); + + std::vector keys = sendTo.getKeys(); + const spark::Params* params = spark::Params::get_default(); + unsigned char network = spark::GetNetworkType(); + + std::vector outputs; + BOOST_FOREACH(const std::string& name_, keys) + { + spark::Address address(params); + unsigned char coinNetwork; + try { + coinNetwork = address.decode(name_); + } catch (...) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Spark address: ")+name_); + } + + if (coinNetwork != network) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid address, wrong network type: ")+name_); + UniValue amountAndMemo = sendTo[name_]; + CAmount nAmount = AmountFromValue(amountAndMemo[0]); + std::string memo = amountAndMemo[1].get_str(); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); + LogPrintf("rpcWallet.mintSpark() nAmount = %d \n", nAmount); + + spark::MintedCoinData data; + data.address = address; + data.memo = memo; + data.v = nAmount; + outputs.push_back(data); + } + + std::vector> wtxAndFee; + std::string strError = pwallet->MintAndStoreSpark(outputs, wtxAndFee); + if (strError != "") + throw JSONRPCError(RPC_WALLET_ERROR, strError); + + UniValue result(UniValue::VARR); + for(const auto& wtx : wtxAndFee) { + result.push_back(wtx.first.GetHash().GetHex()); + } + + return result; +} + +UniValue spendspark(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 2) + throw std::runtime_error( + "spendspark {\"address\":amount,subtractfee...} {\"address\":amount,memo,subtractfee...}\n" + + HelpRequiringPassphrase(pwallet) + "\n" + "\nArguments:\n" + "1. \"transparent\"\n" + " {\n" + " \"address\":amount (numeric or string), subtractfee (bool) The Firo address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value\n" + " ,...\n" + " }\n" + "2. \"private\"\n" + " {\n" + " \"address\":amount (numeric or string), memo (string, not required), subtractfee (bool) The Spark address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value\n" + " ,...\n" + " }\n" + "\nResult:\n" + "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" + " the number of addresses.\n" + "\nExamples:\n" + "\nSend two amounts to two different transparent addresses:\n" + + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":[0.02, false]}\" \"{}\"") + + "\nSend two amounts to two different transparent addresses and two different private addresses:\n" + + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":[0.02, false]}\" \"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":[0.01, \\\"\\\", false],\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":[0.01, \\\"\\\", false]}\"") + + "\nSend two amounts to two different transparent addresses and two different private addresses:\n" + + HelpExampleRpc("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":[0.02, false]}\" \"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":[0.01, \\\"\\\", false],\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":[0.01, \\\"\\\", false]}\"") + ); + + EnsureWalletIsUnlocked(pwallet); + EnsureSparkWalletIsAvailable(); + + // Ensure spark mints is already accepted by network so users will not lost their coins + // due to other nodes will treat it as garbage data. + if (!spark::IsSparkAllowed()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Spark is not activated yet"); + } + + std::vector recipients; + UniValue sendTo = request.params[0].get_obj(); + std::vector keys = sendTo.getKeys(); + std::set setAddress; + BOOST_FOREACH(const std::string& name_, keys) + { + CBitcoinAddress address(name_); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Firo address: ")+name_); + + if (setAddress.count(address)) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ")+name_); + setAddress.insert(address); + + CScript scriptPubKey = GetScriptForDestination(address.Get()); + CAmount nAmount = AmountFromValue(sendTo[name_][0]); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); + + bool fSubtractFeeFromAmount = sendTo[name_][1].get_bool(); + + CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; + recipients.push_back(recipient); + } + + UniValue privSendTo = request.params[1].get_obj(); + keys = privSendTo.getKeys(); + const spark::Params* params = spark::Params::get_default(); + unsigned char network = spark::GetNetworkType(); + + std::vector> privateRecipients; + BOOST_FOREACH(const std::string& name_, keys) + { + spark::Address address(params); + unsigned char coinNetwork; + try { + coinNetwork = address.decode(name_); + } catch (...) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Spark address: ")+name_); + } + + if (coinNetwork != network) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid address, wrong network type: ")+name_); + UniValue amountAndMemo = privSendTo[name_]; + CAmount nAmount = AmountFromValue(amountAndMemo[0]); + std::string memo = amountAndMemo[1].get_str(); + bool subtractFee = amountAndMemo[2].get_bool(); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); + LogPrintf("rpcWallet.mintSpark() nAmount = %d \n", nAmount); + + spark::OutputCoinData data; + data.address = address; + data.memo = memo; + data.v = nAmount; + privateRecipients.push_back(std::make_pair(data, subtractFee)); + } + + CAmount fee; + std::vector wtxs; + try { + wtxs = pwallet->SpendAndStoreSpark(recipients, privateRecipients, fee); + } catch (...) { + throw JSONRPCError(RPC_WALLET_ERROR, "Spark spend creation failed."); + } + + UniValue result(UniValue::VARR); + for(const auto& wtx : wtxs) { + result.push_back(wtx.GetHash().GetHex()); + } + + return result; +} UniValue mint(const JSONRPCRequest& request) { @@ -5027,9 +5431,19 @@ static const CRPCCommand commands[] = { "wallet", "listlelantusjoinsplits", &listlelantusjoinsplits, false }, //spark - { "wallet", "listunspentsparkmints", &listunspentsparkmints, false }, - { "wallet", "listsparkmints", &listsparkmints, false }, - { "wallet", "getsparkdefaultaddress", &getsparkdefaultaddress, false }, + { "wallet", "listunspentsparkmints", &listunspentsparkmints, false }, + { "wallet", "listsparkmints", &listsparkmints, false }, + { "wallet", "listsparkspends", &listsparkspends, false }, + { "wallet", "getsparkdefaultaddress", &getsparkdefaultaddress, false }, + { "wallet", "getallsparkaddresses", &getallsparkaddresses, false }, + { "wallet", "getnewsparkaddress", &getnewsparkaddress, false }, + { "wallet", "getsparkbalance", &getsparkbalance, false }, + { "wallet", "getsparkaddressbalance", &getsparkaddressbalance, false }, + { "wallet", "resetsparkmints", &resetsparkmints, false }, + { "wallet", "setsparkmintstatus", &setsparkmintstatus, false }, + { "wallet", "mintspark", &mintspark, false }, + { "wallet", "spendspark", &spendspark, false }, + //bip47 { "bip47", "createrapaddress", &createrapaddress, true }, From cf395e28b5a6545139c5adc0b96e5ed218bc0c53 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 31 Oct 2022 05:37:16 +0400 Subject: [PATCH 058/197] Failing unittests fixed --- src/chainparams.cpp | 8 +++---- src/sigma/test/serialize_test.cpp | 1 - src/test/evospork_tests.cpp | 32 ++++++++++++++-------------- src/test/lelantus_mintspend_test.cpp | 2 +- src/wallet/test/lelantus_tests.cpp | 4 ++-- 5 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 3ad61cd9b4..0dea9a1a63 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -1087,7 +1087,7 @@ class CRegTestParams : public CChainParams { consensus.llmqForInstantSend = Consensus::LLMQ_5_60; consensus.nInstantSendConfirmationsRequired = 2; consensus.nInstantSendKeepLock = 6; - consensus.nInstantSendBlockFilteringStartHeight = 800; + consensus.nInstantSendBlockFilteringStartHeight = 500; consensus.nMTPSwitchTime = INT_MAX; consensus.nMTPStartBlock = 0; @@ -1191,8 +1191,8 @@ class CRegTestParams : public CChainParams { // evo spork consensus.evoSporkKeyID = "TSpmHGzQT4KJrubWa4N2CRmpA7wKMMWDg4"; // private key is cW2YM2xaeCaebfpKguBahUAgEzLXgSserWRuD29kSyKHq1TTgwRQ - consensus.nEvoSporkStartBlock = 1000; - consensus.nEvoSporkStopBlock = 1500; + consensus.nEvoSporkStartBlock = 550; + consensus.nEvoSporkStopBlock = 950; consensus.nEvoSporkStopBlockExtensionVersion = 0; // reorg @@ -1210,7 +1210,7 @@ class CRegTestParams : public CChainParams { consensus.nMnemonicBlock = 0; // moving lelantus data to v3 payload - consensus.nLelantusV3PayloadStartBlock = 1000; + consensus.nLelantusV3PayloadStartBlock = 800; // ProgPow // this can be overridden with either -ppswitchtime or -ppswitchtimefromnow flags diff --git a/src/sigma/test/serialize_test.cpp b/src/sigma/test/serialize_test.cpp index 98a42e95d3..ff9069f160 100644 --- a/src/sigma/test/serialize_test.cpp +++ b/src/sigma/test/serialize_test.cpp @@ -25,7 +25,6 @@ BOOST_AUTO_TEST_CASE(group_element_invalid) std::string str = " F I R O T E S T S T R I N G "; std::vector buffer(str.begin(), str.end()); buffer.push_back(0); - std::cout<SetBroadcastTransactions(true); - for (int n=chainActive.Height(); n<1001; n++) + for (int n=chainActive.Height(); n<600; n++) GenerateBlock({}); auto utxos = BuildSimpleUtxoMap(coinbaseTxns); CMutableTransaction sporkTx1 = CreateSporkTx(utxos, coinbaseKey, { - {CSporkAction::sporkDisable, CSporkAction::featureLelantus, 0, 1010} + {CSporkAction::sporkDisable, CSporkAction::featureLelantus, 0, 775} }); CMutableTransaction sporkTx2 = CreateSporkTx(utxos, coinbaseKey, { - {CSporkAction::sporkDisable, CSporkAction::featureLelantus, 0, 1020} + {CSporkAction::sporkDisable, CSporkAction::featureLelantus, 0, 785} }); std::vector lelantusMints; @@ -246,7 +246,7 @@ BOOST_AUTO_TEST_CASE(mempool) // because there is active spork at the tip lelantus mint shouldn't get into the mempool BOOST_ASSERT(!CommitToMempool(lelantusMints[1])); - for (int n=chainActive.Height(); n<1010; n++) + for (int n=chainActive.Height(); n<775; n++) CreateAndProcessBlock({}, coinbaseKey); // spork expired, should accept now @@ -271,12 +271,12 @@ BOOST_AUTO_TEST_CASE(limit) int prevHeight; pwalletMain->SetBroadcastTransactions(true); - for (int n=chainActive.Height(); n<1001; n++) + for (int n=chainActive.Height(); n<644; n++) GenerateBlock({}); auto utxos = BuildSimpleUtxoMap(coinbaseTxns); CMutableTransaction sporkTx1 = CreateSporkTx(utxos, coinbaseKey, { - {CSporkAction::sporkLimit, CSporkAction::featureLelantusTransparentLimit, 100*COIN, 1030} + {CSporkAction::sporkLimit, CSporkAction::featureLelantusTransparentLimit, 100*COIN, 750} }); std::vector lelantusMints; @@ -344,7 +344,7 @@ BOOST_AUTO_TEST_CASE(limit) BOOST_ASSERT(chainActive.Height() == prevHeight); // skip to 1030 (spork expiration block) - for (int n=chainActive.Height(); n<1030; n++) + for (int n=chainActive.Height(); n<750; n++) GenerateBlock({}); // should be accepted into the mempool @@ -365,18 +365,18 @@ BOOST_AUTO_TEST_CASE(startstopblock) int prevHeight; pwalletMain->SetBroadcastTransactions(true); - for (int n=chainActive.Height(); n<700; n++) + for (int n=chainActive.Height(); n<510; n++) GenerateBlock({}); auto utxos = BuildSimpleUtxoMap(coinbaseTxns); CMutableTransaction sporkTx1 = CreateSporkTx(utxos, coinbaseKey, { - {CSporkAction::sporkDisable, CSporkAction::featureLelantus, 0, 1010} + {CSporkAction::sporkDisable, CSporkAction::featureLelantus, 0, 560} }); CMutableTransaction sporkTx2 = CreateSporkTx(utxos, coinbaseKey, { {CSporkAction::sporkDisable, CSporkAction::featureLelantus, 0, 0} }); CMutableTransaction sporkTx3 = CreateSporkTx(utxos, coinbaseKey, { - {CSporkAction::sporkDisable, CSporkAction::featureLelantus, 0, 2000} + {CSporkAction::sporkDisable, CSporkAction::featureLelantus, 0, 960} }); // spork can't be put into the mempool/mined yet @@ -385,7 +385,7 @@ BOOST_AUTO_TEST_CASE(startstopblock) CreateAndProcessBlock({sporkTx1}, coinbaseKey); BOOST_ASSERT(chainActive.Height() == prevHeight); - for (int n=chainActive.Height(); n<1001; n++) + for (int n=chainActive.Height(); n<551; n++) GenerateBlock({}); // now we can mine sporkTx1 @@ -419,7 +419,7 @@ BOOST_AUTO_TEST_CASE(startstopblock) BOOST_ASSERT(chainActive.Height() == prevHeight); // go to the end of the spork window and try again - for (int n=chainActive.Height(); n<1500; n++) + for (int n=chainActive.Height(); n<950; n++) GenerateBlock({}); // should work now diff --git a/src/test/lelantus_mintspend_test.cpp b/src/test/lelantus_mintspend_test.cpp index 727d169065..0757b38b3c 100644 --- a/src/test/lelantus_mintspend_test.cpp +++ b/src/test/lelantus_mintspend_test.cpp @@ -26,7 +26,7 @@ BOOST_FIXTURE_TEST_SUITE(lelantus_mintspend, LelantusTestingSetup) BOOST_AUTO_TEST_CASE(lelantus_mintspend_test) { - GenerateBlocks(1000); + GenerateBlocks(400); lelantus::CLelantusState *lelantusState = lelantus::CLelantusState::GetState(); diff --git a/src/wallet/test/lelantus_tests.cpp b/src/wallet/test/lelantus_tests.cpp index 6de4d2cfea..36d080072c 100644 --- a/src/wallet/test/lelantus_tests.cpp +++ b/src/wallet/test/lelantus_tests.cpp @@ -36,7 +36,7 @@ BOOST_AUTO_TEST_CASE(mint_and_store_lelantus) fRequireStandard = true; // to verify mainnet can accept lelantus mint pwalletMain->SetBroadcastTransactions(true); - GenerateBlocks(910); + GenerateBlocks(450); auto amount = 1 * COIN; std::vector> wtxAndFee; @@ -253,7 +253,7 @@ BOOST_AUTO_TEST_CASE(spend) { fRequireStandard = true; // to verify mainnet can accept lelantus mint pwalletMain->SetBroadcastTransactions(true); - GenerateBlocks(910); + GenerateBlocks(450); std::vector> wtxAndFee; std::vector mints; From e8c4da147ecee14d75ac58caef3d9edc11cb66a9 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 3 Nov 2022 14:41:18 +0400 Subject: [PATCH 059/197] Devnet Spark HF block --- src/chainparams.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 0dea9a1a63..cc984222a4 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -945,8 +945,8 @@ class CDevNetParams : public CChainParams { consensus.nLelantusStartBlock = 1; consensus.nLelantusFixesStartBlock = 1; - consensus.nSparkStartBlock = 1000; - consensus.nLelantusGracefulPeriod = 1500; + consensus.nSparkStartBlock = 2000; + consensus.nLelantusGracefulPeriod = 2500; consensus.nMaxSigmaInputPerBlock = ZC_SIGMA_INPUT_LIMIT_PER_BLOCK; consensus.nMaxValueSigmaSpendPerBlock = ZC_SIGMA_VALUE_SPEND_LIMIT_PER_BLOCK; From afca6ca8243beedfd609f98df849ad9226f74cf0 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 3 Nov 2022 14:24:59 +0400 Subject: [PATCH 060/197] Version bump --- configure.ac | 4 ++-- src/clientversion.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index a3c603e29a..80a12538e0 100644 --- a/configure.ac +++ b/configure.ac @@ -2,8 +2,8 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N) AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 14) -define(_CLIENT_VERSION_REVISION, 11) -define(_CLIENT_VERSION_BUILD, 1) +define(_CLIENT_VERSION_REVISION, 12) +define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2022) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/src/clientversion.h b/src/clientversion.h index adce3b6785..2c93d90ae3 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -16,8 +16,8 @@ //! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it #define CLIENT_VERSION_MAJOR 0 #define CLIENT_VERSION_MINOR 14 -#define CLIENT_VERSION_REVISION 11 -#define CLIENT_VERSION_BUILD 1 +#define CLIENT_VERSION_REVISION 12 +#define CLIENT_VERSION_BUILD 0 //! Set to true for release, false for prerelease or test build #define CLIENT_VERSION_IS_RELEASE true From 776544c84d41ac58a4e26498f5ff35ebb9501871 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 21 Nov 2022 12:58:17 +0400 Subject: [PATCH 061/197] LelantusToSpark function implemented --- src/wallet/rpcwallet.cpp | 29 +++++++++++++++++- src/wallet/wallet.cpp | 64 ++++++++++++++++++++++++++++++++++++++-- src/wallet/wallet.h | 4 ++- 3 files changed, 93 insertions(+), 4 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 366566f860..cd6dedc38e 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3696,6 +3696,33 @@ UniValue spendspark(const JSONRPCRequest& request) return result; } +UniValue lelantusToSpark(const JSONRPCRequest& request) { + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 0) { + throw std::runtime_error( + "lelantusToSpark \n" + "Takes all your lelantus mints, spends all to transparent layer, takes all that UTX's and mints to Spark"); + } + + EnsureSparkWalletIsAvailable(); + + assert(pwallet != NULL); + std::string strFailReason; + bool failed = false; + try { + failed = pwallet->LelantusToSpark(strFailReason); + } catch (...) { + throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus to Spark failed."); + } + + if (failed || strFailReason != "") + throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus to Spark failed."); +} + UniValue mint(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -5443,7 +5470,7 @@ static const CRPCCommand commands[] = { "wallet", "setsparkmintstatus", &setsparkmintstatus, false }, { "wallet", "mintspark", &mintspark, false }, { "wallet", "spendspark", &spendspark, false }, - + { "wallet", "lelantusToSpark", &lelantusToSpark, false }, //bip47 { "bip47", "createrapaddress", &createrapaddress, true }, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 14e7f33e89..8218a9b4e4 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5478,13 +5478,13 @@ bool CWallet::CommitSigmaTransaction(CWalletTx& wtxNew, std::vector return true; } -std::vector CWallet::JoinSplitLelantus(const std::vector& recipients, const std::vector& newMints, CWalletTx& result) { +std::vector CWallet::JoinSplitLelantus(const std::vector& recipients, const std::vector& newMints, CWalletTx& result, const CCoinControl *coinControl) { // create transaction std::vector spendCoins; //spends std::vector sigmaSpendCoins; std::vector mintCoins; // new mints CAmount fee; - result = CreateLelantusJoinSplitTransaction(recipients, fee, newMints, spendCoins, sigmaSpendCoins, mintCoins); + result = CreateLelantusJoinSplitTransaction(recipients, fee, newMints, spendCoins, sigmaSpendCoins, mintCoins, coinControl); CommitLelantusTransaction(result, spendCoins, sigmaSpendCoins, mintCoins); @@ -5565,6 +5565,66 @@ std::vector CWallet::SpendAndStoreSpark( return result; } +bool CWallet::LelantusToSpark(std::string& strFailReason) { + std::list coins = GetAvailableLelantusCoins(); + CScript scriptChange; + { + // Reserve a new key pair from key pool + CPubKey vchPubKey; + bool ret; + ret = CReserveKey(this).GetReservedKey(vchPubKey); + if (!ret) + { + strFailReason = _("Keypool ran out, please call keypoolrefill first"); + return false; + } + + scriptChange = GetScriptForDestination(vchPubKey.GetID()); + } + + while (coins.size() > 0) { + bool addMoreCoins = true; + std::size_t selectedNum = 0; + CCoinControl coinControl; + CAmount spendValue = 0; + while (true) { + auto coin = coins.begin(); + COutPoint outPoint; + lelantus::GetOutPoint(outPoint, coin->value); + coinControl.Select(outPoint); + spendValue += coin->amount; + selectedNum ++; + coins.erase(coin); + if (!coins.size()) + break; + + if ((spendValue + coins.begin()->amount) > Params().GetConsensus().nMaxValueLelantusSpendPerTransaction) + break; + + if (selectedNum == Params().GetConsensus().nMaxLelantusInputPerTransaction) + break; + } + CRecipient recipient = {scriptChange, spendValue, true}; + + CWalletTx result; + JoinSplitLelantus({recipient}, {}, result, &coinControl); + coinControl.UnSelectAll(); + + uint32_t i = 0; + for (; i < result.tx->vout.size(); ++i) { + if (result.tx->vout[i].scriptPubKey == recipient.scriptPubKey) + break; + } + + COutPoint outPoint(result.GetHash(), i); + coinControl.Select(outPoint); + std::vector> wtxAndFee; + MintAndStoreSpark({}, wtxAndFee, true, true, &coinControl); + } + + return true; +} + std::pair CWallet::EstimateJoinSplitFee( CAmount required, bool subtractFeeFromAmount, diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 59b4109a1c..6d8b3e1c05 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1077,10 +1077,12 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface CAmount &fee, const CCoinControl *coinControl = NULL); + bool LelantusToSpark(std::string& strFailReason); + std::vector SpendSigma(const std::vector& recipients, CWalletTx& result); std::vector SpendSigma(const std::vector& recipients, CWalletTx& result, CAmount& fee); - std::vector JoinSplitLelantus(const std::vector& recipients, const std::vector& newMints, CWalletTx& result); + std::vector JoinSplitLelantus(const std::vector& recipients, const std::vector& newMints, CWalletTx& result, const CCoinControl *coinControl = NULL); std::pair EstimateJoinSplitFee(CAmount required, bool subtractFeeFromAmount, std::list sigmaCoins, std::list coins, const CCoinControl *coinControl); From fb1b5c99ffbb9a91bd8f927884eafcdb6afc7bcd Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 24 Nov 2022 06:19:42 +0400 Subject: [PATCH 062/197] Refactoring of file paths --- src/compat/byteswap.h | 2 +- src/compat/endian.h | 4 ++-- src/crypto/aes.h | 2 +- src/libspark/util.h | 6 +++--- src/support/allocators/zeroafterfree.h | 2 +- src/support/lockedpool.cpp | 6 +++--- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/compat/byteswap.h b/src/compat/byteswap.h index 3c5a5c0837..a9a96af252 100644 --- a/src/compat/byteswap.h +++ b/src/compat/byteswap.h @@ -6,7 +6,7 @@ #define BITCOIN_COMPAT_BYTESWAP_H #if defined(HAVE_CONFIG_H) -#include "config/bitcoin-config.h" +#include "../config/bitcoin-config.h" #endif #include diff --git a/src/compat/endian.h b/src/compat/endian.h index 79d6b2fdbb..c73470a5c0 100644 --- a/src/compat/endian.h +++ b/src/compat/endian.h @@ -6,12 +6,12 @@ #define BITCOIN_COMPAT_ENDIAN_H #if defined(HAVE_CONFIG_H) -#include "config/bitcoin-config.h" +#include "../config/bitcoin-config.h" #endif #include -#include "compat/byteswap.h" +#include "byteswap.h" #if defined(HAVE_ENDIAN_H) #include diff --git a/src/crypto/aes.h b/src/crypto/aes.h index e9f1b52e71..5a3d37863e 100644 --- a/src/crypto/aes.h +++ b/src/crypto/aes.h @@ -8,7 +8,7 @@ #define BITCOIN_CRYPTO_AES_H extern "C" { -#include "crypto/ctaes/ctaes.h" +#include "ctaes/ctaes.h" } static const int AES_BLOCKSIZE = 16; diff --git a/src/libspark/util.h b/src/libspark/util.h index d976a03dd2..e95c6ded6f 100644 --- a/src/libspark/util.h +++ b/src/libspark/util.h @@ -1,8 +1,8 @@ #ifndef FIRO_SPARK_UTIL_H #define FIRO_SPARK_UTIL_H -#include -#include -#include "../../crypto/aes.h" +#include "../secp256k1/include/Scalar.h" +#include "../secp256k1/include/GroupElement.h" +#include "../crypto/aes.h" #include "../streams.h" #include "../version.h" #include "../util.h" diff --git a/src/support/allocators/zeroafterfree.h b/src/support/allocators/zeroafterfree.h index 28a940ad1b..8ac7f890ed 100644 --- a/src/support/allocators/zeroafterfree.h +++ b/src/support/allocators/zeroafterfree.h @@ -6,7 +6,7 @@ #ifndef BITCOIN_SUPPORT_ALLOCATORS_ZEROAFTERFREE_H #define BITCOIN_SUPPORT_ALLOCATORS_ZEROAFTERFREE_H -#include "support/cleanse.h" +#include "../cleanse.h" #include #include diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index 01273c9791..3bc345b7ce 100644 --- a/src/support/lockedpool.cpp +++ b/src/support/lockedpool.cpp @@ -2,11 +2,11 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "support/lockedpool.h" -#include "support/cleanse.h" +#include "lockedpool.h" +#include "cleanse.h" #if defined(HAVE_CONFIG_H) -#include "config/bitcoin-config.h" +#include "../config/bitcoin-config.h" #endif #ifdef WIN32 From 4621aad0cf363763129d97e59a16bdb79e50968a Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 24 Nov 2022 06:20:31 +0400 Subject: [PATCH 063/197] lelantustospark rpc name refactored --- src/wallet/rpcwallet.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index cd6dedc38e..e7b3601699 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3696,7 +3696,7 @@ UniValue spendspark(const JSONRPCRequest& request) return result; } -UniValue lelantusToSpark(const JSONRPCRequest& request) { +UniValue lelantustospark(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; @@ -3704,7 +3704,7 @@ UniValue lelantusToSpark(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 0) { throw std::runtime_error( - "lelantusToSpark \n" + "lelantustospark \n" "Takes all your lelantus mints, spends all to transparent layer, takes all that UTX's and mints to Spark"); } @@ -3721,6 +3721,8 @@ UniValue lelantusToSpark(const JSONRPCRequest& request) { if (failed || strFailReason != "") throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus to Spark failed."); + + return NullUniValue; } UniValue mint(const JSONRPCRequest& request) @@ -5470,7 +5472,7 @@ static const CRPCCommand commands[] = { "wallet", "setsparkmintstatus", &setsparkmintstatus, false }, { "wallet", "mintspark", &mintspark, false }, { "wallet", "spendspark", &spendspark, false }, - { "wallet", "lelantusToSpark", &lelantusToSpark, false }, + { "wallet", "lelantustospark", &lelantustospark, false }, //bip47 { "bip47", "createrapaddress", &createrapaddress, true }, From 387c37e8d62a05099f226aea88d582c455c59ed9 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 24 Nov 2022 06:21:58 +0400 Subject: [PATCH 064/197] Adding check to stop lelantus on consensus level --- src/lelantus.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lelantus.cpp b/src/lelantus.cpp index 4c53bce46b..c2a3f7ee4a 100644 --- a/src/lelantus.cpp +++ b/src/lelantus.cpp @@ -89,7 +89,7 @@ bool IsLelantusAllowed() bool IsLelantusAllowed(int height) { - return height >= ::Params().GetConsensus().nLelantusStartBlock; + return height >= ::Params().GetConsensus().nLelantusStartBlock && height < ::Params().GetConsensus().nSparkStartBlock; } bool IsAvailableToMint(const CAmount& amount) @@ -727,6 +727,22 @@ bool CheckLelantusTransaction( realHeight = chainActive.Height(); } + // accept Lelantus mint tx into 5 more blocks, to allow mempool cleared + if (!isVerifyDB && realHeight >= (::Params().GetConsensus().nSparkStartBlock + 5)) { + if (tx.IsLelantusMint() && !tx.IsLelantusJoinSplit()) + return state.DoS(100, false, + REJECT_INVALID, + "Lelantus already is not available, start using Spark."); + } + + // accept lelantus spends until nLelantusGracefulPeriod passed, to allow migration of funds from lelantus to spark + if (!isVerifyDB && realHeight >= (::Params().GetConsensus().nLelantusGracefulPeriod)) { + if (tx.IsLelantusJoinSplit()) + return state.DoS(100, false, + REJECT_INVALID, + "Lelantus is fully disabled."); + } + bool const allowLelantus = (realHeight >= consensus.nLelantusStartBlock); if (!isVerifyDB && !isCheckWallet) { From bb3e6ac1ced18cd4c91230c005bc8f0895882fe3 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 27 Nov 2022 04:23:45 +0400 Subject: [PATCH 065/197] Fixing bug resulting batch verification fail --- src/libspark/spend_transaction.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index 063c28bbad..51fa81056a 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -366,6 +366,12 @@ bool SpendTransaction::verify( std::vector sizes; std::vector proofs; + std::size_t full_cover_set_size = cover_sets.at(cover_set_id).size(); + for (std::size_t i = 0; i < full_cover_set_size; i++) { + S.emplace_back(cover_sets.at(cover_set_id)[i].S); + V.emplace_back(cover_sets.at(cover_set_id)[i].C); + } + for (auto proof_index : proof_indexes) { const auto& tx = transactions[proof_index.first]; if (!cover_sets.count(cover_set_id)) @@ -375,12 +381,6 @@ bool SpendTransaction::verify( throw std::invalid_argument("Cover set size missing"); std::size_t this_cover_set_size = tx.cover_set_sizes.at(cover_set_id); - if (this_cover_set_size > S.size()) { - for (std::size_t i = S.size(); i < this_cover_set_size; i++) { - S.emplace_back(cover_sets.at(cover_set_id)[i].S); - V.emplace_back(cover_sets.at(cover_set_id)[i].C); - } - } // We always use the other elements S1.emplace_back(tx.S1[proof_index.second]); From e38693700ce02280341ec0d200c134b2a9fc7bdd Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 27 Nov 2022 18:04:40 +0400 Subject: [PATCH 066/197] Minor fixes on lelantustospark rpc --- src/wallet/rpcwallet.cpp | 14 +++++++------- src/wallet/wallet.cpp | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e7b3601699..491886ec3b 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3708,19 +3708,19 @@ UniValue lelantustospark(const JSONRPCRequest& request) { "Takes all your lelantus mints, spends all to transparent layer, takes all that UTX's and mints to Spark"); } + EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); - std::string strFailReason; - bool failed = false; + std::string strFailReason = ""; + bool passed = false; try { - failed = pwallet->LelantusToSpark(strFailReason); + passed = pwallet->LelantusToSpark(strFailReason); } catch (...) { - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus to Spark failed."); + throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus to Spark failed!"); } - - if (failed || strFailReason != "") - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus to Spark failed."); + if (!passed || strFailReason != "") + throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus to Spark failed. " + strFailReason); return NullUniValue; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 8218a9b4e4..800da2da27 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5619,7 +5619,7 @@ bool CWallet::LelantusToSpark(std::string& strFailReason) { COutPoint outPoint(result.GetHash(), i); coinControl.Select(outPoint); std::vector> wtxAndFee; - MintAndStoreSpark({}, wtxAndFee, true, true, &coinControl); + MintAndStoreSpark({}, wtxAndFee, true, false, &coinControl); } return true; From e3577780b3f624f76ffd399e48a35c55c21045d0 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 28 Nov 2022 02:45:10 +0400 Subject: [PATCH 067/197] Use new thread to recognize spark mints/spends --- src/spark/sparkwallet.cpp | 91 +++++++++++++++++++++++++-------------- src/spark/sparkwallet.h | 5 +++ 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 374fb36093..7130a433b3 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -9,6 +9,7 @@ #include "state.h" #include +#include const uint32_t DEFAULT_SPARK_NCOUNT = 1; @@ -51,7 +52,10 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { } // get the list of coin metadata from db - coinMeta = walletdb.ListSparkMints(); + { + LOCK(cs_spark_wallet); + coinMeta = walletdb.ListSparkMints(); + } } } @@ -69,6 +73,7 @@ CAmount CSparkWallet::getFullBalance() { CAmount CSparkWallet::getAvailableBalance() { CAmount result = 0; + LOCK(cs_spark_wallet); for (auto& it : coinMeta) { CSparkMintMeta mint = it.second; @@ -87,7 +92,7 @@ CAmount CSparkWallet::getAvailableBalance() { CAmount CSparkWallet::getUnconfirmedBalance() { CAmount result = 0; - + LOCK(cs_spark_wallet); for (auto& it : coinMeta) { CSparkMintMeta mint = it.second; if (mint.isUsed) @@ -109,6 +114,7 @@ CAmount CSparkWallet::getAddressFullBalance(const spark::Address& address) { CAmount CSparkWallet::getAddressAvailableBalance(const spark::Address& address) { CAmount result = 0; + LOCK(cs_spark_wallet); for (auto& it : coinMeta) { CSparkMintMeta mint = it.second; @@ -130,6 +136,7 @@ CAmount CSparkWallet::getAddressAvailableBalance(const spark::Address& address) CAmount CSparkWallet::getAddressUnconfirmedBalance(const spark::Address& address) { CAmount result = 0; + LOCK(cs_spark_wallet); for (auto& it : coinMeta) { CSparkMintMeta mint = it.second; @@ -222,7 +229,7 @@ spark::Address CSparkWallet::getAddress(const int32_t& i) { std::vector CSparkWallet::ListSparkMints(bool fUnusedOnly, bool fMatureOnly) const { std::vector setMints; - + LOCK(cs_spark_wallet); for (auto& it : coinMeta) { CSparkMintMeta mint = it.second; if (fUnusedOnly && mint.isUsed) @@ -246,6 +253,7 @@ std::list CSparkWallet::ListSparkSpends() const { } std::unordered_map CSparkWallet::getMintMap() const { + LOCK(cs_spark_wallet); return coinMeta; } @@ -257,7 +265,7 @@ spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta) const { } void CSparkWallet::clearAllMints(CWalletDB& walletdb) { - + LOCK(cs_spark_wallet); for (auto& itr : coinMeta) { walletdb.EraseSparkMint(itr.first); } @@ -268,11 +276,14 @@ void CSparkWallet::clearAllMints(CWalletDB& walletdb) { } void CSparkWallet::eraseMint(const uint256& hash, CWalletDB& walletdb) { + LOCK(cs_spark_wallet); walletdb.EraseSparkMint(hash); coinMeta.erase(hash); } void CSparkWallet::addOrUpdateMint(const CSparkMintMeta& mint, const uint256& lTagHash, CWalletDB& walletdb) { + LOCK(cs_spark_wallet); + if (mint.i > lastDiversifier) { lastDiversifier = mint.i; walletdb.writeDiversifier(lastDiversifier); @@ -282,6 +293,7 @@ void CSparkWallet::addOrUpdateMint(const CSparkMintMeta& mint, const uint256& lT } void CSparkWallet::updateMint(const CSparkMintMeta& mint, CWalletDB& walletdb) { + LOCK(cs_spark_wallet); for (const auto& coin : coinMeta) { if (mint == coin.second) { addOrUpdateMint(mint, coin.first, walletdb); @@ -290,6 +302,7 @@ void CSparkWallet::updateMint(const CSparkMintMeta& mint, CWalletDB& walletdb) { } void CSparkWallet::updateMintInMemory(const CSparkMintMeta& mint) { + LOCK(cs_spark_wallet); for (auto& itr : coinMeta) { if (itr.second == mint) { coinMeta[itr.first] = mint; @@ -299,12 +312,14 @@ void CSparkWallet::updateMintInMemory(const CSparkMintMeta& mint) { } CSparkMintMeta CSparkWallet::getMintMeta(const uint256& hash) { + LOCK(cs_spark_wallet); if (coinMeta.count(hash)) return coinMeta[hash]; return CSparkMintMeta(); } CSparkMintMeta CSparkWallet::getMintMeta(const secp_primitives::Scalar& nonce) { + LOCK(cs_spark_wallet); for (const auto& meta : coinMeta) { if (meta.second.k == nonce) return meta.second; @@ -340,34 +355,39 @@ void CSparkWallet::UpdateSpendState(const GroupElement& lTag, const uint256& lTa } void CSparkWallet::UpdateSpendStateFromMempool(const std::vector& lTags, const uint256& txHash, bool fUpdateMint) { - for (const auto& lTag : lTags) { - uint256 lTagHash = primitives::GetLTagHash(lTag); - if (coinMeta.count(lTagHash)) { - UpdateSpendState(lTag, lTagHash, txHash, fUpdateMint); + std::thread([=]() { + LOCK(cs_spark_wallet); + for (const auto& lTag : lTags) { + uint256 lTagHash = primitives::GetLTagHash(lTag); + if (coinMeta.count(lTagHash)) { + UpdateSpendState(lTag, lTagHash, txHash, fUpdateMint); + } } - } + }).detach(); } void CSparkWallet::UpdateSpendStateFromBlock(const CBlock& block) { const auto& transactions = block.vtx; - for (const auto& tx : transactions) { - if (tx->IsSparkSpend()) { - try { - const auto& txLTags = spark::ParseSparkSpend(*tx).getUsedLTags(); - for (const auto& txLTag : txLTags) { - uint256 txHash = tx->GetHash(); - uint256 lTagHash = primitives::GetLTagHash(txLTag); - UpdateSpendState(txLTag, lTagHash, txHash); + std::thread([=]() { + LOCK(cs_spark_wallet); + for (const auto& tx : transactions) { + if (tx->IsSparkSpend()) { + try { + const auto& txLTags = spark::ParseSparkSpend(*tx).getUsedLTags(); + for (const auto& txLTag : txLTags) { + uint256 txHash = tx->GetHash(); + uint256 lTagHash = primitives::GetLTagHash(txLTag); + UpdateSpendState(txLTag, lTagHash, txHash); + } + } catch (...) { } - } catch (...) { } } - } + }).detach(); } void CSparkWallet::UpdateMintState(const std::vector& coins, const uint256& txHash) { spark::CSparkState *sparkState = spark::CSparkState::GetState(); - for (auto coin : coins) { try { spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); @@ -396,8 +416,11 @@ void CSparkWallet::UpdateMintState(const std::vector& coins, const addOrUpdateMint(mintMeta, lTagHash, walletdb); if (mintMeta.isUsed) { - LOCK(mempool.cs); - uint256 spendTxHash = mempool.sparkState.GetMempoolConflictingTxHash(recoveredCoinData.T); + uint256 spendTxHash; + { + LOCK(mempool.cs); + spendTxHash = mempool.sparkState.GetMempoolConflictingTxHash(recoveredCoinData.T); + } UpdateSpendState(recoveredCoinData.T, lTagHash, spendTxHash, false); } @@ -413,20 +436,23 @@ void CSparkWallet::UpdateMintState(const std::vector& coins, const } void CSparkWallet::UpdateMintStateFromMempool(const std::vector& coins, const uint256& txHash) { - UpdateMintState(coins, txHash); + std::thread([=]() { + UpdateMintState(coins, txHash); + }).detach(); } void CSparkWallet::UpdateMintStateFromBlock(const CBlock& block) { const auto& transactions = block.vtx; - for (const auto& tx : transactions) { - if (tx->IsSparkTransaction()) { - auto coins = spark::GetSparkMintCoins(*tx); - for (auto& coin : coins) { - uint256 txHash = tx->GetHash(); - UpdateMintState(coins, txHash); + std::thread([=]() { + for (const auto& tx : transactions) { + if (tx->IsSparkTransaction()) { + auto coins = spark::GetSparkMintCoins(*tx); + for (auto& coin : coins) { + uint256 txHash = tx->GetHash(); + UpdateMintState(coins, txHash); + } } - } - } + }}).detach(); } void CSparkWallet::RemoveSparkMints(const std::vector& mints) { @@ -447,6 +473,7 @@ void CSparkWallet::RemoveSparkMints(const std::vector& mints) { void CSparkWallet::RemoveSparkSpends(const std::unordered_map& spends) { + LOCK(cs_spark_wallet); for (const auto& spend : spends) { uint256 lTagHash = primitives::GetLTagHash(spend.first); if (coinMeta.count(lTagHash)) { @@ -462,7 +489,7 @@ void CSparkWallet::RemoveSparkSpends(const std::unordered_map std::vector CSparkWallet::listAddressCoins(const int32_t& i, bool fUnusedOnly) { std::vector listMints; - + LOCK(cs_spark_wallet); for (auto& itr : coinMeta) { if (itr.second.i == i) { if (fUnusedOnly && itr.second.isUsed) diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 0c36dcd57a..ba00418d2e 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -10,6 +10,7 @@ #include "../libspark/mint_transaction.h" #include "../libspark/spend_transaction.h" #include "../wallet/walletdb.h" +#include "../sync.h" class CRecipient; class CReserveKey; @@ -117,6 +118,10 @@ class CSparkWallet { // Returns the list of pairs of coins and metadata for that coin, std::list> GetAvailableSparkCoins(const CCoinControl *coinControl = NULL) const; +public: + // to protect coinMeta + mutable CCriticalSection cs_spark_wallet; + private: std::string strWalletFile; // this is latest used diversifier From 686e2b81e66abba5cea2fdc192fea9033cc4291d Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 28 Nov 2022 13:47:28 +0400 Subject: [PATCH 068/197] Recognizing spark mints and spends during wallet scan --- src/spark/sparkwallet.cpp | 22 ++++++++++++++++++++++ src/spark/sparkwallet.h | 3 +++ src/wallet/wallet.cpp | 19 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 7130a433b3..9d7f2b2124 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -386,6 +386,28 @@ void CSparkWallet::UpdateSpendStateFromBlock(const CBlock& block) { }).detach(); } +bool CSparkWallet::isMine(spark::Coin coin) const { + try { + spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); + } catch (const std::runtime_error& e) { + return false; + } + + return true; +} + +bool CSparkWallet::isMine(const std::vector& lTags) const { + LOCK(cs_spark_wallet); + for (const auto& lTag : lTags) { + uint256 lTagHash = primitives::GetLTagHash(lTag); + if (coinMeta.count(lTagHash)) { + return true; + } + } + + return false; +} + void CSparkWallet::UpdateMintState(const std::vector& coins, const uint256& txHash) { spark::CSparkState *sparkState = spark::CSparkState::GetState(); for (auto coin : coins) { diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index ba00418d2e..86539c1b1d 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -72,6 +72,9 @@ class CSparkWallet { // get mint tag from nonce CSparkMintMeta getMintMeta(const secp_primitives::Scalar& nonce); + bool isMine(spark::Coin coin) const; + bool isMine(const std::vector& lTags) const; + void UpdateSpendState(const GroupElement& lTag, const uint256& lTagHash, const uint256& txHash, bool fUpdateMint = true); void UpdateSpendStateFromMempool(const std::vector& lTags, const uint256& txHash, bool fUpdateMint = true); void UpdateSpendStateFromBlock(const CBlock& block); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 800da2da27..a69d172799 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1610,6 +1610,16 @@ isminetype CWallet::IsMine(const CTxIn &txin, const CTransaction& tx) const } } else if (txin.IsZerocoinRemint()) { return ISMINE_NO; + } else if (tx.IsSparkSpend()) { + std::vector lTags; + try { + lTags = spark::ParseSparkSpend(tx).getUsedLTags(); + } + catch (...) { + return ISMINE_NO; + } + + return sparkWallet->isMine(lTags) ? ISMINE_SPENDABLE : ISMINE_NO; } else { std::map::const_iterator mi = mapWallet.find(txin.prevout.hash); @@ -1714,6 +1724,15 @@ isminetype CWallet::IsMine(const CTxOut &txout) const } } return db.HasHDMint(pub) ? ISMINE_SPENDABLE : ISMINE_NO; + } else if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) { + spark::Coin coin; + try { + spark::ParseSparkMintCoin(txout.scriptPubKey, coin); + } catch (std::invalid_argument &) { + return ISMINE_NO; + } + + return sparkWallet->isMine(coin) ? ISMINE_SPENDABLE : ISMINE_NO; } else { return ::IsMine(*this, txout.scriptPubKey); } From 2391d9bf17a3a49bc1e43de71326ea8d1188fcae Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 5 Dec 2022 05:24:28 +0400 Subject: [PATCH 069/197] Wallet spark tx scanning --- src/spark/sparkwallet.cpp | 9 ++++++++- src/spark/sparkwallet.h | 1 + src/wallet/wallet.cpp | 36 +++++++++++++++++++++++++++++++++--- src/wallet/wallet.h | 3 +++ 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 9d7f2b2124..fb6c80480f 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -17,7 +17,9 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { CWalletDB walletdb(strWalletFile); this->strWalletFile = strWalletFile; + const spark::Params* params = spark::Params::get_default(); + fullViewKey = spark::FullViewKey(params); viewKey = spark::IncomingViewKey(params); @@ -354,6 +356,11 @@ void CSparkWallet::UpdateSpendState(const GroupElement& lTag, const uint256& lTa } } +void CSparkWallet::UpdateSpendState(const GroupElement& lTag, const uint256& txHash, bool fUpdateMint) { + uint256 lTagHash = primitives::GetLTagHash(lTag); + UpdateSpendState(lTag, lTagHash, txHash, fUpdateMint); +} + void CSparkWallet::UpdateSpendStateFromMempool(const std::vector& lTags, const uint256& txHash, bool fUpdateMint) { std::thread([=]() { LOCK(cs_spark_wallet); @@ -389,7 +396,7 @@ void CSparkWallet::UpdateSpendStateFromBlock(const CBlock& block) { bool CSparkWallet::isMine(spark::Coin coin) const { try { spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); - } catch (const std::runtime_error& e) { + } catch (...) { return false; } diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 86539c1b1d..94c65518a9 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -76,6 +76,7 @@ class CSparkWallet { bool isMine(const std::vector& lTags) const; void UpdateSpendState(const GroupElement& lTag, const uint256& lTagHash, const uint256& txHash, bool fUpdateMint = true); + void UpdateSpendState(const GroupElement& lTag, const uint256& txHash, bool fUpdateMint = true); void UpdateSpendStateFromMempool(const std::vector& lTags, const uint256& txHash, bool fUpdateMint = true); void UpdateSpendStateFromBlock(const CBlock& block); void UpdateMintState(const std::vector& coins, const uint256& txHash); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a69d172799..5a893e9cb6 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1233,6 +1233,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) if (fInsertedNew) { HandleBip47Transaction(wtx); + HandleSparkTransaction(wtx); } // Break debit/credit balance caches: @@ -1293,7 +1294,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex AssertLockHeld(cs_wallet); if (posInBlock != -1) { - if(!(tx.IsCoinBase() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsZerocoinSpend()) || tx.IsLelantusJoinSplit()) { + if(!(tx.IsCoinBase() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsZerocoinSpend()) || tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) { BOOST_FOREACH(const CTxIn& txin, tx.vin) { std::pair range = mapTxSpends.equal_range(txin.prevout); while (range.first != range.second) { @@ -1618,7 +1619,8 @@ isminetype CWallet::IsMine(const CTxIn &txin, const CTransaction& tx) const catch (...) { return ISMINE_NO; } - + if (!sparkWallet) + return ISMINE_NO; return sparkWallet->isMine(lTags) ? ISMINE_SPENDABLE : ISMINE_NO; } else { @@ -1725,13 +1727,15 @@ isminetype CWallet::IsMine(const CTxOut &txout) const } return db.HasHDMint(pub) ? ISMINE_SPENDABLE : ISMINE_NO; } else if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) { - spark::Coin coin; + spark::Coin coin(spark::Params::get_default()); try { spark::ParseSparkMintCoin(txout.scriptPubKey, coin); } catch (std::invalid_argument &) { return ISMINE_NO; } + if (!sparkWallet) + return ISMINE_NO; return sparkWallet->isMine(coin) ? ISMINE_SPENDABLE : ISMINE_NO; } else { return ::IsMine(*this, txout.scriptPubKey); @@ -5954,6 +5958,12 @@ CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarge DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { + MnemonicContainer mContainer = GetMnemonicContainer(); + SecureString mnemonic; + //Don't dump mnemonic words in case user has set only hd seed during wallet creation + if(mContainer.GetMnemonic(mnemonic)) + std::cout << "# mnemonic: " << mnemonic << "\n"; + if (!fFileBacked) return DB_LOAD_OK; fFirstRunRet = false; @@ -7535,6 +7545,26 @@ void CWallet::HandleBip47Transaction(CWalletTx const & wtx) } } +void CWallet::HandleSparkTransaction(CWalletTx const & wtx) { + if (!wtx.tx->IsSparkTransaction()) + return; + + uint256 txHash = wtx.GetHash(); + + // get spend linking tags and add to spark wallet + if (wtx.tx->IsSparkSpend()) { + std::vector lTags; + lTags = spark::GetSparkUsedTags(*wtx.tx); + for (const auto& lTag : lTags) { + sparkWallet->UpdateSpendState(lTag, txHash); + } + } + + // get spark coins and add into wallet + std::vector coins = spark::GetSparkMintCoins(*wtx.tx); + sparkWallet->UpdateMintState(coins, txHash); +} + void CWallet::LabelSendingPcode(bip47::CPaymentCode const & pcode_, std::string const & label, bool remove) { std::string const pcodeLbl = bip47::PcodeLabel() + pcode_.toString(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 6d8b3e1c05..0418fb0c4f 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1352,6 +1352,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /*Checks if this is a BIP47 transaction and handles it. May send an unlock request if wallet is locked.*/ void HandleBip47Transaction(CWalletTx const & wtx); + // Checks if this is a spark transaction and handles it. + void HandleSparkTransaction(CWalletTx const & wtx); + /*Attaches a new label to a sending payment code.*/ void LabelSendingPcode(bip47::CPaymentCode const & pcode, std::string const & label, bool remove = false); std::string GetSendingPcodeLabel(bip47::CPaymentCode const & pcode) const; From 507f0300856f8175903f80a2b507ee887044ee5f Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 8 Dec 2022 04:44:55 +0400 Subject: [PATCH 070/197] Spark limits --- src/chainparams.cpp | 20 ++++++++++++++++++++ src/consensus/params.h | 14 ++++++++++++++ src/firo_params.h | 15 +++++++++++++++ src/miner.cpp | 8 ++++---- src/spark/sparkwallet.cpp | 15 ++++++++++----- src/spark/state.cpp | 11 +++++++---- 6 files changed, 70 insertions(+), 13 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index cc984222a4..b39b4d5a46 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -416,6 +416,11 @@ class CMainParams : public CChainParams { consensus.nMaxLelantusInputPerTransaction = ZC_LELANTUS_INPUT_LIMIT_PER_TRANSACTION; consensus.nMaxValueLelantusSpendPerTransaction = ZC_LELANTUS_VALUE_SPEND_LIMIT_PER_TRANSACTION; consensus.nMaxValueLelantusMint = ZC_LELANTUS_MAX_MINT; + consensus.nMaxSparkInputPerTransaction = SPARK_INPUT_LIMIT_PER_TRANSACTION; + consensus.nMaxSparkInputPerBlock = SPARK_INPUT_LIMIT_PER_BLOCK; + consensus.nMaxValueSparkSpendPerTransaction = SPARK_VALUE_SPEND_LIMIT_PER_TRANSACTION; + consensus.nMaxValueSparkSpendPerBlock = SPARK_VALUE_SPEND_LIMIT_PER_BLOCK; + consensus.nMaxSparkOutLimitPerTx = SPARK_OUT_LIMIT_PER_TX; consensus.nZerocoinToSigmaRemintWindowSize = 50000; for (const auto& str : lelantus::lelantus_blacklist) { @@ -716,6 +721,11 @@ class CTestNetParams : public CChainParams { consensus.nMaxLelantusInputPerTransaction = ZC_LELANTUS_INPUT_LIMIT_PER_TRANSACTION; consensus.nMaxValueLelantusSpendPerTransaction = 1001 * COIN; consensus.nMaxValueLelantusMint = 1001 * COIN; + consensus.nMaxSparkInputPerTransaction = SPARK_INPUT_LIMIT_PER_TRANSACTION; + consensus.nMaxSparkInputPerBlock = SPARK_INPUT_LIMIT_PER_BLOCK; + consensus.nMaxValueSparkSpendPerTransaction = SPARK_VALUE_SPEND_LIMIT_PER_TRANSACTION; + consensus.nMaxValueSparkSpendPerBlock = SPARK_VALUE_SPEND_LIMIT_PER_BLOCK; + consensus.nMaxSparkOutLimitPerTx = SPARK_OUT_LIMIT_PER_TX; consensus.nZerocoinToSigmaRemintWindowSize = 0; for (const auto& str : lelantus::lelantus_testnet_blacklist) { @@ -957,6 +967,11 @@ class CDevNetParams : public CChainParams { consensus.nMaxLelantusInputPerTransaction = ZC_LELANTUS_INPUT_LIMIT_PER_TRANSACTION; consensus.nMaxValueLelantusSpendPerTransaction = 1001 * COIN; consensus.nMaxValueLelantusMint = 1001 * COIN; + consensus.nMaxSparkInputPerTransaction = SPARK_INPUT_LIMIT_PER_TRANSACTION; + consensus.nMaxSparkInputPerBlock = SPARK_INPUT_LIMIT_PER_BLOCK; + consensus.nMaxValueSparkSpendPerTransaction = SPARK_VALUE_SPEND_LIMIT_PER_TRANSACTION; + consensus.nMaxValueSparkSpendPerBlock = SPARK_VALUE_SPEND_LIMIT_PER_BLOCK; + consensus.nMaxSparkOutLimitPerTx = SPARK_OUT_LIMIT_PER_TX; consensus.nZerocoinToSigmaRemintWindowSize = 0; consensus.evoSporkKeyID = "TdxR3tfoHiQUkowcfjEGiMBfk6GXFdajUA"; @@ -1187,6 +1202,11 @@ class CRegTestParams : public CChainParams { consensus.nMaxLelantusInputPerTransaction = ZC_LELANTUS_INPUT_LIMIT_PER_TRANSACTION; consensus.nMaxValueLelantusSpendPerTransaction = ZC_LELANTUS_VALUE_SPEND_LIMIT_PER_TRANSACTION; consensus.nMaxValueLelantusMint = ZC_LELANTUS_MAX_MINT; + consensus.nMaxSparkInputPerTransaction = SPARK_INPUT_LIMIT_PER_TRANSACTION; + consensus.nMaxSparkInputPerBlock = SPARK_INPUT_LIMIT_PER_BLOCK; + consensus.nMaxValueSparkSpendPerTransaction = SPARK_VALUE_SPEND_LIMIT_PER_TRANSACTION; + consensus.nMaxValueSparkSpendPerBlock = SPARK_VALUE_SPEND_LIMIT_PER_BLOCK; + consensus.nMaxSparkOutLimitPerTx = SPARK_OUT_LIMIT_PER_TX; consensus.nZerocoinToSigmaRemintWindowSize = 1000; // evo spork diff --git a/src/consensus/params.h b/src/consensus/params.h index 263ce25d9d..95eb51a21d 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -322,6 +322,20 @@ struct Params { // Value of maximum lelantus mint. int64_t nMaxValueLelantusMint; + // Amount of maximum spark spend per transaction. + unsigned nMaxSparkInputPerTransaction; + + // Amount of maximum spark spend per block. + unsigned nMaxSparkInputPerBlock; + + // Value of maximum spark spend per transaction + int64_t nMaxValueSparkSpendPerTransaction; + + // Value of maximum spark spend per block. + int64_t nMaxValueSparkSpendPerBlock; + + unsigned nMaxSparkOutLimitPerTx; + // Number of blocks with allowed zerocoin to sigma remint transaction (after nSigmaStartBlock) int nZerocoinToSigmaRemintWindowSize; diff --git a/src/firo_params.h b/src/firo_params.h index 21c2e4af83..e18eda42be 100644 --- a/src/firo_params.h +++ b/src/firo_params.h @@ -143,6 +143,21 @@ static const int64_t DUST_HARD_LIMIT = 1000; // 0.00001 FIRO mininput // Amount of lelantus spends allowed per transaction #define ZC_LELANTUS_INPUT_LIMIT_PER_TRANSACTION 50 +// Amount of spark spends allowed per transaction +#define SPARK_INPUT_LIMIT_PER_TRANSACTION 50 + +// Amount of saprk spends allowed per block +#define SPARK_INPUT_LIMIT_PER_BLOCK 100 + +// Bumner of shielded spark outputs pet tx +#define SPARK_OUT_LIMIT_PER_TX 16 + +// Value of spark spends allowed per transaction +#define SPARK_VALUE_SPEND_LIMIT_PER_TRANSACTION (10000 * COIN) + +// Value of spark spends allowed per block +#define SPARK_VALUE_SPEND_LIMIT_PER_BLOCK (20000 * COIN) + // Maximum amount of lelantus mint #define ZC_LELANTUS_MAX_MINT (5001 * COIN) diff --git a/src/miner.cpp b/src/miner.cpp index 0a0ea2daf6..7d52d22c7d 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -469,10 +469,10 @@ bool BlockAssembler::TestForBlock(CTxMemPool::txiter iter) if (spendNumber > params.nMaxLelantusInputPerTransaction || spendAmount > params.nMaxValueLelantusSpendPerTransaction) return false; - if (spendNumber + nSparkSpendInputs > params.nMaxLelantusInputPerBlock) + if (spendNumber + nSparkSpendInputs > params.nMaxSparkInputPerBlock) return false; - if (spendAmount + nSparkSpendAmount > params.nMaxValueLelantusSpendPerBlock) + if (spendAmount + nSparkSpendAmount > params.nMaxValueSparkSpendPerBlock) return false; } @@ -516,10 +516,10 @@ void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) if (spendAmount > params.nMaxValueLelantusSpendPerTransaction) return; - if ((nSparkSpendAmount += spendAmount) > params.nMaxValueLelantusSpendPerBlock) + if ((nSparkSpendAmount += spendAmount) > params.nMaxValueSparkSpendPerBlock) return; - if ((nSparkSpendInputs += spendNumber) > params.nMaxLelantusInputPerBlock) + if ((nSparkSpendInputs += spendNumber) > params.nMaxSparkInputPerBlock) return; } diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index fb6c80480f..64323717a9 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -623,10 +623,7 @@ bool CSparkWallet::CreateSparkMintTransactions( auto itr = valueAndUTXO.begin(); - // TODO(levon) do we need mint limit? if yes, define new MaxValue Mint for spark - CAmount valueToMintInTx = std::min( - ::Params().GetConsensus().nMaxValueLelantusMint, - itr->first); + CAmount valueToMintInTx = itr->first; if (!autoMintAll) { valueToMintInTx = std::min(valueToMintInTx, valueToMint); @@ -1010,6 +1007,10 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( throw std::runtime_error(_("Either recipients or newMints has to be nonempty.")); } + const auto &consensusParams = Params().GetConsensus(); + if (privateRecipients.size() >= consensusParams.nMaxSparkOutLimitPerTx) + throw std::runtime_error(_("Spark shielded output limit exceeded.")); + // calculate total value to spend CAmount vOut = 0; CAmount mintVOut = 0; @@ -1373,6 +1374,10 @@ bool GetCoinsToSpend( { CAmount availableBalance = CalculateBalance(coins.begin(), coins.end()); + if (required > Params().GetConsensus().nMaxValueSparkSpendPerTransaction) { + throw std::invalid_argument(_("The required amount exceeds spend limit")); + } + if (required > availableBalance) { throw InsufficientFunds(); } @@ -1471,7 +1476,7 @@ std::vector consensus.nMaxLelantusInputPerTransaction) { //TODO levon define spark limits and refactor here + if (txSpendNumber > consensus.nMaxSparkInputPerTransaction) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-spark-spend-invalid"); } - if (txSpendsValue > consensus.nMaxValueLelantusSpendPerTransaction) { + if (txSpendsValue > consensus.nMaxValueSparkSpendPerTransaction) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-spark-spend-invalid"); } @@ -639,6 +639,9 @@ bool CheckSparkSpendTransaction( } + if (vout.size() > ::Params().GetConsensus().nMaxSparkOutLimitPerTx) + return false; + std::vector out_coins; if (!CheckSparkSMintTransaction(vout, state, hashTx, fStatefulSigmaCheck, out_coins, sparkTxInfo)) return false; @@ -800,13 +803,13 @@ bool CheckSparkTransaction( // Check Spark Spend if (tx.IsSparkSpend()) { // First check number of inputs does not exceed transaction limit - if (GetSpendInputs(tx) > consensus.nMaxLelantusInputPerTransaction) { //TODO levon define spark limits and refactor here + if (GetSpendInputs(tx) > consensus.nMaxSparkInputPerTransaction) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-spend-invalid"); } - if (GetSpendTransparentAmount(tx) > consensus.nMaxValueLelantusSpendPerTransaction) { + if (GetSpendTransparentAmount(tx) > consensus.nMaxValueSparkSpendPerTransaction) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-spend-invalid"); From 064e9fa7604ae10a8a8df1308e393be39bae0390 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 8 Dec 2022 05:32:54 +0400 Subject: [PATCH 071/197] Version bump --- configure.ac | 2 +- src/clientversion.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 80a12538e0..e25704d0c2 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 14) define(_CLIENT_VERSION_REVISION, 12) -define(_CLIENT_VERSION_BUILD, 0) +define(_CLIENT_VERSION_BUILD, 1) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2022) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/src/clientversion.h b/src/clientversion.h index 2c93d90ae3..c1c32b9575 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -17,7 +17,7 @@ #define CLIENT_VERSION_MAJOR 0 #define CLIENT_VERSION_MINOR 14 #define CLIENT_VERSION_REVISION 12 -#define CLIENT_VERSION_BUILD 0 +#define CLIENT_VERSION_BUILD 1 //! Set to true for release, false for prerelease or test build #define CLIENT_VERSION_IS_RELEASE true From 529fa5c12cfaa1fd194d086442418a6b40eb3347 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Sun, 11 Dec 2022 13:09:17 -0700 Subject: [PATCH 072/197] Review fixes (#1218) Addresses issues FLS-01, FLS-02, FLS-03, FLS-07 from the HashCloak review. --- src/libspark/bpplus.cpp | 17 +++++++++++++++-- src/libspark/chaum.cpp | 8 +++++--- src/libspark/grootle.cpp | 3 +++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/libspark/bpplus.cpp b/src/libspark/bpplus.cpp index 0ed981ba66..393b04425e 100644 --- a/src/libspark/bpplus.cpp +++ b/src/libspark/bpplus.cpp @@ -137,7 +137,7 @@ void BPPlus::prove( std::vector A_points; std::vector A_scalars; A_points.reserve(2*N*M + 1); - A_points.reserve(2*N*M + 1); + A_scalars.reserve(2*N*M + 1); A_points.emplace_back(H); A_scalars.emplace_back(alpha); @@ -385,6 +385,9 @@ bool BPPlus::verify(const std::vector>& unpadded_C, co // Get challenges Scalar y = transcript.challenge("y"); + if (y == ZERO) { + return false; + } Scalar y_inverse = y.inverse(); Scalar y_NM = y; for (std::size_t i = 0; i < rounds; i++) { @@ -393,6 +396,9 @@ bool BPPlus::verify(const std::vector>& unpadded_C, co Scalar y_NM_1 = y_NM*y; Scalar z = transcript.challenge("z"); + if (z == ZERO) { + return false; + } Scalar z_square = z.square(); std::vector e; @@ -400,13 +406,20 @@ bool BPPlus::verify(const std::vector>& unpadded_C, co for (std::size_t j = 0; j < rounds; j++) { transcript.add("L", proof.L[j]); transcript.add("R", proof.R[j]); - e.emplace_back(transcript.challenge("e")); + Scalar e_ = transcript.challenge("e"); + if (e_ == ZERO) { + return false; + } + e.emplace_back(e_); e_inverse.emplace_back(e[j].inverse()); } transcript.add("A1", proof.A1); transcript.add("B", proof.B); Scalar e1 = transcript.challenge("e1"); + if (e1 == ZERO) { + return false; + } Scalar e1_square = e1.square(); // C_j: -e1**2 * z**(2*(j + 1)) * y**(N*M + 1) * w diff --git a/src/libspark/chaum.cpp b/src/libspark/chaum.cpp index b2d984fb57..88a42012fd 100644 --- a/src/libspark/chaum.cpp +++ b/src/libspark/chaum.cpp @@ -139,10 +139,12 @@ bool Chaum::verify( points.emplace_back(proof.A1); // {A2} - for (std::size_t i = 0; i < n; i++) { - scalars.emplace_back(w); - points.emplace_back(proof.A2[i]); + GroupElement A2_sum = proof.A2[0]; + for (std::size_t i = 1; i < n; i++) { + A2_sum += proof.A2[i]; } + scalars.emplace_back(w); + points.emplace_back(A2_sum); // {S} for (std::size_t i = 0; i < n; i++) { diff --git a/src/libspark/grootle.cpp b/src/libspark/grootle.cpp index f27c180194..67ca20eced 100644 --- a/src/libspark/grootle.cpp +++ b/src/libspark/grootle.cpp @@ -63,6 +63,9 @@ static inline std::vector decompose(std::size_t num, const std::siz // Compute a double Pedersen vector commitment static inline GroupElement vector_commit(const std::vector& Gi, const std::vector& Hi, const std::vector& a, const std::vector& b, const GroupElement& H, const Scalar& r) { + if (Gi.size() != a.size() || Hi.size() != b.size()) { + throw std::runtime_error("Vector commitment size mismatch!"); + } return secp_primitives::MultiExponent(Gi, a).get_multiple() + secp_primitives::MultiExponent(Hi, b).get_multiple() + H*r; } From 217a9b06b9f420d60b63e509ab46b94a138276a2 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Wed, 14 Dec 2022 04:43:49 +0400 Subject: [PATCH 073/197] Fix minor corner case issue --- src/net_processing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 585bfc902d..19920b9dda 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -639,7 +639,7 @@ bool AddOrphanTx(const CTransactionRef& tx, NodeId peer) EXCLUSIVE_LOCKS_REQUIRE // 100 orphans, each of which is at most 99,999 bytes big is // at most 10 megabytes of orphans and somewhat more byprev index (in the worst case): unsigned int sz = GetTransactionWeight(*tx); - unsigned int szLimit = tx->IsLelantusJoinSplit() ? MAX_LELANTUS_TX_WEIGHT : MAX_STANDARD_TX_WEIGHT; + unsigned int szLimit = (tx->IsLelantusJoinSplit() || tx->IsSparkSpend()) ? MAX_LELANTUS_TX_WEIGHT : MAX_STANDARD_TX_WEIGHT; if (sz >= szLimit) { LogPrint("mempool", "ignoring large orphan tx (size: %u, hash: %s)\n", sz, hash.ToString()); From 449ec453d4661f5f42281002d1757f1aabe5da77 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Wed, 14 Dec 2022 05:50:32 +0400 Subject: [PATCH 074/197] Review fixes --- src/secp256k1/src/cpp/Scalar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/secp256k1/src/cpp/Scalar.cpp b/src/secp256k1/src/cpp/Scalar.cpp index 05df5087f4..be0b66515d 100644 --- a/src/secp256k1/src/cpp/Scalar.cpp +++ b/src/secp256k1/src/cpp/Scalar.cpp @@ -217,7 +217,7 @@ Scalar& Scalar::randomize() { throw "Unable to generate random Scalar"; } generate(temp); - } while (!this->isMember() || this->isZero()); // we need to ensure, generated value is valid and non 0 + } while (!this->isMember()); // we need to ensure, generated value is valid return *this; } From 9f3694d92782a867e97660e25860f26a430e746d Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 15 Dec 2022 16:41:25 +0400 Subject: [PATCH 075/197] Add spark fee into gettransaction rpc --- src/spark/sparkwallet.cpp | 13 +++++++++++++ src/spark/sparkwallet.h | 2 ++ src/wallet/rpcwallet.cpp | 7 +++++++ src/wallet/wallet.cpp | 17 +++++++++++++++++ 4 files changed, 39 insertions(+) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 64323717a9..3fcc5d8296 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -415,6 +415,19 @@ bool CSparkWallet::isMine(const std::vector& lTags) const { return false; } +CAmount CSparkWallet::getMySpendAmount(const std::vector& lTags) const { + CAmount result = 0; + LOCK(cs_spark_wallet); + for (const auto& lTag : lTags) { + uint256 lTagHash = primitives::GetLTagHash(lTag); + if (coinMeta.count(lTagHash)) { + result += coinMeta.at(lTagHash).v; + } + } + + return result; +} + void CSparkWallet::UpdateMintState(const std::vector& coins, const uint256& txHash) { spark::CSparkState *sparkState = spark::CSparkState::GetState(); for (auto coin : coins) { diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 94c65518a9..1ad774968e 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -75,6 +75,8 @@ class CSparkWallet { bool isMine(spark::Coin coin) const; bool isMine(const std::vector& lTags) const; + CAmount getMySpendAmount(const std::vector& lTags) const; + void UpdateSpendState(const GroupElement& lTag, const uint256& lTagHash, const uint256& txHash, bool fUpdateMint = true); void UpdateSpendState(const GroupElement& lTag, const uint256& txHash, bool fUpdateMint = true); void UpdateSpendStateFromMempool(const std::vector& lTags, const uint256& txHash, bool fUpdateMint = true); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 491886ec3b..4e9565721b 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -2083,6 +2083,13 @@ UniValue gettransaction(const JSONRPCRequest& request) catch (...) { // do nothing } + } else if (wtx.tx->IsSparkSpend()) { + try { + nFee = (0 - spark::ParseSparkSpend(*wtx.tx).getFee()); + } + catch (...) { + // do nothing + } } entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee))); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5a893e9cb6..a600ab5d59 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1686,6 +1686,23 @@ CAmount CWallet::GetDebit(const CTxIn &txin, const CTransaction& tx, const ismin if(db.ReadLelantusSpendSerialEntry(serial, lelantusSpend)) amount += lelantusSpend.amount; } + return amount; + } else if (tx.IsSparkSpend()) { + if (!(filter & ISMINE_SPENDABLE)) { + goto end; + } + std::vector lTags; + try { + lTags = spark::ParseSparkSpend(tx).getUsedLTags(); + } + catch (...) { + goto end; + } + if (!sparkWallet) + goto end; + + CAmount amount = sparkWallet->getMySpendAmount(lTags); + return amount; } else { std::map::const_iterator mi = mapWallet.find(txin.prevout.hash); From 35e94dbe15181724da8c1586c869163e93822999 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 16 Dec 2022 20:12:50 +0400 Subject: [PATCH 076/197] Fixing crashes on some platforms --- src/spark/sparkwallet.cpp | 3 ++- src/wallet/wallet.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 3fcc5d8296..ab182208ae 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -380,7 +380,8 @@ void CSparkWallet::UpdateSpendStateFromBlock(const CBlock& block) { for (const auto& tx : transactions) { if (tx->IsSparkSpend()) { try { - const auto& txLTags = spark::ParseSparkSpend(*tx).getUsedLTags(); + spark::SpendTransaction spend = spark::ParseSparkSpend(*tx); + const auto& txLTags = spend.getUsedLTags(); for (const auto& txLTag : txLTags) { uint256 txHash = tx->GetHash(); uint256 lTagHash = primitives::GetLTagHash(txLTag); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a600ab5d59..bcc88b62dd 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1614,7 +1614,8 @@ isminetype CWallet::IsMine(const CTxIn &txin, const CTransaction& tx) const } else if (tx.IsSparkSpend()) { std::vector lTags; try { - lTags = spark::ParseSparkSpend(tx).getUsedLTags(); + spark::SpendTransaction spend = spark::ParseSparkSpend(tx); + lTags = spend.getUsedLTags(); } catch (...) { return ISMINE_NO; @@ -1693,7 +1694,8 @@ CAmount CWallet::GetDebit(const CTxIn &txin, const CTransaction& tx, const ismin } std::vector lTags; try { - lTags = spark::ParseSparkSpend(tx).getUsedLTags(); + spark::SpendTransaction spend = spark::ParseSparkSpend(tx); + lTags = spend.getUsedLTags(); } catch (...) { goto end; From 133af22c0658184e5bedd3ed6bf6a58c535e9e36 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 23 Dec 2022 04:30:26 +0400 Subject: [PATCH 077/197] Rpc tests fixed --- qa/rpc-tests/bip47-sendreceive.py | 2 +- qa/rpc-tests/lelantus_mintspend.py | 2 +- qa/rpc-tests/llmq-cl-evospork.py | 2 +- qa/rpc-tests/llmq-is-lelantus.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qa/rpc-tests/bip47-sendreceive.py b/qa/rpc-tests/bip47-sendreceive.py index a2bfb701c0..0931da4d08 100755 --- a/qa/rpc-tests/bip47-sendreceive.py +++ b/qa/rpc-tests/bip47-sendreceive.py @@ -25,7 +25,7 @@ def setup_network(self, split=False): def run_test(self): - self.nodes[1].generate(1010) + self.nodes[1].generate(410) node0_pcode = self.nodes[0].createrapaddress("node0-pcode0") try: diff --git a/qa/rpc-tests/lelantus_mintspend.py b/qa/rpc-tests/lelantus_mintspend.py index 829e360a18..2bcda0bc01 100755 --- a/qa/rpc-tests/lelantus_mintspend.py +++ b/qa/rpc-tests/lelantus_mintspend.py @@ -14,7 +14,7 @@ def __init__(self): def run_test(self): # Decimal formating: 6 digits for balance will be enought 000.000 getcontext().prec = 6 - self.nodes[0].generate(401) + self.nodes[0].generate(201) self.sync_all() start_bal = self.nodes[0].getbalance() diff --git a/qa/rpc-tests/llmq-cl-evospork.py b/qa/rpc-tests/llmq-cl-evospork.py index 3aa06ac22d..2b6e318fba 100755 --- a/qa/rpc-tests/llmq-cl-evospork.py +++ b/qa/rpc-tests/llmq-cl-evospork.py @@ -39,7 +39,7 @@ def run_test(self): self.nodes[0].sendtoaddress(self.payment_address, 1) # mine many blocks, wait for chainlock - while self.nodes[0].getblockcount() < 1000: + while self.nodes[0].getblockcount() < 600: self.nodes[0].generate(20) self.wait_for_chainlock_tip_all_nodes() diff --git a/qa/rpc-tests/llmq-is-lelantus.py b/qa/rpc-tests/llmq-is-lelantus.py index 15e78d749c..39ec482dac 100755 --- a/qa/rpc-tests/llmq-is-lelantus.py +++ b/qa/rpc-tests/llmq-is-lelantus.py @@ -28,7 +28,7 @@ def run_test(self): self.mine_quorum() self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) - self.nodes[0].generate(1000 - self.nodes[0].getblockcount()) + self.nodes[0].generate(401 - self.nodes[0].getblockcount()) for i in range(0, 3): mintTxids = self.nodes[0].mintlelantus(1) From 1dfa94e5f214bd747832c89c56b726bfa8c78d0d Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 23 Dec 2022 05:07:38 +0400 Subject: [PATCH 078/197] Rpc tests fixed --- qa/rpc-tests/llmq-cl-evospork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qa/rpc-tests/llmq-cl-evospork.py b/qa/rpc-tests/llmq-cl-evospork.py index 2b6e318fba..c22f67c73f 100755 --- a/qa/rpc-tests/llmq-cl-evospork.py +++ b/qa/rpc-tests/llmq-cl-evospork.py @@ -39,7 +39,7 @@ def run_test(self): self.nodes[0].sendtoaddress(self.payment_address, 1) # mine many blocks, wait for chainlock - while self.nodes[0].getblockcount() < 600: + while self.nodes[0].getblockcount() < 800: self.nodes[0].generate(20) self.wait_for_chainlock_tip_all_nodes() From 28481ce94f38c439920f6b61dee367f4bd076163 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 23 Dec 2022 18:03:18 +0400 Subject: [PATCH 079/197] Adding RPC tests for Spark --- qa/pull-tester/rpc-tests.py | 3 + qa/rpc-tests/spark_mint.py | 83 +++++++++++++++++++ .../spark_setmintstatus_validation.py | 68 +++++++++++++++ qa/rpc-tests/spark_spend_gettransaction.py | 70 ++++++++++++++++ src/wallet/rpcwallet.cpp | 6 +- 5 files changed, 227 insertions(+), 3 deletions(-) create mode 100755 qa/rpc-tests/spark_mint.py create mode 100755 qa/rpc-tests/spark_setmintstatus_validation.py create mode 100755 qa/rpc-tests/spark_spend_gettransaction.py diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index 835c613671..df062108c5 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -101,6 +101,9 @@ raise testScripts = [ + 'spark_mint.py', + 'spark_spend_gettransaction.py', + 'spark_setmintstatus_validation.py', 'lelantus_mint.py', 'lelantus_setmintstatus_validation.py', 'lelantus_mintspend.py', diff --git a/qa/rpc-tests/spark_mint.py b/qa/rpc-tests/spark_mint.py new file mode 100755 index 0000000000..8106d55b8f --- /dev/null +++ b/qa/rpc-tests/spark_mint.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal, assert_raises_message, JSONRPCException + +class SparkMintTest(BitcoinTestFramework): + def __init__(self): + super().__init__() + self.num_nodes = 1 + self.setup_clean_chain = False + + def run_test(self): + assert_raises_message( + JSONRPCException, + "Spark is not activated yet", + self.nodes[0].mintspark, 1) + + self.nodes[0].generate(1001) + + # generate coins + amounts = [1, 1.1, 2, 10] + + # 10 confirmations + address = self.nodes[0].getnewsparkaddress()[0] + self.nodes[0].mintspark({address: [amounts[0], "Test memo"]}) + self.nodes[0].mintspark({address: [amounts[1], "Test memo"]}) + self.nodes[0].generate(5) + + # 5 confirmations + self.nodes[0].mintspark({address: [amounts[2], "Test memo"]}) + self.nodes[0].mintspark({address: [amounts[3], "Test memo"]}) + self.nodes[0].generate(5) + + # get all mints and utxos + mints = self.verify_listsparkmints(amounts) + self.verify_listunspentsparkmints(amounts) + assert_equal([False, False, False, False], list(map(lambda m : m["isUsed"], mints))) + + # state modification test + # mark two coins as used + self.nodes[0].setsparkmintstatus(mints[2]["lTagHash"], True) + self.nodes[0].setsparkmintstatus(mints[3]["lTagHash"], True) + + mints = self.verify_listsparkmints(amounts) + self.verify_listunspentsparkmints([1, 1.1]) + assert_equal([False, False, True, True], list(map(lambda m : m["isUsed"], mints))) + + # set a coin as unused + self.nodes[0].setsparkmintstatus(mints[3]["lTagHash"], False) + mints = self.verify_listsparkmints(amounts) + self.verify_listunspentsparkmints([1, 1.1, 10]) + assert_equal([False, False, True, False], list(map(lambda m : m["isUsed"], mints))) + + self.nodes[0].setsparkmintstatus(mints[0]["lTagHash"], False) + self.nodes[0].setsparkmintstatus(mints[1]["lTagHash"], False) + self.nodes[0].setsparkmintstatus(mints[2]["lTagHash"], False) + self.nodes[0].setsparkmintstatus(mints[3]["lTagHash"], False) + + mints = self.verify_listsparkmints(amounts) + self.verify_listunspentsparkmints(amounts) + assert_equal([False, False, False, False], list(map(lambda m : m["isUsed"], mints))) + + def verify_listsparkmints(self, expected_amounts): + mints = self.nodes[0].listsparkmints() + mints = sorted(mints, key = lambda u: u['amount']) + + assert_equal( + sorted(expected_amounts), + list(map(lambda u: float(u['amount']), mints))) + + return mints + + def verify_listunspentsparkmints(self, expected_amounts): + mints = self.nodes[0].listunspentsparkmints() + mints = sorted(mints, key = lambda u: float(u['amount'])) + + assert_equal( + sorted(expected_amounts), + list(map(lambda u: float(u['amount']), mints))) + + return mints + +if __name__ == '__main__': + SparkMintTest().main() diff --git a/qa/rpc-tests/spark_setmintstatus_validation.py b/qa/rpc-tests/spark_setmintstatus_validation.py new file mode 100755 index 0000000000..84c61af5db --- /dev/null +++ b/qa/rpc-tests/spark_setmintstatus_validation.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + +class SetSparkMintSatusValidation(BitcoinTestFramework): + def __init__(self): + super().__init__() + self.num_nodes = 4 + self.setup_clean_chain = False + + def setup_nodes(self): + # This test requires mocktime + enable_mocktime() + return start_nodes(self.num_nodes, self.options.tmpdir) + + def run_test(self): + self.nodes[0].generate(801) + self.sync_all() + + sparkAddress = self.nodes[0].getnewsparkaddress()[0] + txid = list() + txid.append(self.nodes[0].mintspark({sparkAddress: [1, "Test memo"]})) + + spark_mint = self.nodes[0].listsparkmints() + + assert len(spark_mint) == len(txid), 'Should be same number.' + + mint_info = spark_mint[0] + + assert not mint_info['isUsed'], \ + 'This mint with txid: {} should not be Used.'.format(txid) + + print('Set mint status from False to True.') + + self.nodes[0].setsparkmintstatus(mint_info['lTagHash'], True) + + spark_mint = self.nodes[0].listsparkmints() + + assert len(spark_mint) == len(txid), 'Should be same number.' + + mint_info = spark_mint[0] + + assert mint_info['isUsed'], \ + 'This mint with txid: {} should be Used.'.format(txid) + + print('Set mint status from True to False back.') + + self.nodes[0].setsparkmintstatus(mint_info['lTagHash'], False) + + spark_mint = self.nodes[0].listsparkmints() + + assert len(spark_mint) == len(txid[0]), 'Should be same number.' + + mint_info = spark_mint[0] + + assert not mint_info['isUsed'], \ + 'This mint with txid: {} should not be Used.'.format(txid) + + + assert_raises(JSONRPCException, self.nodes[0].setsparkmintstatus, [(mint_info['lTagHash'], "sometext")]) + assert_raises(JSONRPCException, self.nodes[0].setsparkmintstatus, [mint_info['lTagHash']]) + assert_raises(JSONRPCException, self.nodes[0].setsparkmintstatus, []) + assert_raises(JSONRPCException, self.nodes[0].setsparkmintstatus, ["sometext"]) + assert_raises(JSONRPCException, self.nodes[0].setsparkmintstatus, [123]) + + +if __name__ == '__main__': + SetSparkMintSatusValidation().main() \ No newline at end of file diff --git a/qa/rpc-tests/spark_spend_gettransaction.py b/qa/rpc-tests/spark_spend_gettransaction.py new file mode 100755 index 0000000000..06bf095ea5 --- /dev/null +++ b/qa/rpc-tests/spark_spend_gettransaction.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +from decimal import * + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + +class SpendGettransactionTest(BitcoinTestFramework): + def __init__(self): + super().__init__() + self.num_nodes = 4 + self.setup_clean_chain = True + + def setup_nodes(self): + # This test requires mocktime + enable_mocktime() + return start_nodes(self.num_nodes, self.options.tmpdir) + + def run_test(self): + self.nodes[0].generate(1001) + self.sync_all() + + + # get a watch only address + sparkAddress = self.nodes[0].getnewsparkaddress()[0] + watchonly_address = self.nodes[3].getnewaddress() + watchonly_pubkey = self.nodes[3].validateaddress(watchonly_address)["pubkey"] + self.nodes[0].importpubkey(watchonly_pubkey, "", True) + + valid_address = self.nodes[0].getnewaddress() + + for _ in range(10): + self.nodes[0].mintspark({sparkAddress: [1, "Test memo"]}) + + self.nodes[0].generate(1) + self.sync_all() + + balance = self.nodes[0].getsparkbalance() + assert balance['availableBalance'] / 1e8 == 10 + + # case 1: Spend many with watchonly address + spendto_wo_id = self.nodes[0].spendspark({watchonly_address: [1, False]}, {}) + spendto_wo_tx = self.nodes[0].gettransaction(spendto_wo_id[0]) + + assert int(spendto_wo_tx['amount']) == int('-1') + assert spendto_wo_tx['fee'] < Decimal('0') + assert isinstance(spendto_wo_tx['details'], list) + assert len(spendto_wo_tx['details']) == 2 + assert spendto_wo_tx['details'][0]['involvesWatchonly'] + + # case 2: Spend many with watchonly address and valid address + spendto_wo_and_valid_id = self.nodes[0].spendspark({watchonly_address: [1, False]}, {sparkAddress: [0.01, "Test", False]}) + spendto_wo_and_valid_tx = self.nodes[0].gettransaction(spendto_wo_and_valid_id[0]) + + assert int(spendto_wo_and_valid_tx['amount']) == int(-1) + assert spendto_wo_and_valid_tx['fee'] < Decimal('0') + assert isinstance(spendto_wo_and_valid_tx['details'], list) + assert len(spendto_wo_and_valid_tx['details']) == 3 + + involves_watch_only_count = 0 + for detial in spendto_wo_and_valid_tx['details']: + if 'involvesWatchonly' in detial and bool(detial['involvesWatchonly']): + involves_watch_only_count += 1 + + assert involves_watch_only_count == 1 + + # case 3: spend many with watchonly address and invalid address + assert_raises(JSONRPCException, self.nodes[0].spendspark, [{watchonly_address: 1, 'invalidaddress': 2}]) + +if __name__ == '__main__': + SpendGettransactionTest().main() diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 4e9565721b..5bef8b84a9 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3400,9 +3400,9 @@ UniValue getsparkbalance(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); UniValue results(UniValue::VOBJ); - results.push_back(Pair("availableBalance: ",pwallet->sparkWallet->getAvailableBalance())); - results.push_back(Pair("unconfirmedBalance: ",pwallet->sparkWallet->getUnconfirmedBalance())); - results.push_back(Pair("fullBalance: ",pwallet->sparkWallet->getFullBalance())); + results.push_back(Pair("availableBalance",pwallet->sparkWallet->getAvailableBalance())); + results.push_back(Pair("unconfirmedBalance",pwallet->sparkWallet->getUnconfirmedBalance())); + results.push_back(Pair("fullBalance",pwallet->sparkWallet->getFullBalance())); return results; } From ce55d0a1ffd222ec339724d37e83c920f7941bf4 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 22 Jan 2023 05:51:16 +0400 Subject: [PATCH 080/197] Crashing issue and failing tests fixed --- src/spark/sparkwallet.cpp | 50 +++++++++++++++++++++---------------- src/spark/sparkwallet.h | 7 +++--- src/test/lelantus_tests.cpp | 2 +- src/wallet/wallet.cpp | 3 ++- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index ab182208ae..d23ad9e831 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -1,3 +1,4 @@ +#include "../liblelantus/threadpool.h" #include "sparkwallet.h" #include "../wallet/wallet.h" #include "../wallet/coincontrol.h" @@ -9,7 +10,6 @@ #include "state.h" #include -#include const uint32_t DEFAULT_SPARK_NCOUNT = 1; @@ -59,6 +59,11 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { coinMeta = walletdb.ListSparkMints(); } } + threadPool = new ParallelOpThreadPool(boost::thread::hardware_concurrency()); +} + +CSparkWallet::~CSparkWallet() { + delete (ParallelOpThreadPool*)threadPool; } void CSparkWallet::resetDiversifierFromDB(CWalletDB& walletdb) { @@ -362,7 +367,7 @@ void CSparkWallet::UpdateSpendState(const GroupElement& lTag, const uint256& txH } void CSparkWallet::UpdateSpendStateFromMempool(const std::vector& lTags, const uint256& txHash, bool fUpdateMint) { - std::thread([=]() { + ((ParallelOpThreadPool*)threadPool)->PostTask([=]() { LOCK(cs_spark_wallet); for (const auto& lTag : lTags) { uint256 lTagHash = primitives::GetLTagHash(lTag); @@ -370,12 +375,12 @@ void CSparkWallet::UpdateSpendStateFromMempool(const std::vector& UpdateSpendState(lTag, lTagHash, txHash, fUpdateMint); } } - }).detach(); + }); } void CSparkWallet::UpdateSpendStateFromBlock(const CBlock& block) { const auto& transactions = block.vtx; - std::thread([=]() { + ((ParallelOpThreadPool*)threadPool)->PostTask([=]() { LOCK(cs_spark_wallet); for (const auto& tx : transactions) { if (tx->IsSparkSpend()) { @@ -391,7 +396,7 @@ void CSparkWallet::UpdateSpendStateFromBlock(const CBlock& block) { } } } - }).detach(); + }); } bool CSparkWallet::isMine(spark::Coin coin) const { @@ -429,7 +434,7 @@ CAmount CSparkWallet::getMySpendAmount(const std::vector& lTags) c return result; } -void CSparkWallet::UpdateMintState(const std::vector& coins, const uint256& txHash) { +void CSparkWallet::UpdateMintState(const std::vector& coins, const uint256& txHash, CWalletDB& walletdb) { spark::CSparkState *sparkState = spark::CSparkState::GetState(); for (auto coin : coins) { try { @@ -454,7 +459,6 @@ void CSparkWallet::UpdateMintState(const std::vector& coins, const mintMeta.isUsed = mempool.sparkState.HasLTag(recoveredCoinData.T); } - CWalletDB walletdb(strWalletFile); uint256 lTagHash = primitives::GetLTagHash(recoveredCoinData.T); addOrUpdateMint(mintMeta, lTagHash, walletdb); @@ -467,11 +471,11 @@ void CSparkWallet::UpdateMintState(const std::vector& coins, const UpdateSpendState(recoveredCoinData.T, lTagHash, spendTxHash, false); } - pwalletMain->NotifyZerocoinChanged( - pwalletMain, - lTagHash.GetHex(), - std::string("Update (") + std::to_string((double)mintMeta.v / COIN) + "mint)", - CT_UPDATED); +// pwalletMain->NotifyZerocoinChanged( +// pwalletMain, +// lTagHash.GetHex(), +// std::string("Update (") + std::to_string((double)mintMeta.v / COIN) + "mint)", +// CT_UPDATED); } catch (const std::runtime_error& e) { continue; } @@ -479,23 +483,27 @@ void CSparkWallet::UpdateMintState(const std::vector& coins, const } void CSparkWallet::UpdateMintStateFromMempool(const std::vector& coins, const uint256& txHash) { - std::thread([=]() { - UpdateMintState(coins, txHash); - }).detach(); + ((ParallelOpThreadPool*)threadPool)->PostTask([=]() mutable { + LOCK(cs_spark_wallet); + CWalletDB walletdb(strWalletFile); + UpdateMintState(coins, txHash, walletdb); + }); } void CSparkWallet::UpdateMintStateFromBlock(const CBlock& block) { const auto& transactions = block.vtx; - std::thread([=]() { + + ((ParallelOpThreadPool*)threadPool)->PostTask([=] () mutable { + LOCK(cs_spark_wallet); + CWalletDB walletdb(strWalletFile); for (const auto& tx : transactions) { if (tx->IsSparkTransaction()) { auto coins = spark::GetSparkMintCoins(*tx); - for (auto& coin : coins) { - uint256 txHash = tx->GetHash(); - UpdateMintState(coins, txHash); - } + uint256 txHash = tx->GetHash(); + UpdateMintState(coins, txHash, walletdb); } - }}).detach(); + } + }); } void CSparkWallet::RemoveSparkMints(const std::vector& mints) { diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 1ad774968e..1277d11919 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -15,7 +15,6 @@ class CRecipient; class CReserveKey; class CCoinControl; - extern CChain chainActive; const uint32_t BIP44_SPARK_INDEX = 0x6; @@ -23,7 +22,7 @@ const uint32_t BIP44_SPARK_INDEX = 0x6; class CSparkWallet { public: CSparkWallet(const std::string& strWalletFile); - + ~CSparkWallet(); // increment diversifier and generate address for that spark::Address generateNextAddress(); spark::Address generateNewAddress(); @@ -81,7 +80,7 @@ class CSparkWallet { void UpdateSpendState(const GroupElement& lTag, const uint256& txHash, bool fUpdateMint = true); void UpdateSpendStateFromMempool(const std::vector& lTags, const uint256& txHash, bool fUpdateMint = true); void UpdateSpendStateFromBlock(const CBlock& block); - void UpdateMintState(const std::vector& coins, const uint256& txHash); + void UpdateMintState(const std::vector& coins, const uint256& txHash, CWalletDB& walletdb); void UpdateMintStateFromMempool(const std::vector& coins, const uint256& txHash); void UpdateMintStateFromBlock(const CBlock& block); void RemoveSparkMints(const std::vector& mints); @@ -143,6 +142,8 @@ class CSparkWallet { // map lTagHash to coin meta std::unordered_map coinMeta; + + void* threadPool; }; diff --git a/src/test/lelantus_tests.cpp b/src/test/lelantus_tests.cpp index a11dd36e43..c7a6507920 100644 --- a/src/test/lelantus_tests.cpp +++ b/src/test/lelantus_tests.cpp @@ -371,7 +371,7 @@ BOOST_AUTO_TEST_CASE(connect_and_disconnect_block) ActivateBestChain(state, ::Params(), sharedBlock); }; - GenerateBlocks(1000); + GenerateBlocks(400); std::vector mintTxs; auto hdMints = GenerateMints({3 * COIN, 3 * COIN, 3 * COIN}, mintTxs); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c045b2d242..3ab9fcc391 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -7569,6 +7569,7 @@ void CWallet::HandleSparkTransaction(CWalletTx const & wtx) { return; uint256 txHash = wtx.GetHash(); + CWalletDB walletdb(strWalletFile); // get spend linking tags and add to spark wallet if (wtx.tx->IsSparkSpend()) { @@ -7581,7 +7582,7 @@ void CWallet::HandleSparkTransaction(CWalletTx const & wtx) { // get spark coins and add into wallet std::vector coins = spark::GetSparkMintCoins(*wtx.tx); - sparkWallet->UpdateMintState(coins, txHash); + sparkWallet->UpdateMintState(coins, txHash, walletdb); } void CWallet::LabelSendingPcode(bip47::CPaymentCode const & pcode_, std::string const & label, bool remove) From 9796f26db24e19d79cf7533f396dd77b6f230509 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 23 Jan 2023 05:18:01 +0400 Subject: [PATCH 081/197] Fixing unstable unittest --- src/spark/sparkwallet.cpp | 10 +++++----- src/test/spark_tests.cpp | 4 ++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index d23ad9e831..8af90e73ef 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -353,11 +353,11 @@ void CSparkWallet::UpdateSpendState(const GroupElement& lTag, const uint256& lTa addOrUpdateMint(mintMeta, lTagHash, walletdb); } - pwalletMain->NotifyZerocoinChanged( - pwalletMain, - lTagHash.GetHex(), - std::string("used (") + std::to_string((double)mintMeta.v / COIN) + "mint)", - CT_UPDATED); +// pwalletMain->NotifyZerocoinChanged( +// pwalletMain, +// lTagHash.GetHex(), +// std::string("used (") + std::to_string((double)mintMeta.v / COIN) + "mint)", +// CT_UPDATED); } } diff --git a/src/test/spark_tests.cpp b/src/test/spark_tests.cpp index a0ad2c79bb..3ce12a9479 100644 --- a/src/test/spark_tests.cpp +++ b/src/test/spark_tests.cpp @@ -367,6 +367,10 @@ BOOST_AUTO_TEST_CASE(connect_and_disconnect_block) } auto sTx1 = GenerateSparkSpend({1 * COIN}, {}, &coinControl); + + // wait while another thread updates mint status in wallet, and then continue + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + // Update isused status { CSparkMintMeta meta = pwalletMain->sparkWallet->getMintMeta(mints[1].k); From 2a0d5281c1f46467ad646d20aec48fcca78817d6 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 13 Feb 2023 12:07:13 +0400 Subject: [PATCH 082/197] Fixed remining conflict --- src/spark/sparkwallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 8af90e73ef..30c256222f 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -1332,7 +1332,7 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( // check fee wtxNew.SetTx(MakeTransactionRef(std::move(tx))); - if (GetTransactionWeight(tx) >= MAX_LELANTUS_TX_WEIGHT) { + if (GetTransactionWeight(tx) >= MAX_NEW_TX_WEIGHT) { throw std::runtime_error(_("Transaction too large")); } From 03c0b75f6b04830cfd253b964fa7b6693dfdad3e Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 19 Feb 2023 21:23:26 +0400 Subject: [PATCH 083/197] Adding several more unitests --- src/Makefile.test.include | 1 + src/wallet/test/spark_tests.cpp | 324 ++++++++++++++++++++++++++++++++ 2 files changed, 325 insertions(+) create mode 100644 src/wallet/test/spark_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 87f5cb7e53..792225ceaf 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -203,6 +203,7 @@ BITCOIN_TESTS += \ wallet/test/wallet_tests.cpp \ wallet/test/crypto_tests.cpp \ wallet/test/lelantus_tests.cpp \ + wallet/test/spark_tests.cpp \ wallet/test/sigma_tests.cpp \ wallet/test/mnemonic_tests.cpp \ wallet/test/txbuilder_tests.cpp diff --git a/src/wallet/test/spark_tests.cpp b/src/wallet/test/spark_tests.cpp new file mode 100644 index 0000000000..f764f0349c --- /dev/null +++ b/src/wallet/test/spark_tests.cpp @@ -0,0 +1,324 @@ +#include <../../test/fixtures.h> +#include "../wallet.h" +#include "../../spark/sparkwallet.h" +#include "../../validation.h" + +#include + +static std::vector random_char_vector() +{ + Scalar temp; + temp.randomize(); + std::vector result; + result.resize(spark::SCALAR_ENCODING); + temp.serialize(result.data()); + return result; +} + +CBlock GetCBlock(CBlockIndex const *blockIdx) +{ + CBlock block; + if (!ReadBlockFromDisk(block, blockIdx, ::Params().GetConsensus())) { + throw std::invalid_argument("No block index data"); + } + + return block; +} + +void ExtractSpend(CTransaction const &tx, + std::vector& coins, + std::vector& lTags) { + + if (tx.vin[0].scriptSig.IsSparkSpend()) { + coins.clear(); + coins = spark::GetSparkMintCoins(tx); + lTags.clear(); + lTags = spark::GetSparkUsedTags(tx); + } +} + +BOOST_FIXTURE_TEST_SUITE(spark_tests, SparkTestingSetup) + +BOOST_AUTO_TEST_CASE(create_mint_recipient) +{ + const uint64_t v = 1; + spark::Address sparkAddress = pwalletMain->sparkWallet->getDefaultAddress(); + + spark::MintedCoinData data; + data.address = sparkAddress; + data.v = v; + data.memo = "Test memo"; + + std::vector mintedCoins; + mintedCoins.push_back(data); + + auto recipients = CSparkWallet::CreateSparkMintRecipients(mintedCoins, random_char_vector(), true); + + BOOST_CHECK(recipients[0].scriptPubKey.IsSparkMint()); + BOOST_CHECK_EQUAL(recipients[0].nAmount, v); +} + +BOOST_AUTO_TEST_CASE(mint_and_store_spark) +{ + pwalletMain->SetBroadcastTransactions(true); + GenerateBlocks(1001); + + std::vector> wtxAndFee; + + const uint64_t v = 1; + spark::Address sparkAddress = pwalletMain->sparkWallet->getDefaultAddress(); + + spark::MintedCoinData data; + data.address = sparkAddress; + data.v = v; + data.memo = "Test memo"; + + std::vector mintedCoins; + mintedCoins.push_back(data); + + std::string result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee); + BOOST_CHECK_EQUAL(result, ""); + + size_t mintAmount = 0; + for (const auto& wtx : wtxAndFee) { + auto tx = wtx.first.tx.get(); + + BOOST_CHECK(tx->IsSparkMint()); + BOOST_CHECK(tx->IsSparkTransaction()); + + for (const auto& out : tx->vout) { + if (out.scriptPubKey.IsSparkMint()) { + mintAmount += out.nValue; + } + } + CMutableTransaction mtx(*tx); + BOOST_CHECK(GenerateBlock({mtx})); + } + + BOOST_CHECK_EQUAL(data.v, mintAmount); + + auto sparkState = spark::CSparkState::GetState(); + sparkState->Reset(); +} + +BOOST_AUTO_TEST_CASE(list_spark_mints) +{ + GenerateBlocks(1001); + std::vector confirmedAmounts = {1, 2 * COIN}; + std::vector unconfirmedAmounts = {10 * COIN}; + std::vector allAmounts(confirmedAmounts); + allAmounts.insert(allAmounts.end(), unconfirmedAmounts.begin(), unconfirmedAmounts.end()); + + std::vector txs; + auto mints = GenerateMints(allAmounts, txs); + std::vector inTxs(txs.begin(), txs.begin() + txs.size() - 1); + + auto bIndex = GenerateBlock(inTxs); + BOOST_CHECK(bIndex); + + auto block = GetCBlock(bIndex); + pwalletMain->sparkWallet->UpdateMintStateFromBlock(block); + + auto extractAmountsFromAvailableCoins = [](std::vector const &coins) -> std::vector { + std::vector amounts; + for (auto const &coin : coins) { + amounts.push_back(coin.v); + } + + return amounts; + }; + + std::vector confirmedCoins = pwalletMain->sparkWallet->ListSparkMints(true, true); + std::vector allCoins = pwalletMain->sparkWallet->ListSparkMints(true, false); + auto confirmed = extractAmountsFromAvailableCoins(confirmedCoins); + auto all = extractAmountsFromAvailableCoins(allCoins); + + BOOST_CHECK(std::is_permutation(confirmed.begin(), confirmed.end(), confirmedAmounts.begin())); + BOOST_CHECK(std::is_permutation(all.begin(), all.end(), allAmounts.begin())); + + // get mint + CSparkMintMeta mint = pwalletMain->sparkWallet->getMintMeta(mints.front().k); + BOOST_CHECK(mint.v == mints.front().v); + + auto sparkState = spark::CSparkState::GetState(); + sparkState->Reset(); +} + + +BOOST_AUTO_TEST_CASE(spend) +{ + pwalletMain->SetBroadcastTransactions(true); + GenerateBlocks(1001); + const uint64_t v = 2 * COIN; + + spark::Address sparkAddress = pwalletMain->sparkWallet->getDefaultAddress(); + + spark::MintedCoinData data; + data.address = sparkAddress; + data.v = v; + data.memo = "Test memo"; + + std::vector mintedCoins; + mintedCoins.push_back(data); + + std::vector> wtxAndFee; + std::string result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee); + + std::vector> wtxAndFee2; + pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee2); + + BOOST_CHECK_EQUAL("", result); + + CMutableTransaction mutableTx(*(wtxAndFee[0].first.tx)); + CMutableTransaction mutableTx2(*(wtxAndFee2[0].first.tx)); + GenerateBlock({mutableTx, mutableTx2}, &script); + GenerateBlocks(5); + BOOST_CHECK_EQUAL(1, wtxAndFee.size()); + wtxAndFee.clear(); + + auto spTx = GenerateSparkSpend({1 * COIN}, {}, nullptr); + BOOST_CHECK(!(spTx.empty())); + + std::vector coins; + std::vector tags; + ExtractSpend(spTx[0], coins, tags); + + BOOST_CHECK_EQUAL(1, coins.size()); + BOOST_CHECK_EQUAL(1, tags.size()); + + auto sparkState = spark::CSparkState::GetState(); + sparkState->Reset(); +} + +BOOST_AUTO_TEST_CASE(mintspark_and_mint_all) +{ + auto countMintsInBalance = [&]( + std::vector> const& wtxs, + bool includeFee = false) -> CAmount { + + CAmount sum = 0; + for (auto const &w : wtxs) { + for (auto const &out : w.first.tx->vout) { + if (out.scriptPubKey.IsSparkMint()) { + sum += out.nValue; + } + } + + if (includeFee) { + sum += w.second; + } + } + return sum; + }; + + auto getAvailableCoinsForMintBalance = [&]() -> CAmount { + std::vector>> valueAndUTXO; + pwalletMain->AvailableCoinsForLMint(valueAndUTXO, nullptr); + CAmount s = 0; + + for (auto const &v : valueAndUTXO) { + s += v.first; + } + + return s; + }; + + CScript externalScript; + { + uint160 seed; + GetRandBytes(seed.begin(), seed.size()); + + externalScript = GetScriptForDestination(CKeyID(seed)); + } + + auto generateBlocksPerScripts = [&](size_t blocks, size_t blocksPerScript) -> std::vector { + LOCK2(cs_main, pwalletMain->cs_wallet); + std::vector scripts; + while (blocks != 0) { + CPubKey key; + { + LOCK(pwalletMain->cs_wallet); + key = pwalletMain->GenerateNewKey(); + } + scripts.push_back(GetScriptForDestination(key.GetID())); + auto blockCount = std::min(blocksPerScript, blocks); + GenerateBlocks(blockCount, &scripts.back()); + blocks -= blockCount; + } + + return scripts; + }; + + auto scripts = generateBlocksPerScripts(200, 10); + GenerateBlocks(100, &externalScript); + + std::vector> wtxAndFee; + const uint64_t v = 10 * COIN; + + spark::Address sparkAddress = pwalletMain->sparkWallet->getDefaultAddress(); + + spark::MintedCoinData data; + data.address = sparkAddress; + data.v = v; + data.memo = "Test memo"; + std::vector mintedCoins; + mintedCoins.push_back(data); + + auto result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee); + BOOST_CHECK_EQUAL("", result); + BOOST_CHECK_EQUAL(1, wtxAndFee.size()); + BOOST_CHECK_EQUAL(10 * COIN, countMintsInBalance(wtxAndFee)); + wtxAndFee.clear(); + mintedCoins.clear(); + + data.v = 600 * COIN;; + mintedCoins.clear(); + mintedCoins.push_back(data); + + result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee); + BOOST_CHECK_EQUAL("", result); + BOOST_CHECK_GT(wtxAndFee.size(), 1); + BOOST_CHECK_EQUAL(600 * COIN, countMintsInBalance(wtxAndFee)); + + wtxAndFee.clear(); + mintedCoins.clear(); + + auto balance = getAvailableCoinsForMintBalance(); + BOOST_CHECK_GT(balance, 0); + + result = pwalletMain->MintAndStoreSpark({}, wtxAndFee, true); + BOOST_CHECK_EQUAL("", result); + BOOST_CHECK_GT(balance, countMintsInBalance(wtxAndFee)); + BOOST_CHECK_EQUAL(balance, countMintsInBalance(wtxAndFee, true)); + BOOST_CHECK_EQUAL(0, getAvailableCoinsForMintBalance()); + + scripts = generateBlocksPerScripts(500, 200); + GenerateBlocks(100, &externalScript); + + wtxAndFee.clear(); + mintedCoins.clear(); + balance = getAvailableCoinsForMintBalance(); + BOOST_CHECK_GT(balance, 0); + + result = pwalletMain->MintAndStoreSpark({ }, wtxAndFee, true); + BOOST_CHECK_EQUAL("", result); + BOOST_CHECK_GT(balance, countMintsInBalance(wtxAndFee)); + BOOST_CHECK_EQUAL(balance, countMintsInBalance(wtxAndFee, true)); + BOOST_CHECK_EQUAL(0, pwalletMain->GetBalance()); + + // Scripts of all changes should unique + std::set changeScripts; + for (auto const &wtx : wtxAndFee) { + for (auto const &out : wtx.first.tx->vout) { + if (!out.scriptPubKey.IsSparkMint()) { + BOOST_CHECK(!changeScripts.count(out.scriptPubKey)); + changeScripts.insert(out.scriptPubKey); + } + } + } + + auto sparkState = spark::CSparkState::GetState(); + sparkState->Reset(); +} + +BOOST_AUTO_TEST_SUITE_END() From db1a27d7d99850e51d5a35e82da1e03862a765af Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 20 Feb 2023 05:18:43 +0400 Subject: [PATCH 084/197] Adding one more rpc test --- qa/rpc-tests/spark_mintspend.py | 123 ++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100755 qa/rpc-tests/spark_mintspend.py diff --git a/qa/rpc-tests/spark_mintspend.py b/qa/rpc-tests/spark_mintspend.py new file mode 100755 index 0000000000..4341b884ad --- /dev/null +++ b/qa/rpc-tests/spark_mintspend.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +from decimal import * + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from time import * + +class SparkMintSpendTest(BitcoinTestFramework): + def __init__(self): + super().__init__() + self.num_nodes = 4 + self.setup_clean_chain = False + + def run_test(self): + # Decimal formating: 6 digits for balance will be enought 000.000 + getcontext().prec = 6 + self.nodes[0].generate(801) + self.sync_all() + + start_bal = self.nodes[0].getbalance() + + sparkAddress = self.nodes[0].getnewsparkaddress()[0] + + mint_trans = list() + mint_trans.append(self.nodes[0].mintspark({sparkAddress: [1, "Test memo"]})) + mint_trans.append(self.nodes[0].mintspark({sparkAddress: [2, "Test memo"]})) + + # Get last added transaction and fee for it + info = self.nodes[0].gettransaction(mint_trans[-1][0]) + + # fee in transaction is negative + fee = -(info['fee'] * 2) + cur_bal = self.nodes[0].getbalance() + start_bal = float(start_bal) - float(fee) - 3 + start_bal = Decimal(format(start_bal, '.8f')) + + assert start_bal == cur_bal, \ + 'Unexpected current balance: {}, should be minus two mints and two fee, ' \ + 'but start was {}'.format(cur_bal, start_bal) + + for tr in mint_trans: + info = self.nodes[0].gettransaction(tr[0]) + confrms = info['confirmations'] + assert confrms == 0, \ + 'Confirmations should be {}, ' \ + 'due to {} blocks was generated after transaction was created,' \ + 'but was {}'.format(0, 0, confrms) + + tr_type = info['details'][0]['category'] + assert tr_type == 'send', 'Unexpected transaction type: {}'.format(tr_type) + # assert(self.wait_for_instantlock(tr, self.nodes[0])) + + self.nodes[0].generate(2) + self.sync_all() + + res = False + firoAddress = self.nodes[0].getnewaddress() + try: + res = self.nodes[0].spendspark({firoAddress: [1, False]}, {}) + except JSONRPCException as ex: + assert ex.error['message'] == 'Insufficient funds' + + assert res, 'spendspark successfully called' + + # generate last confirmation block - now all transactions should be confimed + self.nodes[0].generate(1) + self.sync_all() + + for tr in mint_trans: + info = self.nodes[0].gettransaction(tr[0]) + confrms = info['confirmations'] + assert confrms >= 1, \ + 'Confirmations should be 3, ' \ + 'due to 3 blocks was generated after transaction was created,' \ + 'but was {}.'.format(confrms) + tr_type = info['details'][0]['category'] + assert tr_type == 'send', 'Unexpected transaction type' + + spend_trans = list() + spend_total = Decimal(0) + + self.sync_all() + + spend_trans.append(self.nodes[0].spendspark({firoAddress: [1, False]}, {})) + + info = self.nodes[0].gettransaction(spend_trans[-1][0]) + confrms = info['confirmations'] + tr_type = info['details'][0]['category'] + + assert confrms == 0, \ + 'Confirmations should be 0, ' \ + 'due to 0 blocks was generated after transaction was created,' \ + 'but was {}.'.format(confrms) + + assert tr_type == 'send', 'Unexpected transaction type' + + before_bal = self.nodes[0].getbalance() + self.nodes[0].generate(1) + self.sync_all() + after_new = self.nodes[0].getbalance() + delta = after_new - before_bal + start_bal = before_bal + delta + cur_bal = Decimal(format(self.nodes[0].getbalance(), '.1f')) + spend_total = Decimal(format(1, '.8f')) + + assert start_bal == cur_bal, \ + 'Unexpected current balance: {}, should increase on {}, ' \ + 'but start was {}'.format(cur_bal, spend_total, start_bal) + + for tr in spend_trans: + info = self.nodes[0].gettransaction(tr[0]) + + confrms = info['confirmations'] + tr_type = info['details'][0]['category'] + assert confrms >= 1, \ + 'Confirmations should be 1 or more, ' \ + 'due to 1 blocks was generated after transaction was created,' \ + 'but was {}.'.format(confrms) + assert tr_type == 'send', 'Unexpected transaction type' + + +if __name__ == '__main__': + SparkMintSpendTest().main() From a77d07f0c05db97fe01b8ac5a24ba610b9e6e31c Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 20 Feb 2023 07:03:15 +0400 Subject: [PATCH 085/197] Keeping some additional data in block index for mobile/light wallet --- src/chain.h | 7 +++++ src/spark/state.cpp | 62 +++++++++++++++++++++++++++++++++++++++++++ src/spark/state.h | 9 +++++++ src/txdb.cpp | 1 + src/wallet/wallet.cpp | 1 + 5 files changed, 80 insertions(+) diff --git a/src/chain.h b/src/chain.h index 80e4b10a1f..387752f019 100644 --- a/src/chain.h +++ b/src/chain.h @@ -251,6 +251,8 @@ class CBlockIndex std::map> sparkMintedCoins; //! Map id to std::map> sparkSetHash; + //! map spark coin S to tx hash, this is used when you run with -mobile + std::unordered_map sparkTxHash; //! Values of coin serials spent in this block sigma::spend_info_container sigmaSpentSerials; @@ -297,6 +299,7 @@ class CBlockIndex sparkMintedCoins.clear(); sparkSetHash.clear(); spentLTags.clear(); + sparkTxHash.clear(); sigmaSpentSerials.clear(); lelantusSpentSerials.clear(); activeDisablingSporks.clear(); @@ -549,6 +552,10 @@ class CDiskBlockIndex : public CBlockIndex READWRITE(sparkMintedCoins); READWRITE(sparkSetHash); READWRITE(spentLTags); + + if (GetBoolArg("-mobile", false)) { + READWRITE(sparkTxHash); + } } diff --git a/src/spark/state.cpp b/src/spark/state.cpp index bffa330f35..3859373fdf 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -827,6 +827,12 @@ bool CheckSparkTransaction( return true; } +uint256 GetTxHashFromCoin(const spark::Coin& coin) { + COutPoint outPoint; + GetOutPoint(outPoint, coin); + return outPoint.hash; +} + bool GetOutPoint(COutPoint& outPoint, const spark::Coin& coin) { spark::CSparkState *sparkState = spark::CSparkState::GetState(); @@ -1042,6 +1048,9 @@ void CSparkState::AddMintsToStateAndBlockIndex( AddMint(mint, CMintedCoinInfo::make(latestCoinId, index->nHeight)); LogPrintf("AddMintsToStateAndBlockIndex: Spark mint added id=%d\n", latestCoinId); index->sparkMintedCoins[latestCoinId].push_back(mint); + if (GetBoolArg("-mobile", false)) { + index->sparkTxHash[mint.S] = GetTxHashFromCoin(mint); + } } } @@ -1277,6 +1286,59 @@ int CSparkState::GetCoinSetForSpend( return numberOfCoins; } +void CSparkState::GetCoinsForRecovery( + CChain *chain, + int maxHeight, + int coinGroupID, + std::string start_block_hash, + uint256& blockHash_out, + std::vector> coins, + std::vector& setHash_out) { + coins.clear(); + if (coinGroups.count(coinGroupID) == 0) { + return; + } + SparkCoinGroupInfo &coinGroup = coinGroups[coinGroupID]; + int numberOfCoins = 0; + for (CBlockIndex *block = coinGroup.lastBlock;; block = block->pprev) { + // ignore block heigher than max height + if (block->nHeight > maxHeight) { + continue; + } + if (block->GetBlockHash().GetHex() == start_block_hash) { + break; + } + // check coins in group coinGroupID - 1 in the case that using coins from prev group. + int id = 0; + if (CountCoinInBlock(block, coinGroupID)) { + id = coinGroupID; + } else if (CountCoinInBlock(block, coinGroupID - 1)) { + id = coinGroupID - 1; + } + if (id) { + if (numberOfCoins == 0) { + // latest block satisfying given conditions + // remember block hash and set hash + blockHash_out = block->GetBlockHash(); + setHash_out = GetAnonymitySetHash(block, id); + } + numberOfCoins += block->sparkMintedCoins[id].size(); + if (block->sparkMintedCoins.count(id) > 0) { + for (const auto &coin : block->sparkMintedCoins[id]) { + uint256 txHash; + if (block->sparkTxHash.count(coin.S)) + txHash = block->sparkTxHash[coin.S]; + coins.push_back({coin, txHash}); + } + } + } + if (block == coinGroup.firstBlock) { + break ; + } + } +} + + std::unordered_map const & CSparkState::GetMints() const { return mintedCoins; } diff --git a/src/spark/state.h b/src/spark/state.h index 9a44228f2a..7d1b16d34a 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -200,6 +200,15 @@ class CSparkState { std::vector& coins_out, std::vector& setHash_out); + void GetCoinsForRecovery( + CChain *chain, + int maxHeight, + int coinGroupID, + std::string start_block_hash, + uint256& blockHash_out, + std::vector> coins, + std::vector& setHash_out); + std::unordered_map const & GetMints() const; std::unordered_map const & GetSpends() const; std::unordered_map const & GetCoinGroups() const; diff --git a/src/txdb.cpp b/src/txdb.cpp index ea61713c24..0960850b01 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -401,6 +401,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts(boost::functionsparkMintedCoins = diskindex.sparkMintedCoins; pindexNew->sparkSetHash = diskindex.sparkSetHash; pindexNew->spentLTags = diskindex.spentLTags; + pindexNew->sparkTxHash = diskindex.sparkTxHash; pindexNew->activeDisablingSporks = diskindex.activeDisablingSporks; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 3ab9fcc391..61e686a66f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -6749,6 +6749,7 @@ std::string CWallet::GetWalletHelpString(bool showDebug) strUsage += HelpMessageOpt("-mnemonicpassphrase=", _("User defined mnemonic passphrase for HD wallet (BIP39). Only has effect during wallet creation/first start (default: empty string)")); strUsage += HelpMessageOpt("-hdseed=", _("User defined seed for HD wallet (should be in hex). Only has effect during wallet creation/first start (default: randomly generated)")); strUsage += HelpMessageOpt("-batching", _("In case of sync/reindex verifies sigma/lelantus proofs with batch verification, default: true")); + strUsage += HelpMessageOpt("-mobile", _("Use this argument when you want to keep additional data in block index for mobile api, default: false")); strUsage += HelpMessageOpt("-walletrbf", strprintf(_("Send transactions with full-RBF opt-in enabled (default: %u)"), DEFAULT_WALLET_RBF)); strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup")); strUsage += HelpMessageOpt("-wallet=", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT)); From 0542177a0383304bf37fe52592833b79ef595e48 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Wed, 22 Feb 2023 09:01:56 +0400 Subject: [PATCH 086/197] Memo encryption fixed and test added --- src/libspark/coin.cpp | 4 ++-- src/libspark/test/coin_test.cpp | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libspark/coin.cpp b/src/libspark/coin.cpp index 34b73005c0..6ced04d761 100644 --- a/src/libspark/coin.cpp +++ b/src/libspark/coin.cpp @@ -63,7 +63,7 @@ Coin::Coin( MintCoinRecipientData r; r.d = address.get_d(); r.k = k; - r.memo = std::string(memo.begin(), memo.end()); + r.memo = std::string(padded_memo.begin(), padded_memo.end()); CDataStream r_stream(SER_NETWORK, PROTOCOL_VERSION); r_stream << r; this->r_ = AEAD::encrypt(address.get_Q1()*SparkUtils::hash_k(k), "Mint coin data", r_stream); @@ -73,7 +73,7 @@ Coin::Coin( r.v = v; r.d = address.get_d(); r.k = k; - r.memo = std::string(memo.begin(), memo.end()); + r.memo = std::string(padded_memo.begin(), padded_memo.end()); CDataStream r_stream(SER_NETWORK, PROTOCOL_VERSION); r_stream << r; this->r_ = AEAD::encrypt(address.get_Q1()*SparkUtils::hash_k(k), "Spend coin data", r_stream); diff --git a/src/libspark/test/coin_test.cpp b/src/libspark/test/coin_test.cpp index c162ecdc9c..522ea29f2a 100644 --- a/src/libspark/test/coin_test.cpp +++ b/src/libspark/test/coin_test.cpp @@ -57,8 +57,8 @@ BOOST_AUTO_TEST_CASE(mint_identify_recover) BOOST_CHECK_EQUAL_COLLECTIONS(i_data.d.begin(), i_data.d.end(), address.get_d().begin(), address.get_d().end()); BOOST_CHECK_EQUAL(i_data.v, v); BOOST_CHECK_EQUAL(i_data.k, k); - BOOST_CHECK_EQUAL(i_data.memo, memo); - + BOOST_CHECK_EQUAL(strcmp(memo.c_str(), i_data.memo.c_str()), 0); // compare strings in a lexicographical manner, as we pad the memo in the coin + BOOST_CHECK_EQUAL(i_data.memo.size(), params->get_memo_bytes()); // check that it is padded // Recover coin RecoveredCoinData r_data = coin.recover(full_view_key, i_data); BOOST_CHECK_EQUAL( @@ -105,7 +105,8 @@ BOOST_AUTO_TEST_CASE(spend_identify_recover) BOOST_CHECK_EQUAL_COLLECTIONS(i_data.d.begin(), i_data.d.end(), address.get_d().begin(), address.get_d().end()); BOOST_CHECK_EQUAL(i_data.v, v); BOOST_CHECK_EQUAL(i_data.k, k); - BOOST_CHECK_EQUAL(i_data.memo, memo); + BOOST_CHECK_EQUAL(strcmp(memo.c_str(), i_data.memo.c_str()), 0); // compare strings in a lexicographical manner, as we pad the memo in the coin + BOOST_CHECK_EQUAL(i_data.memo.size(), params->get_memo_bytes()); // check that it is padded // Recover coin RecoveredCoinData r_data = coin.recover(full_view_key, i_data); From 60343033bb259243a52f6ee15cfaf25a47f5302f Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 23 Feb 2023 09:49:56 +0400 Subject: [PATCH 087/197] Keep additional mint data for Lelantus only when -mobile passed --- src/chain.h | 12 ++++++++++-- src/lelantus.cpp | 42 +++++++++++++++++++++++++++--------------- src/txdb.cpp | 1 + 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/chain.h b/src/chain.h index 387752f019..8c39b5c970 100644 --- a/src/chain.h +++ b/src/chain.h @@ -244,7 +244,10 @@ class CBlockIndex //! Maps to vector of public coins std::map, std::vector> sigmaMintedPubCoins; //! Map id to - std::map>>> lelantusMintedPubCoins; + std::map>> lelantusMintedPubCoins; + + std::unordered_map lelantusMintData; + //! Map id to std::map> anonymitySetHash; //! Map id to spark coin @@ -295,6 +298,7 @@ class CBlockIndex sigmaMintedPubCoins.clear(); lelantusMintedPubCoins.clear(); + lelantusMintData.clear(); anonymitySetHash.clear(); sparkMintedCoins.clear(); sparkSetHash.clear(); @@ -536,11 +540,15 @@ class CDiskBlockIndex : public CBlockIndex for(auto& itr : lelantusPubCoins) { if(!itr.second.empty()) { for(auto& coin : itr.second) - lelantusMintedPubCoins[itr.first].push_back(std::make_pair(coin, std::make_pair(lelantus::MintValueData(), uint256()))); + lelantusMintedPubCoins[itr.first].push_back(std::make_pair(coin,uint256())); } } } else READWRITE(lelantusMintedPubCoins); + if (GetBoolArg("-mobile", false)) { + READWRITE(lelantusMintData); + } + READWRITE(lelantusSpentSerials); if (nHeight >= params.nLelantusFixesStartBlock) diff --git a/src/lelantus.cpp b/src/lelantus.cpp index c2a3f7ee4a..1e21491074 100644 --- a/src/lelantus.cpp +++ b/src/lelantus.cpp @@ -1232,20 +1232,24 @@ void CLelantusState::AddMintsToStateAndBlockIndex( CBlockIndex *index, const CBlock* pblock) { - std::vector>> blockMints; - for (const auto& mint : pblock->lelantusTxInfo->mints) { - lelantus::MintValueData mintdata; - mintdata.amount = mint.second.first; - if (pblock->lelantusTxInfo->encryptedJmintValues.count(mint.first) > 0) { - mintdata.isJMint = true; - mintdata.encryptedValue = pblock->lelantusTxInfo->encryptedJmintValues[mint.first]; - } + std::vector> blockMints; + std::unordered_map lelantusMintData; - COutPoint outPoint; - GetOutPointFromBlock(outPoint, mint.first.getValue(), *pblock); - mintdata.txHash = outPoint.hash; + for (const auto& mint : pblock->lelantusTxInfo->mints) { + if (GetBoolArg("-mobile", false)) { + lelantus::MintValueData mintdata; + mintdata.amount = mint.second.first; + if (pblock->lelantusTxInfo->encryptedJmintValues.count(mint.first) > 0) { + mintdata.isJMint = true; + mintdata.encryptedValue = pblock->lelantusTxInfo->encryptedJmintValues[mint.first]; + } - blockMints.push_back(std::make_pair(mint.first, std::make_pair(mintdata, mint.second.second))); + COutPoint outPoint; + GetOutPointFromBlock(outPoint, mint.first.getValue(), *pblock); + mintdata.txHash = outPoint.hash; + lelantusMintData[mint.first.getValue()] = mintdata; + } + blockMints.push_back(std::make_pair(mint.first, mint.second.second)); } latestCoinId = std::max(1, latestCoinId); @@ -1280,10 +1284,14 @@ void CLelantusState::AddMintsToStateAndBlockIndex( } for (const auto& mint : blockMints) { - containers.AddMint(mint.first, CMintedCoinInfo::make(latestCoinId, index->nHeight), mint.second.second); + containers.AddMint(mint.first, CMintedCoinInfo::make(latestCoinId, index->nHeight), mint.second); LogPrintf("AddMintsToStateAndBlockIndex: Lelantus mint added id=%d\n", latestCoinId); index->lelantusMintedPubCoins[latestCoinId].push_back(mint); + + if (GetBoolArg("-mobile", false)) { + index->lelantusMintData[mint.first.getValue()] = lelantusMintData[mint.first.getValue()]; + } } } @@ -1315,7 +1323,7 @@ void CLelantusState::AddBlock(CBlockIndex *index) { latestCoinId = pubCoins.first; for (auto const &coin : pubCoins.second) { - containers.AddMint(coin.first, CMintedCoinInfo::make(pubCoins.first, index->nHeight), coin.second.second); + containers.AddMint(coin.first, CMintedCoinInfo::make(pubCoins.first, index->nHeight), coin.second); } } @@ -1565,7 +1573,11 @@ void CLelantusState::GetCoinsForRecovery( } } - coins.push_back(coin); + lelantus::MintValueData lelantusMintData; + if (block->lelantusMintData.count(coin.first.getValue())) + lelantusMintData = block->lelantusMintData[coin.first.getValue()]; + coins.push_back(std::make_pair(coin.first, std::make_pair(lelantusMintData, coin.second))); + } } } diff --git a/src/txdb.cpp b/src/txdb.cpp index 0960850b01..931711fc9b 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -395,6 +395,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts(boost::functionsigmaSpentSerials = diskindex.sigmaSpentSerials; pindexNew->lelantusMintedPubCoins = diskindex.lelantusMintedPubCoins; + pindexNew->lelantusMintData = diskindex.lelantusMintData; pindexNew->lelantusSpentSerials = diskindex.lelantusSpentSerials; pindexNew->anonymitySetHash = diskindex.anonymitySetHash; From a16a56c8cee7560001a17954c82bf3a0a3503a6a Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sat, 25 Feb 2023 09:30:49 +0400 Subject: [PATCH 088/197] Spark mobile api --- src/firo_params.h | 2 +- src/rpc/misc.cpp | 197 +++++++++++++++++++++++++++++++++++++++ src/rpc/server.cpp | 7 ++ src/rpc/server.h | 5 + src/wallet/rpcwallet.cpp | 6 +- 5 files changed, 212 insertions(+), 5 deletions(-) diff --git a/src/firo_params.h b/src/firo_params.h index e18eda42be..bfff2f1f04 100644 --- a/src/firo_params.h +++ b/src/firo_params.h @@ -182,7 +182,7 @@ static const int64_t DUST_HARD_LIMIT = 1000; // 0.00001 FIRO mininput #define DANDELION_FLUFF 10 // Spark -#define SPARK_START_BLOCK 550000 +#define SPARK_START_BLOCK 700000 #define SPARK_TESTNET_START_BLOCK 150000 #define LELANTUS_GRACEFUL_PERIOD 600000 #define LELANTUS_TESTNET_GRACEFUL_PERIOD 200000 diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index c0a7494053..027f00017e 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1136,6 +1136,197 @@ UniValue getlatestcoinid(const JSONRPCRequest& request) return UniValue(latestCoinId); } +UniValue getsparkanonymityset(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 2) + throw std::runtime_error( + "getsparkanonymityset\n" + "\nReturns the anonymity set and latest block hash.\n" + "\nArguments:\n" + "{\n" + " \"coinGroupId\" (int)\n" + " \"startBlockHash\" (string)\n" // if this is empty it returns the full set + "}\n" + "\nResult:\n" + "{\n" + " \"blockHash\" (string) Latest block hash for anonymity set\n" + " \"setHash\" (string) Anonymity set hash\n" + " \"mints\" (Pair) Serialized Spark coin paired with txhash\n" + "}\n" + ); + + + int coinGroupId; + std::string startBlockHash; + try { + coinGroupId = std::stol(request.params[0].get_str()); + startBlockHash = request.params[1].get_str(); + } catch (std::logic_error const & e) { + throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); + } + + uint256 blockHash; + std::vector> coins; + std::vector setHash; + + { + LOCK(cs_main); + spark::CSparkState* sparkState = spark::CSparkState::GetState(); + sparkState->GetCoinsForRecovery( + &chainActive, + chainActive.Height() - (ZC_MINT_CONFIRMATIONS - 1), + coinGroupId, + startBlockHash, + blockHash, + coins, + setHash); + } + + UniValue ret(UniValue::VOBJ); + UniValue mints(UniValue::VARR); + + for (const auto& coin : coins) { + CDataStream serializedCoin(SER_NETWORK, PROTOCOL_VERSION); + serializedCoin << coin; + std::vector vch(serializedCoin.begin(), serializedCoin.end()); + + std::vector data; + data.push_back(EncodeBase64(vch.data(), size_t(vch.size()))); // coin + data.push_back(EncodeBase64(coin.second.begin(), coin.second.size())); // tx hash + + UniValue entity(UniValue::VARR); + entity.push_backV(data); + mints.push_back(entity); + } + + ret.push_back(Pair("blockHash", EncodeBase64(blockHash.begin(), blockHash.size()))); + ret.push_back(Pair("setHash", UniValue(EncodeBase64(setHash.data(), setHash.size())))); + ret.push_back(Pair("coins", mints)); + + return ret; +} + +UniValue getsparkmintmetadata(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "getmintmetadata\n" + "\nReturns the anonymity set id and nHeight of mint.\n" + "\nArguments:\n" + " \"coinHashes\"\n" + " [\n" + " {\n" + " \"coinHash\" (string) The hash of the spark mint\n" + " }\n" + " ,...\n" + " ]\n" + "\nResult:\n" + "{\n" + " \"metadata\" (Pair) nHeight and id for each coin\n" + "}\n" + ); + + UniValue coinHashes = find_value(request.params[0].get_obj(), "coinHashes"); + if (!coinHashes.isArray()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "mints is expected to be an array"); + } + + spark::CSparkState* sparkState = spark::CSparkState::GetState(); + + UniValue ret(UniValue::VARR); + for(UniValue const & element : coinHashes.getValues()) { + uint256 coinHash; + coinHash.SetHex(element.get_str()); + spark::Coin coin(spark::Params::get_default()); + if(!sparkState->HasCoinHash(coin, coinHash)) + continue; + + std::pair coinHeightAndId; + { + LOCK(cs_main); + coinHeightAndId = sparkState->GetMintedCoinHeightAndId(coin); + } + UniValue metaData(UniValue::VOBJ); + metaData.pushKV(std::to_string(coinHeightAndId.first), coinHeightAndId.second); + ret.push_back(metaData); + } + + return ret; +} + +UniValue getusedcoinstags(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "getusedcoinstags\n" + "\nReturns the set of used coin tags.\n" + "\nArguments:\n" + "{\n" + " \"startNumber \" (int) Number of elements already existing on user side\n" + "}\n" + "\nResult:\n" + "{\n" + " \"tags\" (std::string[]) array of Serialized GroupElements\n" + "}\n" + ); + + int startNumber; + try { + startNumber = std::stol(request.params[0].get_str()); + } catch (std::logic_error const & e) { + throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); + } + + spark::CSparkState* sparkState = spark::CSparkState::GetState(); + std::unordered_map tags; + { + LOCK(cs_main); + tags = sparkState->GetSpends(); + } + UniValue serializedTags(UniValue::VARR); + int i = 0; + for ( auto it = tags.begin(); it != tags.end(); ++it, ++i) { + if (i < startNumber) + continue; + std::vector serialized; + serialized.resize(34); + it->first.serialize(serialized.data()); + serializedTags.push_back(EncodeBase64(serialized.data(), 34)); + } + + UniValue ret(UniValue::VOBJ); + ret.push_back(Pair("tags", serializedTags)); + + return ret; +} + +UniValue getsparklatestcoinid(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 0) + throw std::runtime_error( + "getlatestcoinid\n" + "\nReturns the last coin group ID for Spark.\n" + "\nResult:\n" + "{\n" + " [\n" + " {\n" + " \"coinGroupId\" (int) The latest group id\n" + " }\n" + " ,...\n" + " ]\n" + "}\n" + ); + + spark::CSparkState* sparkState = spark::CSparkState::GetState(); + int latestCoinId; + { + LOCK(cs_main); + latestCoinId = sparkState->GetLatestCoinID(); + } + + return UniValue(latestCoinId); +} + UniValue getaddresstxids(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) @@ -1425,6 +1616,12 @@ static const CRPCCommand commands[] = { "mobile", "getfeerate", &getfeerate, true }, { "mobile", "getlatestcoinid", &getlatestcoinid, true }, + /* Mobile Spark */ + { "mobile", "getsparkanonymityset", &getsparkanonymityset, false }, + { "mobile", "getsparkmintmetadata", &getsparkmintmetadata, true }, + { "mobile", "getusedcoinstags", &getusedcoinstags, false }, + { "mobile", "getsparklatestcoinid", &getsparklatestcoinid, true }, + { "hidden", "setmocktime", &setmocktime, true, {"timestamp"}}, { "hidden", "echo", &echo, true, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}}, diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 7908baa564..2559a29a00 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -337,6 +337,13 @@ static const CRPCCommand vRPCCommands[] = { "mobile", "getusedcoinserials", &getusedcoinserials, false }, { "mobile", "getfeerate", &getfeerate, true }, { "mobile", "getlatestcoinid", &getlatestcoinid, true }, + + /* Mobile Spark */ + { "mobile", "getsparkanonymityset", &getsparkanonymityset, false }, + { "mobile", "getsparkmintmetadata", &getsparkmintmetadata, true }, + { "mobile", "getusedcoinstags", &getusedcoinstags, false }, + { "mobile", "getsparklatestcoinid", &getsparklatestcoinid, true }, + }; CRPCTable::CRPCTable() diff --git a/src/rpc/server.h b/src/rpc/server.h index 8b87c8efb3..642e33451e 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -213,6 +213,11 @@ extern UniValue getusedcoinserials(const JSONRPCRequest& params); extern UniValue getfeerate(const JSONRPCRequest& params); extern UniValue getlatestcoinid(const JSONRPCRequest& params); +extern UniValue getsparkanonymityset(const JSONRPCRequest& params); +extern UniValue getsparkmintmetadata(const JSONRPCRequest& params); +extern UniValue getusedcoinstags(const JSONRPCRequest& params); +extern UniValue getsparklatestcoinid(const JSONRPCRequest& params); + extern UniValue znode(const JSONRPCRequest &request); extern UniValue znodelist(const JSONRPCRequest &request); extern UniValue znodebroadcast(const JSONRPCRequest &request); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 5bef8b84a9..ec7e545145 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3361,7 +3361,7 @@ UniValue listsparkspends(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() > 0) { throw std::runtime_error( "listsparkspends \n" - "Returns array spark address in encoded form\n" + "Returns array spark spends\n" "Results are an array of Objects, each of which has:\n" "{txid, lTagHash, lTag and amount}"); } @@ -3481,9 +3481,7 @@ UniValue setsparkmintstatus(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 2) throw std::runtime_error( "setsparkmintstatus \"lTagHash\" (true/false)\n" - "Set mintIsUsed status to True or False\n" - "Results are an array of one or no Objects, each of which has:\n" - "{id, IsUsed, denomination, value, serialNumber, nHeight, randomness}"); + "Set mintIsUsed status to True or False"); EnsureSparkWalletIsAvailable(); From 58e31c97071d5e212f31254cbc35ab9eddbc9765 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 27 Feb 2023 07:57:34 +0400 Subject: [PATCH 089/197] Minor inprovements --- src/firo_params.h | 2 +- src/rpc/misc.cpp | 14 ++++++++++++-- src/test/fixtures.cpp | 1 + src/test/spark_tests.cpp | 10 +++++----- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/firo_params.h b/src/firo_params.h index bfff2f1f04..63ab3485f0 100644 --- a/src/firo_params.h +++ b/src/firo_params.h @@ -184,7 +184,7 @@ static const int64_t DUST_HARD_LIMIT = 1000; // 0.00001 FIRO mininput // Spark #define SPARK_START_BLOCK 700000 #define SPARK_TESTNET_START_BLOCK 150000 -#define LELANTUS_GRACEFUL_PERIOD 600000 +#define LELANTUS_GRACEFUL_PERIOD 800000 #define LELANTUS_TESTNET_GRACEFUL_PERIOD 200000 // Versions of zerocoin mint/spend transactions diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 027f00017e..819c3f0757 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -938,8 +938,8 @@ UniValue getanonymityset(const JSONRPCRequest& request) " \"setHash\" (string) Anonymity set hash\n" " \"mints\" (Pair>) Serialized GroupElements paired with txhash which is paired with mint tag and mint value\n" "}\n" - + HelpExampleCli("getanonymityset", "\"1\"" "\"f2d16ca8c1e220912f11dfe0797c88b367bcbb9b8c13aa2bc5892114da47f7b7\"") - + HelpExampleRpc("getanonymityset", "\"1\"" "\"f2d16ca8c1e220912f11dfe0797c88b367bcbb9b8c13aa2bc5892114da47f7b7\"") + + HelpExampleCli("getanonymityset", "\"1\"" "{\"ca511f07489e35c9bc60ca62c82de225ba7aae7811ce4c090f95aa976639dc4e\"}") + + HelpExampleRpc("getanonymityset", "\"1\"" "{\"ca511f07489e35c9bc60ca62c82de225ba7aae7811ce4c090f95aa976639dc4e\"}") ); @@ -952,6 +952,10 @@ UniValue getanonymityset(const JSONRPCRequest& request) throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); } + if(!GetBoolArg("-mobile", false)){ + throw std::runtime_error(std::string("Please rerun Firo with -mobile ")); + } + uint256 blockHash; std::vector>> coins; std::vector setHash; @@ -1153,6 +1157,8 @@ UniValue getsparkanonymityset(const JSONRPCRequest& request) " \"setHash\" (string) Anonymity set hash\n" " \"mints\" (Pair) Serialized Spark coin paired with txhash\n" "}\n" + + HelpExampleCli("getsparkanonymityset", "\"1\"" "{\"ca511f07489e35c9bc60ca62c82de225ba7aae7811ce4c090f95aa976639dc4e\"}") + + HelpExampleRpc("getsparkanonymityset", "\"1\"" "{\"ca511f07489e35c9bc60ca62c82de225ba7aae7811ce4c090f95aa976639dc4e\"}") ); @@ -1165,6 +1171,10 @@ UniValue getsparkanonymityset(const JSONRPCRequest& request) throw std::runtime_error(std::string("An exception occurred while parsing parameters: ") + e.what()); } + if(!GetBoolArg("-mobile", false)){ + throw std::runtime_error(std::string("Please rerun Firo with -mobile ")); + } + uint256 blockHash; std::vector> coins; std::vector setHash; diff --git a/src/test/fixtures.cpp b/src/test/fixtures.cpp index a196384567..f80e501d29 100644 --- a/src/test/fixtures.cpp +++ b/src/test/fixtures.cpp @@ -390,6 +390,7 @@ std::vector SparkTestingSetup::GenerateMints( } } } + reverse(mints.begin(), mints.end()); return mints; } diff --git a/src/test/spark_tests.cpp b/src/test/spark_tests.cpp index 3ce12a9479..399c27deff 100644 --- a/src/test/spark_tests.cpp +++ b/src/test/spark_tests.cpp @@ -192,8 +192,8 @@ BOOST_AUTO_TEST_CASE(get_outpoint) BOOST_CHECK_EQUAL(mints.size(), amounts.size()); - auto mint = mints[1]; - auto nonCommitted = mints[0]; + auto mint = mints[0]; + auto nonCommitted = mints[1]; auto tx = txs[0]; size_t mintIdx = 0; @@ -373,7 +373,7 @@ BOOST_AUTO_TEST_CASE(connect_and_disconnect_block) // Update isused status { - CSparkMintMeta meta = pwalletMain->sparkWallet->getMintMeta(mints[1].k); + CSparkMintMeta meta = pwalletMain->sparkWallet->getMintMeta(mints[0].k); BOOST_CHECK(meta != CSparkMintMeta()); BOOST_CHECK(meta.isUsed); @@ -381,7 +381,7 @@ BOOST_AUTO_TEST_CASE(connect_and_disconnect_block) meta.isUsed = false; pwalletMain->sparkWallet->updateMintInMemory(meta); meta = CSparkMintMeta(); - meta = pwalletMain->sparkWallet->getMintMeta(mints[1].k); + meta = pwalletMain->sparkWallet->getMintMeta(mints[0].k); BOOST_CHECK(!meta.isUsed); } @@ -618,7 +618,7 @@ BOOST_AUTO_TEST_CASE(coingroup) mempool.clear(); auto idx1 = GenerateBlock(txRange(0, 1)); auto block1 = GetCBlock(idx1); - checker.coins.push_back(pwalletMain->sparkWallet->getCoinFromMeta(mints[mints.size()-1])); + checker.coins.push_back(pwalletMain->sparkWallet->getCoinFromMeta(mints[0])); checker.lastId = 1; checker.first = idx1; checker.last = idx1; From 4c0d0f2434a3f20a13e22cc9d9a1e409c43172ca Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 27 Feb 2023 12:21:27 +0400 Subject: [PATCH 090/197] Adding more unitests --- src/Makefile.test.include | 2 + src/spark/sparkwallet.cpp | 28 +++ src/spark/sparkwallet.h | 4 + src/spark/state.h | 4 + src/test/spark_mintspend_test.cpp | 132 +++++++++++++ src/test/spark_state_test.cpp | 296 ++++++++++++++++++++++++++++++ 6 files changed, 466 insertions(+) create mode 100644 src/test/spark_mintspend_test.cpp create mode 100644 src/test/spark_state_test.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 792225ceaf..2abce15d3c 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -103,6 +103,8 @@ BITCOIN_TESTS = \ libspark/test/f4grumble_test.cpp \ libspark/test/address_test.cpp \ test/spark_tests.cpp \ + test/spark_state_test.cpp \ + test/spark_mintspend_test.cpp \ sigma/test/coin_spend_tests.cpp \ sigma/test/coin_tests.cpp \ sigma/test/primitives_tests.cpp \ diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 30c256222f..411860b498 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -271,6 +271,22 @@ spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta) const { return spark::Coin(params, meta.type, meta.k, address, meta.v, meta.memo, meta.serial_context); } +spark::Coin CSparkWallet::getCoinFromLTagHash(const uint256& lTagHash) const { + LOCK(cs_spark_wallet); + CSparkMintMeta meta; + if (coinMeta.count(lTagHash)) { + meta = coinMeta.at(lTagHash); + return getCoinFromMeta(meta); + } + return spark::Coin(); +} + +spark::Coin CSparkWallet::getCoinFromLTag(const GroupElement& lTag) const { + uint256 lTagHash = primitives::GetLTagHash(lTag); + return getCoinFromLTagHash(lTagHash); +} + + void CSparkWallet::clearAllMints(CWalletDB& walletdb) { LOCK(cs_spark_wallet); for (auto& itr : coinMeta) { @@ -308,6 +324,18 @@ void CSparkWallet::updateMint(const CSparkMintMeta& mint, CWalletDB& walletdb) { } } +void CSparkWallet::setCoinUnused(const GroupElement& lTag) { + LOCK(cs_spark_wallet); + CWalletDB walletdb(strWalletFile); + uint256 lTagHash = primitives::GetLTagHash(lTag); + CSparkMintMeta coinMeta = getMintMeta(lTagHash); + + if (coinMeta != CSparkMintMeta()) { + coinMeta.isUsed = false; + updateMint(coinMeta, walletdb); + } +} + void CSparkWallet::updateMintInMemory(const CSparkMintMeta& mint) { LOCK(cs_spark_wallet); for (auto& itr : coinMeta) { diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 1277d11919..85fac397ed 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -47,6 +47,8 @@ class CSparkWallet { std::unordered_map getMintMap() const; // generate spark Coin from meta data spark::Coin getCoinFromMeta(const CSparkMintMeta& meta) const; + spark::Coin getCoinFromLTag(const GroupElement& lTag) const; + spark::Coin getCoinFromLTagHash(const uint256& lTagHash) const; // functions to get spark balance CAmount getFullBalance(); @@ -65,6 +67,8 @@ class CSparkWallet { void addOrUpdateMint(const CSparkMintMeta& mint, const uint256& lTagHash, CWalletDB& walletdb); void updateMint(const CSparkMintMeta& mint, CWalletDB& walletdb); + void setCoinUnused(const GroupElement& lTag); + void updateMintInMemory(const CSparkMintMeta& mint); // get mint meta from linking tag hash CSparkMintMeta getMintMeta(const uint256& hash); diff --git a/src/spark/state.h b/src/spark/state.h index 7d1b16d34a..1e1fb21ec0 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -11,6 +11,8 @@ #include "../libspark/spend_transaction.h" #include "primitives.h" +namespace spark_mintspend { class spark_mintspend_test; } + namespace spark { // Spark transaction info, added to the CBlock to ensure spark mint/spend transactions got their info stored into index @@ -239,6 +241,8 @@ class CSparkState { typedef std::map metainfo_container_t; metainfo_container_t extendedMintMetaInfo, mintMetaInfo, spendMetaInfo; + + friend class spark_mintspend::spark_mintspend_test; }; } // namespace spark diff --git a/src/test/spark_mintspend_test.cpp b/src/test/spark_mintspend_test.cpp new file mode 100644 index 0000000000..a972dcb3ed --- /dev/null +++ b/src/test/spark_mintspend_test.cpp @@ -0,0 +1,132 @@ +#include "util.h" + +#include +#include + +#include "chainparams.h" +#include "key.h" +#include "validation.h" +#include "txdb.h" +#include "txmempool.h" +#include "../spark/state.h" +#include "../net.h" + +#include "test/fixtures.h" +#include "test/testutil.h" + +#include "wallet/db.h" +#include "wallet/wallet.h" +#include "wallet/walletexcept.h" +#include "../wallet/coincontrol.h" + +#include +#include +#include + +BOOST_FIXTURE_TEST_SUITE(spark_mintspend, SparkTestingSetup) + +BOOST_AUTO_TEST_CASE(spark_mintspend_test) +{ + GenerateBlocks(1001); + spark::CSparkState *sparkState = spark::CSparkState::GetState(); + + pwalletMain->SetBroadcastTransactions(true); + + std::vector mintTxs; + auto sparkMints = GenerateMints({50 * COIN, 60 * COIN}, mintTxs); + + // Verify Mint gets in the mempool + BOOST_CHECK_MESSAGE(mempool.size() == sparkMints.size(), "Mints were not added to mempool"); + + int previousHeight = chainActive.Height(); + auto blockIdx1 = GenerateBlock(mintTxs); + BOOST_CHECK(blockIdx1); + + BOOST_CHECK_MESSAGE(previousHeight + 1 == chainActive.Height(), "Block not added to chain"); + BOOST_CHECK_MESSAGE(mempool.size() == 0, "Mints were not removed from mempool"); + previousHeight = chainActive.Height(); + + CPubKey pub; + { + LOCK(pwalletMain->cs_wallet); + pub = pwalletMain->GenerateNewKey(); + } + + std::vector recipients = {{GetScriptForDestination(pub.GetID()), 1 * COIN, false}}; + + GenerateBlock({}); + BOOST_CHECK_MESSAGE(previousHeight + 1 == chainActive.Height(), "Block not added to chain"); + BOOST_CHECK_MESSAGE(mempool.size() == 0, "Mempool must be empty"); + + auto wtx = GenerateSparkSpend({70 * COIN}, {}, nullptr); + BOOST_CHECK_MESSAGE(mempool.size() == 1, "SparkSpend is not added into mempool"); + + previousHeight = chainActive.Height(); + GenerateBlock({CMutableTransaction(wtx[0])}); + BOOST_CHECK_MESSAGE(previousHeight + 1 == chainActive.Height(), "Block not added to chain"); + BOOST_CHECK_MESSAGE(mempool.size() == 0, "SparkSpend is not removed from mempool"); + GenerateBlocks(6); + + CAmount fee; + auto result = pwalletMain->CreateSparkSpendTransaction(recipients, {}, fee, nullptr)[0]; + CWallet* wallet = pwalletMain; + CReserveKey reserveKey(wallet); + CValidationState state; + pwalletMain->CommitTransaction(result, reserveKey, g_connman.get(), state); + + BOOST_CHECK_MESSAGE(mempool.size() == 1, "SparkSpend was not added to mempool"); + + //try double spend + pwalletMain->CommitTransaction(result, reserveKey, g_connman.get(), state); + BOOST_CHECK_MESSAGE(mempool.size() == 1, "Double spend was added into mempool, but was not supposed"); + + previousHeight = chainActive.Height(); + GenerateBlock({CMutableTransaction(*result.tx)}); + BOOST_CHECK_MESSAGE(previousHeight + 1 == chainActive.Height(), "Block not added to chain"); + BOOST_CHECK_MESSAGE(mempool.size() == 0, "Mempool not cleared"); + GenerateBlocks(2); + + auto tempTags = sparkState->usedLTags; + sparkState->usedLTags.clear(); + + { + //Set mints unused, and try to spend again + for(auto ltag : tempTags) + pwalletMain->sparkWallet->setCoinUnused(ltag.first); + + spark::Coin coin = pwalletMain->sparkWallet->getCoinFromLTag(tempTags.begin()->first); + COutPoint outPoint; + spark::GetOutPoint(outPoint, coin); + CCoinControl coinControl; + coinControl.Select(outPoint); + + CAmount fee; + result.Init(NULL); + result = pwalletMain->CreateSparkSpendTransaction(recipients, {}, fee, &coinControl)[0]; + CReserveKey reserveKey(pwalletMain); + CValidationState state; + pwalletMain->CommitTransaction(result, reserveKey, g_connman.get(), state); + + BOOST_CHECK_MESSAGE(mempool.size() == 1, "Spend was not added to mempool"); + } + + sparkState->usedLTags = tempTags; + BOOST_CHECK_EXCEPTION(GenerateBlock({CMutableTransaction(*result.tx)}), std::runtime_error, no_check); + BOOST_CHECK_MESSAGE(mempool.size() == 1, "Mempool not set"); + tempTags = sparkState->usedLTags; + sparkState->usedLTags.clear(); + CBlock b = CreateBlock({CMutableTransaction(*result.tx)}, script); + + sparkState->usedLTags = tempTags; + mempool.clear(); + previousHeight = chainActive.Height(); + + const CChainParams& chainparams = Params(); + BOOST_CHECK_MESSAGE(ProcessNewBlock(chainparams, std::make_shared(b), true, NULL), "ProcessBlock failed"); + //This test confirms that a block containing a double spend is rejected and not added in the chain + BOOST_CHECK_MESSAGE(previousHeight == chainActive.Height(), "Double spend - Block added to chain even though same spend in previous block"); + + mempool.clear(); + sparkState->Reset(); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/spark_state_test.cpp b/src/test/spark_state_test.cpp new file mode 100644 index 0000000000..84a642d1a6 --- /dev/null +++ b/src/test/spark_state_test.cpp @@ -0,0 +1,296 @@ +#include "../spark/state.h" +#include "../validation.h" +#include "../wallet/wallet.h" +#include "fixtures.h" +#include "test_bitcoin.h" + +#include + +namespace std +{ + +template +basic_ostream& operator<<(basic_ostream& os, const pair& p) +{ + return os << '(' << p.first << ", " << p.second << ')'; +} + +} // namespace std + + +class SparkStateTests : public SparkTestingSetup +{ +public: + SparkStateTests() : SparkTestingSetup(), + sparkState(spark::CSparkState::GetState()) + { + } + + ~SparkStateTests() + { + sparkState->Reset(); + } + +public: + CBlock GetCBlock(CBlockIndex const* blockIdx) + { + CBlock block; + if (!ReadBlockFromDisk(block, blockIdx, ::Params().GetConsensus())) { + throw std::invalid_argument("No block index data"); + } + + return block; + } + + void PopulateSparkTxInfo( + CBlock& block, + std::vector const& mints, + std::vector > const& lTags) + { + block.sparkTxInfo = std::make_shared(); + block.sparkTxInfo->mints.insert(block.sparkTxInfo->mints.end(), mints.begin(), mints.end()); + + for (auto const& lTag : lTags) { + block.sparkTxInfo->spentLTags.emplace(lTag); + } + } +public: + spark::CSparkState* sparkState; +}; + +BOOST_FIXTURE_TEST_SUITE(spark_state_tests, SparkStateTests) + +BOOST_AUTO_TEST_CASE(add_mints_to_state) +{ + GenerateBlocks(1100); + + std::vector txs; + auto mints = GenerateMints({1 * COIN, 2 * COIN, 1 * CENT}, txs); + + mempool.clear(); + auto blockIdx1 = GenerateBlock({txs[0]}); + auto block1 = GetCBlock(blockIdx1); + PopulateSparkTxInfo(block1, {pwalletMain->sparkWallet->getCoinFromMeta(mints[0])}, {}); + + sparkState->AddMintsToStateAndBlockIndex(blockIdx1, &block1); + + auto blockIdx2 = GenerateBlock({txs[1]}); + auto block2 = GetCBlock(blockIdx2); + PopulateSparkTxInfo(block2, {pwalletMain->sparkWallet->getCoinFromMeta(mints[1])}, {}); + + sparkState->AddMintsToStateAndBlockIndex(blockIdx2, &block2); + + //verify heigh and id was assigned. + BOOST_CHECK_EQUAL(std::make_pair(chainActive.Height() - 1, 1), sparkState->GetMintedCoinHeightAndId(pwalletMain->sparkWallet->getCoinFromMeta(mints[0]))); + BOOST_CHECK_EQUAL(std::make_pair(chainActive.Height(), 1), sparkState->GetMintedCoinHeightAndId(pwalletMain->sparkWallet->getCoinFromMeta(mints[1]))); + BOOST_CHECK_EQUAL(std::make_pair(-1, -1), sparkState->GetMintedCoinHeightAndId(pwalletMain->sparkWallet->getCoinFromMeta(mints[2]))); + + // test has coin + BOOST_CHECK(sparkState->HasCoin(pwalletMain->sparkWallet->getCoinFromMeta(mints[0]))); + BOOST_CHECK(sparkState->HasCoin(pwalletMain->sparkWallet->getCoinFromMeta(mints[1]))); + BOOST_CHECK(!sparkState->HasCoin(pwalletMain->sparkWallet->getCoinFromMeta(mints[2]))); + + // test has coin hash + auto cn0 = pwalletMain->sparkWallet->getCoinFromMeta(mints[0]); + BOOST_CHECK(sparkState->HasCoinHash(cn0, cn0.getHash())); + auto cn1 = pwalletMain->sparkWallet->getCoinFromMeta(mints[1]); + BOOST_CHECK(sparkState->HasCoinHash(cn1, cn1.getHash())); + auto cn2 = pwalletMain->sparkWallet->getCoinFromMeta(mints[2]); + BOOST_CHECK(!sparkState->HasCoinHash(cn2, cn2.getHash())); + + BOOST_CHECK_EQUAL(2, sparkState->GetTotalCoins()); + + // check group info + spark::CSparkState::SparkCoinGroupInfo group, fakeGroup; + BOOST_CHECK(sparkState->GetCoinGroupInfo(1, group)); + BOOST_CHECK(!sparkState->GetCoinGroupInfo(0, fakeGroup)); + BOOST_CHECK(!sparkState->GetCoinGroupInfo(2, fakeGroup)); + + BOOST_CHECK(blockIdx1 == group.firstBlock); + BOOST_CHECK(blockIdx2 == group.lastBlock); + + BOOST_CHECK_EQUAL(4, group.nCoins); + + BOOST_CHECK_EQUAL(1, sparkState->GetLatestCoinID()); + + sparkState->Reset(); + mempool.clear(); +} + +BOOST_AUTO_TEST_CASE(lTag_adding) +{ + GenerateBlocks(1001); + std::vector txs; + auto mints = GenerateMints({1 * COIN, 2 * COIN, 1 * CENT}, txs); + + GenerateBlock(txs); + + auto blockIdx = chainActive.Tip(); + auto block = GetCBlock(blockIdx); + PopulateSparkTxInfo(block, {{pwalletMain->sparkWallet->getCoinFromMeta(mints[0])}}, {}); + + sparkState->AddMintsToStateAndBlockIndex(blockIdx, &block); + + GroupElement lTag1, lTag2; + lTag1.randomize(); + lTag2.randomize(); + auto lTagHash1 = primitives::GetLTagHash(lTag1); + auto lTagHash2 = primitives::GetLTagHash(lTag2); + + sparkState->AddSpend(lTag1, 1); + + GroupElement receivedLTag; + BOOST_CHECK(sparkState->IsUsedLTag(lTag1)); + BOOST_CHECK(sparkState->IsUsedLTagHash(receivedLTag, lTagHash1)); + BOOST_CHECK(lTag1 == receivedLTag); + + BOOST_CHECK(!sparkState->IsUsedLTag(lTag2)); + BOOST_CHECK(!sparkState->IsUsedLTagHash(receivedLTag, lTagHash2)); + + // add lTags to group that doesn't exist, should fail + BOOST_CHECK_THROW(sparkState->AddSpend(GroupElement(), 100), std::invalid_argument); + + sparkState->Reset(); + mempool.clear(); +} + +BOOST_AUTO_TEST_CASE(mempool) +{ + GenerateBlocks(1001); + std::vector txs; + auto mint = GenerateMints({1 * COIN}, txs)[0]; + + GenerateBlock(txs); + + auto blockIdx = chainActive.Tip(); + auto block = GetCBlock(blockIdx); + PopulateSparkTxInfo(block, {{pwalletMain->sparkWallet->getCoinFromMeta(mint)}}, {}); + + sparkState->AddMintsToStateAndBlockIndex(blockIdx, &block); + + GroupElement spendLTag; + spendLTag.randomize(); + sparkState->AddSpend(spendLTag, 1); + + // test mint mempool + // - can not add on-chain coin + BOOST_CHECK(!sparkState->CanAddMintToMempool(pwalletMain->sparkWallet->getCoinFromMeta(mint))); + + // - can not add duplicated coin + spark::Coin randMint; + BOOST_CHECK(sparkState->CanAddMintToMempool(randMint)); + sparkState->AddMintsToMempool({randMint}); + BOOST_CHECK(!sparkState->CanAddMintToMempool(randMint)); + + // - remove from mempool then can add again + sparkState->RemoveMintFromMempool(randMint); + BOOST_CHECK(sparkState->CanAddMintToMempool(randMint)); + + // test spend mempool + // - can not add on-chain spend + BOOST_CHECK(!sparkState->CanAddSpendToMempool(spendLTag)); + + // - can not add duplicated serial + GroupElement anotherLTag; + anotherLTag.randomize(); + + auto txid = ArithToUint256(1); + + BOOST_CHECK(sparkState->CanAddSpendToMempool(anotherLTag)); + sparkState->AddSpendToMempool({anotherLTag}, txid); + BOOST_CHECK(!sparkState->CanAddSpendToMempool(anotherLTag)); + + BOOST_CHECK(txid == sparkState->GetMempoolConflictingTxHash(anotherLTag)); + + GroupElement fakeLTag; + fakeLTag.randomize(); + BOOST_CHECK(uint256() == sparkState->GetMempoolConflictingTxHash(fakeLTag)); + + // - remove spend then can add again + sparkState->RemoveSpendFromMempool({anotherLTag}); + BOOST_CHECK(sparkState->CanAddSpendToMempool(anotherLTag)); + sparkState->AddSpendToMempool({anotherLTag}, txid); + BOOST_CHECK(!sparkState->CanAddSpendToMempool(anotherLTag)); + + sparkState->Reset(); +} + +BOOST_AUTO_TEST_CASE(add_remove_block) +{ + GenerateBlocks(1001); + + auto index1 = GenerateBlock({}); + auto block1 = GetCBlock(index1); + PopulateSparkTxInfo(block1, {}, {}); + + sparkState->AddBlock(index1); + + BOOST_CHECK_EQUAL(0, sparkState->GetMints().size()); + BOOST_CHECK_EQUAL(0, sparkState->GetSpends().size()); + + // some mints + std::vector txs; + auto mint1 = GenerateMints({1 * COIN}, txs)[0]; + auto mint2 = GenerateMints({2 * COIN}, txs)[0]; + + auto index2 = GenerateBlock({}); + auto block2 = GetCBlock(index2); + PopulateSparkTxInfo(block2, {pwalletMain->sparkWallet->getCoinFromMeta(mint1), pwalletMain->sparkWallet->getCoinFromMeta(mint2)}, {}); + + sparkState->AddMintsToStateAndBlockIndex(index2, &block2); + sparkState->AddBlock(index2); + + BOOST_CHECK_EQUAL(2, sparkState->GetMints().size()); + BOOST_CHECK_EQUAL(0, sparkState->GetSpends().size()); + + // some serials + GroupElement lTag1, lTag2; + lTag1.randomize(); + lTag2.randomize(); + + auto index3 = GenerateBlock({}); + auto block3 = GetCBlock(index3); + PopulateSparkTxInfo(block3, {}, {{lTag1, 1}, {lTag2, 1}}); + index3->spentLTags = block3.sparkTxInfo->spentLTags; + + sparkState->AddBlock(index3); + + BOOST_CHECK_EQUAL(2, sparkState->GetMints().size()); + BOOST_CHECK_EQUAL(2, sparkState->GetSpends().size()); + + // both mint and lTag + auto mint3 = GenerateMints({3 * COIN}, txs)[0]; + + GroupElement lTag3; + lTag3.randomize(); + + auto index4 = GenerateBlock({}); + auto block4 = GetCBlock(index4); + PopulateSparkTxInfo(block4, {pwalletMain->sparkWallet->getCoinFromMeta(mint3)}, {{lTag3, 1}}); + sparkState->AddMintsToStateAndBlockIndex(index4, &block4); + index4->spentLTags = block4.sparkTxInfo->spentLTags; + + sparkState->AddBlock(index4); + + BOOST_CHECK_EQUAL(3, sparkState->GetMints().size()); + BOOST_CHECK_EQUAL(3, sparkState->GetSpends().size()); + + // remove last block + sparkState->RemoveBlock(index4); + + BOOST_CHECK_EQUAL(2, sparkState->GetMints().size()); + BOOST_CHECK_EQUAL(2, sparkState->GetSpends().size()); + + // verify mints and spends on blocks + BOOST_CHECK(sparkState->HasCoin(pwalletMain->sparkWallet->getCoinFromMeta(mint1))); + BOOST_CHECK(sparkState->HasCoin(pwalletMain->sparkWallet->getCoinFromMeta(mint2))); + BOOST_CHECK(!sparkState->HasCoin(pwalletMain->sparkWallet->getCoinFromMeta(mint3))); + + BOOST_CHECK(sparkState->IsUsedLTag(lTag1)); + BOOST_CHECK(sparkState->IsUsedLTag(lTag2)); + BOOST_CHECK(!sparkState->IsUsedLTag(lTag3)); + + sparkState->Reset(); +} + +BOOST_AUTO_TEST_SUITE_END() From 33e92461331ce6c4d0413fcf596dc0c21a33ab1b Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 5 Mar 2023 07:41:11 +0400 Subject: [PATCH 091/197] Minor fixes --- src/rpc/rawtransaction.cpp | 21 ++++++++++++++++++++- src/script/script.h | 3 ++- src/script/standard.cpp | 3 ++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index c21827c6aa..38606f55ed 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -136,6 +136,22 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) serials.push_back(serial.GetHex()); } in.push_back(Pair("serials", serials)); + } else if (tx.IsSparkSpend()) { + in.push_back("sparkSpend"); + fillStdFields(in, txin); + std::unique_ptr sparkSpend; + try { + sparkSpend = std::make_unique(spark::ParseSparkSpend(tx)); + } + catch (...) { + continue; + } + in.push_back(Pair("nFees", ValueFromAmount(sparkSpend->getFee()))); + UniValue lTags(UniValue::VARR); + for (GroupElement const & lTag : sparkSpend->getUsedLTags()) { + lTags.push_back(lTag.GetHex()); + } + in.push_back(Pair("lTags", lTags)); } else { in.push_back(Pair("txid", txin.prevout.hash.GetHex())); in.push_back(Pair("vout", (int64_t)txin.prevout.n)); @@ -170,7 +186,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) for (unsigned int i = 0; i < tx.vout.size(); i++) { const CTxOut& txout = tx.vout[i]; UniValue out(UniValue::VOBJ); - if (txout.scriptPubKey.IsLelantusJMint()) { + if (txout.scriptPubKey.IsLelantusJMint() || txout.scriptPubKey.IsSparkSMint()) { out.push_back(Pair("value", 0)); } else { out.push_back(Pair("value", ValueFromAmount(txout.nValue))); @@ -240,6 +256,9 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) case TRANSACTION_LELANTUS: entry.push_back(Pair("lelantusData", HexStr(tx.vExtraPayload))); break; + case TRANSACTION_SPARK: + entry.push_back(Pair("sparkData", HexStr(tx.vExtraPayload))); + break; default: break; } diff --git a/src/script/script.h b/src/script/script.h index 3f5465271a..c67f0068a6 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -580,7 +580,8 @@ class CScript : public CScriptBase opcodeRet = (opcodetype)opcode; if (opcodeRet == opcodetype::OP_SIGMASPEND || opcodeRet == opcodetype::OP_SIGMAMINT || - opcodeRet == opcodetype::OP_LELANTUSMINT || opcodeRet == opcodetype::OP_LELANTUSJMINT || opcodeRet == opcodetype::OP_LELANTUSJOINSPLIT) { + opcodeRet == opcodetype::OP_LELANTUSMINT || opcodeRet == opcodetype::OP_LELANTUSJMINT || opcodeRet == opcodetype::OP_LELANTUSJOINSPLIT || + opcodeRet == opcodetype::OP_SPARKMINT || opcodeRet == opcodetype::OP_SPARKSMINT || opcodeRet == opcodetype::OP_SPARKSPEND) { if (pvchRet) { pvchRet->assign(pc, end()); } diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 78321569de..c44fca6a13 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -35,7 +35,8 @@ const char* GetTxnOutputType(txnouttype t) case TX_ZEROCOINMINTV3: return "zerocoinmintv3"; case TX_LELANTUSMINT: return "lelantusmint"; case TX_LELANTUSJMINT: return "lelantusmint"; - + case TX_SPARKMINT: return "sparkmint"; + case TX_SPARKSMINT: return "sparksmint"; } return NULL; } From e1c5b20bc5fae4bff2d5b29ff31f6e8dee853786 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 10 Mar 2023 05:34:54 +0400 Subject: [PATCH 092/197] Add one more unittest --- src/test/spark_state_test.cpp | 182 +++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 2 deletions(-) diff --git a/src/test/spark_state_test.cpp b/src/test/spark_state_test.cpp index 84a642d1a6..cea2eef88d 100644 --- a/src/test/spark_state_test.cpp +++ b/src/test/spark_state_test.cpp @@ -77,9 +77,9 @@ BOOST_AUTO_TEST_CASE(add_mints_to_state) auto blockIdx2 = GenerateBlock({txs[1]}); auto block2 = GetCBlock(blockIdx2); PopulateSparkTxInfo(block2, {pwalletMain->sparkWallet->getCoinFromMeta(mints[1])}, {}); - + sparkState->AddMintsToStateAndBlockIndex(blockIdx2, &block2); - + //verify heigh and id was assigned. BOOST_CHECK_EQUAL(std::make_pair(chainActive.Height() - 1, 1), sparkState->GetMintedCoinHeightAndId(pwalletMain->sparkWallet->getCoinFromMeta(mints[0]))); BOOST_CHECK_EQUAL(std::make_pair(chainActive.Height(), 1), sparkState->GetMintedCoinHeightAndId(pwalletMain->sparkWallet->getCoinFromMeta(mints[1]))); @@ -293,4 +293,182 @@ BOOST_AUTO_TEST_CASE(add_remove_block) sparkState->Reset(); } +BOOST_AUTO_TEST_CASE(get_coin_group) +{ + GenerateBlocks(500); + + std::vector amounts(12, COIN); + std::vector txs; + + auto mints = GenerateMints(amounts, txs); + + std::vector coins; + std::vector indexes; + std::vector blocks; + + for (size_t i = 0; i != mints.size(); i += 2) { + auto index = GenerateBlock({txs[i], txs[i + 1]}); + + auto block = GetCBlock(index); + coins.push_back(pwalletMain->sparkWallet->getCoinFromMeta(mints[i + 1])); + coins.push_back(pwalletMain->sparkWallet->getCoinFromMeta(mints[i])); + + PopulateSparkTxInfo( + block, + { + pwalletMain->sparkWallet->getCoinFromMeta(mints[i]), + pwalletMain->sparkWallet->getCoinFromMeta(mints[i + 1]) + }, + {}); + + indexes.push_back(index); + blocks.push_back(block); + + GenerateBlock({}); + } + + size_t maxSize = 6; + size_t startCoin = 2; + auto sparkState = new spark::CSparkState(maxSize, startCoin); + + auto addMintsToState = [&](CBlockIndex* index, CBlock const& block) { + sparkState->AddMintsToStateAndBlockIndex(index, &block); + }; + + auto verifyMints = [&](size_t i, size_t j, std::vector const& coinSet) { + std::vector expected(coins.begin() + i, coins.begin() + j); + std::reverse(expected.begin(), expected.end()); + + BOOST_CHECK(expected == coinSet); + }; + + auto verifyGroup = [&](int expectedId, size_t expectedCoins, CBlockIndex* expectedFirst, CBlockIndex* expectedLast, int testId = 0) -> void { + if (!testId) { + testId = sparkState->GetLatestCoinID(); + } + + spark::CSparkState::SparkCoinGroupInfo group; + + BOOST_CHECK(sparkState->GetCoinGroupInfo(testId, group)); + if (expectedId > 0) { // verify last Id + BOOST_CHECK_EQUAL(expectedId, testId); + } + + BOOST_CHECK_EQUAL(expectedCoins, group.nCoins); + BOOST_CHECK_EQUAL(expectedFirst, group.firstBlock); + BOOST_CHECK_EQUAL(expectedLast, group.lastBlock); + }; + + + addMintsToState(indexes[0], blocks[0]); + addMintsToState(indexes[1], blocks[1]); + addMintsToState(indexes[2], blocks[2]); + + verifyGroup(1, 6, indexes[0], indexes[2]); + + uint256 blockHashOut1; + std::vector coinOut1; + std::vector setHash; + + BOOST_CHECK_EQUAL(6, sparkState->GetCoinSetForSpend( + &chainActive, + indexes[2]->nHeight, + 1, + blockHashOut1, + coinOut1, + setHash)); + + verifyMints(0, 6, coinOut1); + BOOST_CHECK(indexes[2]->GetBlockHash() == blockHashOut1); + + // 8 coins, 1(6), 2(4) + addMintsToState(indexes[3], blocks[3]); + verifyGroup(2, 4, indexes[2], indexes[3]); + verifyGroup(1, 6, indexes[0], indexes[2], 1); + + uint256 blockHashOut2; + std::vector coinOut2; + BOOST_CHECK_EQUAL(4, sparkState->GetCoinSetForSpend( + &chainActive, + indexes[3]->nHeight + 1, + 2, + blockHashOut2, + coinOut2, + setHash)); + + verifyMints(4, 8, coinOut2); + BOOST_CHECK(indexes[3]->GetBlockHash() == blockHashOut2); + + // 10 coins, 1(6), 2(6) + addMintsToState(indexes[4], blocks[4]); + + verifyGroup(2, 6, indexes[2], indexes[4]); + verifyGroup(1, 6, indexes[0], indexes[2], 1); + + uint256 blockHashOut3; + std::vector coinOut3; + BOOST_CHECK_EQUAL(6, sparkState->GetCoinSetForSpend( + &chainActive, + indexes[4]->nHeight, + 2, + blockHashOut3, + coinOut3, + setHash)); + + verifyMints(4, 10, coinOut3); + BOOST_CHECK(indexes[4]->GetBlockHash() == blockHashOut3); + + // 12 coins, 1(6), 2(6), 3(4) + addMintsToState(indexes[5], blocks[5]); + + verifyGroup(3, 4, indexes[4], indexes[5]); + verifyGroup(2, 6, indexes[2], indexes[4], 2); + verifyGroup(1, 6, indexes[0], indexes[2], 1); + + uint256 blockHashOut4; + std::vector coinOut4; + BOOST_CHECK_EQUAL(4, sparkState->GetCoinSetForSpend( + &chainActive, + indexes[5]->nHeight, + 3, + blockHashOut4, + coinOut4, + setHash)); + verifyMints(8, 12, coinOut4); + + // Get first group + uint256 blockHashOut5; + std::vector coinOut5; + BOOST_CHECK_EQUAL(6, sparkState->GetCoinSetForSpend( + &chainActive, + indexes[5]->nHeight, + 1, + blockHashOut5, + coinOut5, + setHash)); + + verifyMints(0, 6, coinOut5); + BOOST_CHECK(indexes[2]->GetBlockHash() == blockHashOut5); + + // Get first group with low max height + uint256 blockHashOut6; + std::vector coinOut6; + BOOST_CHECK_EQUAL(2, sparkState->GetCoinSetForSpend( + &chainActive, + indexes[0]->nHeight, + 1, + blockHashOut6, + coinOut6, + setHash)); + + verifyMints(0, 2, coinOut6); + BOOST_CHECK(indexes[0]->GetBlockHash() == blockHashOut6); + + sparkState->RemoveBlock(indexes[5]); + verifyGroup(2, 6, indexes[2], indexes[4]); + verifyGroup(1, 6, indexes[0], indexes[2], 1); + + sparkState->Reset(); +} + BOOST_AUTO_TEST_SUITE_END() From 7fbcd12e97911fc25350352fb35973a7936a2f04 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 20 Mar 2023 06:53:23 +0400 Subject: [PATCH 093/197] Detect zero challenges --- src/libspark/chaum.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libspark/chaum.cpp b/src/libspark/chaum.cpp index 88a42012fd..7f69852020 100644 --- a/src/libspark/chaum.cpp +++ b/src/libspark/chaum.cpp @@ -72,6 +72,9 @@ void Chaum::prove( proof.t3 = t; Scalar c_power(c); for (std::size_t i = 0; i < n; i++) { + if (c_power.isZero()) { + throw std::invalid_argument("Unexpected challenge!"); + } proof.t1[i] = r[i] + c_power*x[i]; proof.t2 += s[i] + c_power*y[i]; proof.t3 += c_power*z[i]; @@ -92,10 +95,16 @@ bool Chaum::verify( } Scalar c = challenge(mu, S, T, proof.A1, proof.A2); + if (c.isZero()) { + throw std::invalid_argument("Unexpected challenge!"); + } std::vector c_powers; c_powers.emplace_back(c); for (std::size_t i = 1; i < n; i++) { c_powers.emplace_back(c_powers[i-1]*c); + if (c_powers[i].isZero()) { + throw std::invalid_argument("Unexpected challenge!"); + } } // Weight the verification equations From e01e3db5f2982b7083b7c582a2eaf3f69d6d5c34 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 30 Mar 2023 04:54:22 +0400 Subject: [PATCH 094/197] isAddressMine implemented --- src/spark/sparkwallet.cpp | 29 +++++++++++++++++++++++++++++ src/spark/sparkwallet.h | 2 ++ 2 files changed, 31 insertions(+) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 411860b498..7b81ddfa5f 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -234,6 +234,35 @@ spark::Address CSparkWallet::getAddress(const int32_t& i) { return addresses[i]; } +bool CSparkWallet::isAddressMine(const std::string& encodedAddr) { + const spark::Params* params = spark::Params::get_default(); + spark::Address address(params); + try { + address.decode(encodedAddr); + } catch (...) { + return false; + } + + for (const auto& itr : addresses) { + if (itr.second.get_Q1() == address.get_Q1() && itr.second.get_Q2() == address.get_Q2()) + return true; + } + + uint64_t d; + + try { + d = viewKey.get_diversifier(address.get_d()); + } catch (...) { + return false; + } + + spark::Address newAddr = getAddress(int32_t(d)); + if (newAddr.get_Q1() == address.get_Q1() && newAddr.get_Q2() == address.get_Q2()) + return true; + + return false; +} + std::vector CSparkWallet::ListSparkMints(bool fUnusedOnly, bool fMatureOnly) const { std::vector setMints; LOCK(cs_spark_wallet); diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 85fac397ed..e1886bac8f 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -41,6 +41,8 @@ class CSparkWallet { std::unordered_map getAllAddresses(); // get address for a diversifier spark::Address getAddress(const int32_t& i); + bool isAddressMine(const std::string& encodedAddr); + // list spark mint, mint metadata in memory and in db should be the same at this moment, so get from memory std::vector ListSparkMints(bool fUnusedOnly = false, bool fMatureOnly = false) const; std::list ListSparkSpends() const; From f1b41ab45e316134032f146e31955012542052c4 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 31 Mar 2023 02:36:44 +0400 Subject: [PATCH 095/197] Typo fix --- src/wallet/rpcwallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index ec7e545145..b309b3a489 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3377,7 +3377,7 @@ UniValue listsparkspends(const JSONRPCRequest& request) { entry.push_back(Pair("txid", itr.hashTx.GetHex())); entry.push_back(Pair("lTagHash", itr.lTagHash.GetHex())); entry.push_back(Pair("lTag", itr.lTag.GetHex())); - entry.push_back(Pair("amount", itr.lTag.GetHex())); + entry.push_back(Pair("amount", itr.amount)); results.push_back(entry); } return results; From 61dc3c559059dfa72d0784fcbb9ac1f1a3411788 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Wed, 5 Apr 2023 06:24:20 +0400 Subject: [PATCH 096/197] Script tests fixed --- src/test/data/script_tests.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/data/script_tests.json b/src/test/data/script_tests.json index 4524eaf0a2..3c6cb903a7 100644 --- a/src/test/data/script_tests.json +++ b/src/test/data/script_tests.json @@ -258,9 +258,6 @@ ["0", "IF 0xce ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xcf ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xd0 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], -["0", "IF 0xd1 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], -["0", "IF 0xd2 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], -["0", "IF 0xd3 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xd4 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xd5 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xd6 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], From 7594d669eb59ff7823dea0293e2ccb8097f211c8 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 6 Apr 2023 03:21:28 +0400 Subject: [PATCH 097/197] Spark state reset in tests --- src/test/spark_tests.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/spark_tests.cpp b/src/test/spark_tests.cpp index 399c27deff..7ea628d53f 100644 --- a/src/test/spark_tests.cpp +++ b/src/test/spark_tests.cpp @@ -541,6 +541,9 @@ BOOST_AUTO_TEST_CASE(checktransaction) BOOST_CHECK(!CheckSparkTransaction( spendTx, state, spendTx.GetHash(), false, chainActive.Height(), false, true, &info)); + + mempool.clear(); + sparkState->Reset(); } BOOST_AUTO_TEST_CASE(coingroup) @@ -681,6 +684,7 @@ BOOST_AUTO_TEST_CASE(coingroup) reconnect(block5); checker.Verify(); + mempool.clear(); sparkState->Reset(); } From ff5ee240bd80d15edd8e65326b34f8819a17fdf3 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 6 Apr 2023 06:59:40 +0400 Subject: [PATCH 098/197] isMine() fixed --- src/spark/state.cpp | 41 ++++++++++++++++++++++++----------------- src/spark/state.h | 2 +- src/wallet/wallet.cpp | 19 +++++++++++++++++++ 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/spark/state.cpp b/src/spark/state.cpp index 3859373fdf..c24b2f21e3 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -196,22 +196,7 @@ std::vector GetSparkMintCoins(const CTransaction &tx) std::vector result; if (tx.IsSparkTransaction()) { - CDataStream serialContextStream(SER_NETWORK, PROTOCOL_VERSION); - if (tx.IsSparkSpend()) { - try { - spark::SpendTransaction spend = ParseSparkSpend(tx); - serialContextStream << spend.getUsedLTags(); - } catch (...) { - return result; - } - } else { - for (auto input: tx.vin) { - input.scriptSig.clear(); - serialContextStream << input; - } - } - - std::vector serial_context(serialContextStream.begin(), serialContextStream.end()); + std::vector serial_context = getSerialContext(tx); for (const auto& vout : tx.vout) { const auto& script = vout.scriptPubKey; if (script.IsSparkMint() || script.IsSparkSMint()) { @@ -847,8 +832,10 @@ bool GetOutPoint(COutPoint& outPoint, const spark::Coin& coin) CBlockIndex *mintBlock = chainActive[mintHeight]; CBlock block; //TODO levon, try to optimize this - if (!ReadBlockFromDisk(block, mintBlock, ::Params().GetConsensus())) + if (!ReadBlockFromDisk(block, mintBlock, ::Params().GetConsensus())) { LogPrintf("can't read block from disk.\n"); + return false; + } return GetOutPointFromBlock(outPoint, coin, block); } @@ -888,6 +875,26 @@ bool GetOutPointFromBlock(COutPoint& outPoint, const spark::Coin& coin, const CB return false; } +std::vector getSerialContext(const CTransaction &tx) { + CDataStream serialContextStream(SER_NETWORK, PROTOCOL_VERSION); + if (tx.IsSparkSpend()) { + try { + spark::SpendTransaction spend = ParseSparkSpend(tx); + serialContextStream << spend.getUsedLTags(); + } catch (...) { + return std::vector(); + } + } else { + for (auto input: tx.vin) { + input.scriptSig.clear(); + serialContextStream << input; + } + } + + std::vector serial_context(serialContextStream.begin(), serialContextStream.end()); + return serial_context; +} + static bool CheckSparkSpendTAg( CValidationState& state, CSparkTxInfo* sparkTxInfo, diff --git a/src/spark/state.h b/src/spark/state.h index 1e1fb21ec0..bdb09f5c4a 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -44,7 +44,7 @@ unsigned char GetNetworkType(); // Pass Scripts form mint transaction and get spark MintTransaction object void ParseSparkMintTransaction(const std::vector& scripts, MintTransaction& mintTransaction); void ParseSparkMintCoin(const CScript& script, spark::Coin& txCoin); - +std::vector getSerialContext(const CTransaction &tx); spark::SpendTransaction ParseSparkSpend(const CTransaction &tx); std::vector GetSparkUsedTags(const CTransaction &tx); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 61e686a66f..0438e00f3f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1746,6 +1746,23 @@ isminetype CWallet::IsMine(const CTxOut &txout) const } return db.HasHDMint(pub) ? ISMINE_SPENDABLE : ISMINE_NO; } else if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) { + std::vector serialContext; + for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { + const CWalletTx *pcoin = &(*it).second; + for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) { + if (txout == pcoin->tx->vout[i]) { + serialContext = spark::getSerialContext(*pcoin->tx); + break; + } + } + + if (!serialContext.empty()) + break; + } + + if (serialContext.empty()) + return ISMINE_NO; + spark::Coin coin(spark::Params::get_default()); try { spark::ParseSparkMintCoin(txout.scriptPubKey, coin); @@ -1753,6 +1770,8 @@ isminetype CWallet::IsMine(const CTxOut &txout) const return ISMINE_NO; } + coin.setSerialContext(serialContext); + if (!sparkWallet) return ISMINE_NO; return sparkWallet->isMine(coin) ? ISMINE_SPENDABLE : ISMINE_NO; From a444b4ae448a2352bacdb5aef276fafaabe25d91 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 10 Apr 2023 03:23:12 +0400 Subject: [PATCH 099/197] Spark tx index --- src/addresstype.h | 19 +++++++++++++++++++ src/core_write.cpp | 3 ++- src/rpc/blockchain.cpp | 1 + src/rpc/misc.cpp | 11 +++++++++++ src/txdb.cpp | 16 ++++++++++++---- 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/addresstype.h b/src/addresstype.h index ac4ba8ff3b..155508a131 100644 --- a/src/addresstype.h +++ b/src/addresstype.h @@ -14,6 +14,9 @@ enum struct AddressType , lelantusMint = 8 , lelantusJMint = 9 , lelantusJSplit = 10 + , sparkMint = 11 + , sparksMint = 12 + , sparkSpend = 13 }; namespace zerocoin { namespace utils { @@ -62,6 +65,22 @@ inline bool isLelantusJSplit(std::string const & str){ return str == "Lelantusjsplit"; } +inline bool isSpark(std::string const & str){ + return str == "Spark"; +} + +inline bool isSparkMint(std::string const & str){ + return str == "Sparkmint"; +} + +inline bool isSparkSMint(std::string const & str){ + return str == "Sparksmint"; +} + +inline bool isSparkSpend(std::string const & str){ + return str == "Sparkspend"; +} + }} #endif /* ADDRESSTYPE_H */ diff --git a/src/core_write.cpp b/src/core_write.cpp index 6c6b21cb83..80a3815b65 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -112,7 +112,8 @@ std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDeco } if (opcode == opcodetype::OP_SIGMASPEND || opcode == opcodetype::OP_SIGMAMINT || - opcode == opcodetype::OP_LELANTUSMINT || opcode == opcodetype::OP_LELANTUSJOINSPLIT) { + opcode == opcodetype::OP_LELANTUSMINT || opcode == opcodetype::OP_LELANTUSJOINSPLIT || + opcode == opcodetype::OP_SPARKMINT || opcode == opcodetype::OP_SPARKSPEND) { str += " "; str += HexStr(vch); break; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index d9060bb8d5..6578d87eb3 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -918,6 +918,7 @@ static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) addresses.push_back(std::make_pair(uint160(), AddressType::lelantusJSplit)); addresses.push_back(std::make_pair(uint160(), AddressType::sigmaSpend)); addresses.push_back(std::make_pair(uint160(), AddressType::zerocoinSpend)); + addresses.push_back(std::make_pair(uint160(), AddressType::sparkSpend)); // Iterate over all types of transactions std::vector > addressIndex; diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 819c3f0757..70585e5bc8 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -593,6 +593,17 @@ void handleSingleAddress(const UniValue& uniAddress, std::vectorpush_back(std::make_pair(CAddressIndexKey(addrType, uint160(), height, txNumber, txHash, 0, true), -spendAmount)); @@ -592,6 +594,12 @@ void handleOutput(const CTxOut &out, size_t outNo, uint256 const & txHash, int h if(out.scriptPubKey.IsLelantusJMint()) addressIndex->push_back(std::make_pair(CAddressIndexKey(AddressType::lelantusJMint, uint160(), height, txNumber, txHash, outNo, false), out.nValue)); + if(out.scriptPubKey.IsSparkMint()) + addressIndex->push_back(std::make_pair(CAddressIndexKey(AddressType::sparkMint, uint160(), height, txNumber, txHash, outNo, false), out.nValue)); + + if(out.scriptPubKey.IsSparkSMint()) + addressIndex->push_back(std::make_pair(CAddressIndexKey(AddressType::sparksMint, uint160(), height, txNumber, txHash, outNo, false), out.nValue)); + txnouttype type; std::vector > addresses; @@ -616,7 +624,7 @@ void handleOutput(const CTxOut &out, size_t outNo, uint256 const & txHash, int h void CDbIndexHelper::ConnectTransaction(CTransaction const & tx, int height, int txNumber, CCoinsViewCache const & view) { size_t no = 0; - if(!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit()) { + if(!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { for (CTxIn const & input : tx.vin) { handleInput(input, no++, tx.GetHash(), height, txNumber, view, addressIndex, addressUnspentIndex, spentIndex); } @@ -634,7 +642,7 @@ void CDbIndexHelper::ConnectTransaction(CTransaction const & tx, int height, int handleRemint(tx.vin[0], tx.GetHash(), height, txNumber, remintValue, addressIndex, addressUnspentIndex, spentIndex); } - if(tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsLelantusJoinSplit()) + if(tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) handleZerocoinSpend(tx.vout.begin(), tx.vout.end(), tx.GetHash(), height, txNumber, view, addressIndex, tx); no = 0; @@ -671,7 +679,7 @@ void CDbIndexHelper::DisconnectTransactionInputs(CTransaction const & tx, int he size_t no = 0; - if(!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit()) + if(!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) for (CTxIn const & input : tx.vin) { handleInput(input, no++, tx.GetHash(), height, txNumber, view, addressIndex, addressUnspentIndex, spentIndex); } @@ -690,7 +698,7 @@ void CDbIndexHelper::DisconnectTransactionInputs(CTransaction const & tx, int he void CDbIndexHelper::DisconnectTransactionOutputs(CTransaction const & tx, int height, int txNumber, CCoinsViewCache const & view) { - if(tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsLelantusJoinSplit()) + if(tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) handleZerocoinSpend(tx.vout.begin(), tx.vout.end(), tx.GetHash(), height, txNumber, view, addressIndex, tx); size_t no = 0; From e4e44aea126ac69d93b07b2016fc83ef027fcdd4 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 10 Apr 2023 06:53:03 +0400 Subject: [PATCH 100/197] Rpc test fix and more --- qa/rpc-tests/spark_spend_gettransaction.py | 2 +- src/spark/sparkwallet.cpp | 12 +++++ src/spark/sparkwallet.h | 1 + src/wallet/rpcwallet.cpp | 4 +- src/wallet/wallet.cpp | 56 +++++++++++++++++++--- 5 files changed, 65 insertions(+), 10 deletions(-) diff --git a/qa/rpc-tests/spark_spend_gettransaction.py b/qa/rpc-tests/spark_spend_gettransaction.py index 06bf095ea5..bbd11f88b2 100755 --- a/qa/rpc-tests/spark_spend_gettransaction.py +++ b/qa/rpc-tests/spark_spend_gettransaction.py @@ -44,7 +44,7 @@ def run_test(self): assert int(spendto_wo_tx['amount']) == int('-1') assert spendto_wo_tx['fee'] < Decimal('0') assert isinstance(spendto_wo_tx['details'], list) - assert len(spendto_wo_tx['details']) == 2 + assert len(spendto_wo_tx['details']) == 1 assert spendto_wo_tx['details'][0]['involvesWatchonly'] # case 2: Spend many with watchonly address and valid address diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 7b81ddfa5f..4627d7ee6a 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -478,6 +478,17 @@ bool CSparkWallet::isMine(const std::vector& lTags) const { return false; } +CAmount CSparkWallet::getMyCoinV(spark::Coin coin) const { + CAmount v(0); + try { + spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); + v = identifiedCoinData.v; + } catch (const std::runtime_error& e) { + //don nothing + } + return v; +} + CAmount CSparkWallet::getMySpendAmount(const std::vector& lTags) const { CAmount result = 0; LOCK(cs_spark_wallet); @@ -1288,6 +1299,7 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( output.v = spendInCurrentTx; else output.v = 0; + wtxNew.changes.insert(static_cast(tx.vout.size() + privOutputs.size())); privOutputs.push_back(output); } diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index e1886bac8f..1eb96c5fef 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -80,6 +80,7 @@ class CSparkWallet { bool isMine(spark::Coin coin) const; bool isMine(const std::vector& lTags) const; + CAmount getMyCoinV(spark::Coin coin) const; CAmount getMySpendAmount(const std::vector& lTags) const; void UpdateSpendState(const GroupElement& lTag, const uint256& lTagHash, const uint256& txHash, bool fUpdateMint = true); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index b309b3a489..0fd0078d9d 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1574,10 +1574,10 @@ void ListTransactions(CWallet * const pwallet, const CWalletTx& wtx, const std:: } entry.push_back(Pair("account", strSentAccount)); MaybePushAddress(entry, s.destination, addr); - if (wtx.tx->IsZerocoinSpend() || wtx.tx->IsSigmaSpend() || wtx.tx->IsZerocoinRemint() || wtx.tx->IsLelantusJoinSplit()) { + if (wtx.tx->IsZerocoinSpend() || wtx.tx->IsSigmaSpend() || wtx.tx->IsZerocoinRemint() || wtx.tx->IsLelantusJoinSplit() || wtx.tx->IsSparkSpend()) { entry.push_back(Pair("category", "spend")); } - else if (wtx.tx->IsZerocoinMint() || wtx.tx->IsSigmaMint() || wtx.tx->IsLelantusMint()) { + else if (wtx.tx->IsZerocoinMint() || wtx.tx->IsSigmaMint() || wtx.tx->IsLelantusMint() || wtx.tx->IsSparkMint()) { entry.push_back(Pair("category", "mint")); } else { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0438e00f3f..1e9875c6ee 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1802,6 +1802,39 @@ CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) cons } return 0; } + + if (txout.scriptPubKey.IsSparkSMint()) { + if (!(filter & ISMINE_SPENDABLE)) + return 0; + std::vector serialContext; + for (std::map::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) { + const CWalletTx *pcoin = &(*it).second; + for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) { + if (txout == pcoin->tx->vout[i]) { + serialContext = spark::getSerialContext(*pcoin->tx); + break; + } + } + + if (!serialContext.empty()) + break; + } + + if (serialContext.empty()) + return 0; + + spark::Coin coin(spark::Params::get_default()); + try { + spark::ParseSparkMintCoin(txout.scriptPubKey, coin); + } catch (std::invalid_argument &) { + return 0; + } + coin.setSerialContext(serialContext); + if (!sparkWallet) + return 0; + return sparkWallet->getMyCoinV(coin); + } + return ((IsMine(txout) & filter) ? txout.nValue : 0); } @@ -2146,17 +2179,24 @@ void CWalletTx::GetAmounts(std::list& listReceived, CAmount nDebit = GetDebit(filter); if (nDebit > 0) // debit>0 means we signed/sent this transaction { - if (!tx->IsLelantusJoinSplit()) { - CAmount nValueOut = tx->GetValueOut(); - nFee = nDebit - nValueOut; - } - else + if (tx->IsLelantusJoinSplit()) { try { nFee = lelantus::ParseLelantusJoinSplit(*tx)->getFee(); } catch (...) { // do nothing } + } else if (tx->IsSparkSpend()) { + try { + nFee = spark::ParseSparkSpend(*tx).getFee(); + } + catch (...) { + // do nothing + } + } else { + CAmount nValueOut = tx->GetValueOut(); + nFee = nDebit - nValueOut; + } } // Sent/received. @@ -2179,7 +2219,9 @@ void CWalletTx::GetAmounts(std::list& listReceived, // In either case, we need to get the destination address CTxDestination address; - if (txout.scriptPubKey.IsZerocoinMint() || txout.scriptPubKey.IsSigmaMint() || txout.scriptPubKey.IsLelantusMint() || txout.scriptPubKey.IsLelantusJMint()) + if (txout.scriptPubKey.IsZerocoinMint() || txout.scriptPubKey.IsSigmaMint() + || txout.scriptPubKey.IsLelantusMint() || txout.scriptPubKey.IsLelantusJMint() + || txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) { address = CNoDestination(); } @@ -2191,7 +2233,7 @@ void CWalletTx::GetAmounts(std::list& listReceived, } CAmount nValue; - if(txout.scriptPubKey.IsLelantusJMint()) { + if(txout.scriptPubKey.IsLelantusJMint() || txout.scriptPubKey.IsSparkSMint()) { LOCK(pwalletMain->cs_wallet); nValue = pwallet->GetCredit(txout, ISMINE_SPENDABLE); } else { From cf36d94b1501bd318a87d959d83f2753a2b89d1b Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 10 Apr 2023 07:04:44 +0400 Subject: [PATCH 101/197] Add mint limit in tx creation --- src/spark/sparkwallet.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 4627d7ee6a..6c5896e88c 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -713,7 +713,8 @@ bool CSparkWallet::CreateSparkMintTransactions( auto itr = valueAndUTXO.begin(); - CAmount valueToMintInTx = itr->first; + CAmount valueToMintInTx = std::min( + ::Params().GetConsensus().nMaxValueLelantusMint, itr->first); if (!autoMintAll) { valueToMintInTx = std::min(valueToMintInTx, valueToMint); From 1316f93213b5237f3b6475af47f58a635a469b93 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 10 Apr 2023 07:06:55 +0400 Subject: [PATCH 102/197] Devnet graceful period extended --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index abe6e62614..475086cd63 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -956,7 +956,7 @@ class CDevNetParams : public CChainParams { consensus.nLelantusFixesStartBlock = 1; consensus.nSparkStartBlock = 2000; - consensus.nLelantusGracefulPeriod = 2500; + consensus.nLelantusGracefulPeriod = 6000; consensus.nMaxSigmaInputPerBlock = ZC_SIGMA_INPUT_LIMIT_PER_BLOCK; consensus.nMaxValueSigmaSpendPerBlock = ZC_SIGMA_VALUE_SPEND_LIMIT_PER_BLOCK; From 5ddbdd507ad33faf03a2db483eb8447ff920cf60 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 10 Apr 2023 15:35:10 +0400 Subject: [PATCH 103/197] Remove mint limit --- src/spark/sparkwallet.cpp | 6 ++++-- src/spark/state.cpp | 10 +++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 6c5896e88c..37399b0950 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -713,8 +713,10 @@ bool CSparkWallet::CreateSparkMintTransactions( auto itr = valueAndUTXO.begin(); - CAmount valueToMintInTx = std::min( - ::Params().GetConsensus().nMaxValueLelantusMint, itr->first); +// CAmount valueToMintInTx = std::min( +// ::Params().GetConsensus().nMaxValueLelantusMint, itr->first); + + CAmount valueToMintInTx = itr->first; if (!autoMintAll) { valueToMintInTx = std::min(valueToMintInTx, valueToMint); diff --git a/src/spark/state.cpp b/src/spark/state.cpp index c24b2f21e3..e1d9ee05ba 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -444,11 +444,11 @@ bool CheckSparkMintTransaction( PUBCOIN_NOT_VALIDATE, "CheckSparkMintTransaction : mintTransaction failed, wrong amount"); - if (coin.v > ::Params().GetConsensus().nMaxValueLelantusMint) - return state.DoS(100, - false, - REJECT_INVALID, - "CTransaction::CheckTransaction() : Spark Mint is out of limit."); +// if (coin.v > ::Params().GetConsensus().nMaxValueLelantusMint) +// return state.DoS(100, +// false, +// REJECT_INVALID, +// "CTransaction::CheckTransaction() : Spark Mint is out of limit."); hasCoin = sparkState.HasCoin(coin); From 13c3db4d649317a19777a111f4b30be2a2ba75ef Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 10 Apr 2023 15:35:46 +0400 Subject: [PATCH 104/197] Version bump --- configure.ac | 2 +- src/clientversion.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index e25704d0c2..18a7a0581f 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 14) define(_CLIENT_VERSION_REVISION, 12) -define(_CLIENT_VERSION_BUILD, 1) +define(_CLIENT_VERSION_BUILD, 2) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2022) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/src/clientversion.h b/src/clientversion.h index c1c32b9575..fe1154d62e 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -17,7 +17,7 @@ #define CLIENT_VERSION_MAJOR 0 #define CLIENT_VERSION_MINOR 14 #define CLIENT_VERSION_REVISION 12 -#define CLIENT_VERSION_BUILD 1 +#define CLIENT_VERSION_BUILD 2 //! Set to true for release, false for prerelease or test build #define CLIENT_VERSION_IS_RELEASE true From 638341bf751b6b80b2a5e9184b4ee5b34cd3f4d3 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 17 Apr 2023 04:32:38 +0400 Subject: [PATCH 105/197] Spend limits fixed --- src/spark/sparkwallet.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 37399b0950..ac747d9463 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -1101,7 +1101,7 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( } const auto &consensusParams = Params().GetConsensus(); - if (privateRecipients.size() >= consensusParams.nMaxSparkOutLimitPerTx) + if (privateRecipients.size() >= (consensusParams.nMaxSparkOutLimitPerTx - 1)) throw std::runtime_error(_("Spark shielded output limit exceeded.")); // calculate total value to spend @@ -1130,6 +1130,9 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( } } + if (vOut > consensusParams.nMaxValueSparkSpendPerTransaction) + throw std::runtime_error(_("Spend to transparent address limit exceeded (10,000 Firo per transaction).")); + std::vector result; std::vector txs; CWalletTx wtxNew; @@ -1468,10 +1471,6 @@ bool GetCoinsToSpend( { CAmount availableBalance = CalculateBalance(coins.begin(), coins.end()); - if (required > Params().GetConsensus().nMaxValueSparkSpendPerTransaction) { - throw std::invalid_argument(_("The required amount exceeds spend limit")); - } - if (required > availableBalance) { throw InsufficientFunds(); } From 48a83c8b52553c3c5d8ee98829dfae828827f1aa Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 17 Apr 2023 06:33:10 +0400 Subject: [PATCH 106/197] Using json object instead of array --- src/wallet/rpcwallet.cpp | 66 ++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0fd0078d9d..6d9281a021 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3525,9 +3525,9 @@ UniValue mintspark(const JSONRPCRequest& request) " the number of addresses.\n" "\nExamples:\n" "\nSend two amounts to two different spark addresses:\n" - + HelpExampleCli("mintspark", "\"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":[0.01, \\\"\\\"],\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":[0.01, \\\"\\\"]}\"") + + + HelpExampleCli("mintspark", "\"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\"},\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"\\\"}}\"") + "\nSend two amounts to two different spark addresses setting memo:\n" - + HelpExampleRpc("mintspark", "\"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":[0.01, \\\"\\\"],\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":[0.01, \\\"memo\\\"]}\"") + + HelpExampleRpc("mintspark", "\"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":{\\\"amount\\\":1},\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo2\\\"}}\"") ); EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); @@ -3557,9 +3557,18 @@ UniValue mintspark(const JSONRPCRequest& request) if (coinNetwork != network) throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid address, wrong network type: ")+name_); - UniValue amountAndMemo = sendTo[name_]; - CAmount nAmount = AmountFromValue(amountAndMemo[0]); - std::string memo = amountAndMemo[1].get_str(); + UniValue amountAndMemo = sendTo[name_].get_obj(); + + CAmount nAmount(0); + if (amountAndMemo.exists("amount")) + nAmount = AmountFromValue(amountAndMemo["amount"]); + else + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no amount: ")+name_); + + std::string memo = ""; + if (amountAndMemo.exists("memo")) + memo = amountAndMemo["memo"].get_str(); + if (nAmount <= 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); LogPrintf("rpcWallet.mintSpark() nAmount = %d \n", nAmount); @@ -3610,12 +3619,12 @@ UniValue spendspark(const JSONRPCRequest& request) "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" " the number of addresses.\n" "\nExamples:\n" - "\nSend two amounts to two different transparent addresses:\n" - + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":[0.02, false]}\" \"{}\"") + + "\nSend an amount to transparent address:\n" + + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\" \"{}\"") + + "\nSend an amount to a transparent address and two different private addresses:\n" + + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\" \"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false},\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\"") + "\nSend two amounts to two different transparent addresses and two different private addresses:\n" - + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":[0.02, false]}\" \"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":[0.01, \\\"\\\", false],\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":[0.01, \\\"\\\", false]}\"") + - "\nSend two amounts to two different transparent addresses and two different private addresses:\n" - + HelpExampleRpc("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":[0.02, false]}\" \"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":[0.01, \\\"\\\", false],\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":[0.01, \\\"\\\", false]}\"") + + HelpExampleRpc("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false},\\\"TuzUyNtTznSNnT2rPXG6Mk7hHG8Svuuoci\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": true}}\" \"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"\\\", \\\"subtractFee\\\": false},\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false}}\"") ); EnsureWalletIsUnlocked(pwallet); @@ -3642,11 +3651,21 @@ UniValue spendspark(const JSONRPCRequest& request) setAddress.insert(address); CScript scriptPubKey = GetScriptForDestination(address.Get()); - CAmount nAmount = AmountFromValue(sendTo[name_][0]); + + UniValue amountObj = sendTo[name_].get_obj(); + CAmount nAmount(0); + if (amountObj.exists("amount")) + nAmount = AmountFromValue(amountObj["amount"]); + else + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no amount: ")+name_); if (nAmount <= 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); - bool fSubtractFeeFromAmount = sendTo[name_][1].get_bool(); + bool fSubtractFeeFromAmount = false; + if (amountObj.exists("subtractFee")) + fSubtractFeeFromAmount = amountObj["subtractFee"].get_bool(); + else + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no subtractFee: ")+name_); CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; recipients.push_back(recipient); @@ -3670,10 +3689,25 @@ UniValue spendspark(const JSONRPCRequest& request) if (coinNetwork != network) throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid address, wrong network type: ")+name_); - UniValue amountAndMemo = privSendTo[name_]; - CAmount nAmount = AmountFromValue(amountAndMemo[0]); - std::string memo = amountAndMemo[1].get_str(); - bool subtractFee = amountAndMemo[2].get_bool(); + UniValue amountAndMemo = privSendTo[name_].get_obj(); + CAmount nAmount(0); + if (amountAndMemo.exists("amount")) + nAmount = AmountFromValue(amountAndMemo["amount"]); + else + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no amount: ")+name_); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); + + std::string memo = ""; + if (amountAndMemo.exists("memo")) + memo = amountAndMemo["memo"].get_str(); + + bool subtractFee = false; + if (amountAndMemo.exists("subtractFee")) + subtractFee = amountAndMemo["subtractFee"].get_bool(); + else + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no subtractFee: ")+name_); + if (nAmount <= 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); LogPrintf("rpcWallet.mintSpark() nAmount = %d \n", nAmount); From fd47c9315f1263a8fbbcf23713e2ca37d7f15e71 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 24 Apr 2023 11:55:31 +0400 Subject: [PATCH 107/197] A little cleanup --- src/libspark/keys.h | 4 ---- src/spark/sparkwallet.cpp | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libspark/keys.h b/src/libspark/keys.h index 8234e7eed4..4af8b25687 100644 --- a/src/libspark/keys.h +++ b/src/libspark/keys.h @@ -7,9 +7,6 @@ namespace spark { -// Let's define inital spark address version as P (private), -const char SPARK_ADDRESS_VERSION = 'P'; - using namespace secp_primitives; class SpendKey { @@ -86,7 +83,6 @@ class Address { unsigned char decode(const std::string& str); private: - char version = SPARK_ADDRESS_VERSION; const Params* params; std::vector d; GroupElement Q1, Q2; diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index ac747d9463..0bd0c428db 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -1174,7 +1174,7 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( assert(tx.nLockTime <= static_cast(chainActive.Height())); assert(tx.nLockTime < LOCKTIME_THRESHOLD); std::list> coins = GetAvailableSparkCoins(coinControl); - // TODO levon check spend limit + std::vector>>> estimated = SelectSparkCoins(vOut + mintVOut, recipientsToSubtractFee, coins, privateRecipients.size(), recipients.size(), coinControl); From 6b637936a5654fed08e98d4ddaa6ba348b53c354 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 8 May 2023 06:57:41 +0400 Subject: [PATCH 108/197] Add subtractFeeFromAmount in spark mint creation --- src/spark/sparkwallet.cpp | 35 ++++++++++++++----- src/spark/sparkwallet.h | 1 + src/test/fixtures.cpp | 2 +- src/wallet/rpcwallet.cpp | 8 +++-- src/wallet/test/spark_tests.cpp | 59 +++++++++++++++++++++++++++++---- src/wallet/wallet.cpp | 3 +- src/wallet/wallet.h | 1 + 7 files changed, 89 insertions(+), 20 deletions(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 0bd0c428db..19185da36e 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -663,6 +663,7 @@ bool CSparkWallet::CreateSparkMintTransactions( CAmount& nAllFeeRet, std::list& reservekeys, int& nChangePosInOut, + bool subtractFeeFromAmount, std::string& strFailReason, const CCoinControl *coinControl, bool autoMintAll) @@ -680,7 +681,7 @@ bool CSparkWallet::CreateSparkMintTransactions( assert(txNew.nLockTime <= (unsigned int) chainActive.Height()); assert(txNew.nLockTime < LOCKTIME_THRESHOLD); - std::vector outputs_ = outputs; + std::vector outputs_ = outputs; CAmount valueToMint = 0; for (auto& output : outputs_) @@ -729,12 +730,15 @@ bool CSparkWallet::CreateSparkMintTransactions( // Start with no fee and loop until there is enough fee while (true) { mintedValue = valueToMintInTx; - nValueToSelect = mintedValue + nFeeRet; + if (subtractFeeFromAmount) + nValueToSelect = mintedValue; + else + nValueToSelect = mintedValue + nFeeRet; - // if have no enough coins in this group then subtract fee from mint - if (nValueToSelect > itr->first) { + // if no enough coins in this group then subtract fee from mint + if (nValueToSelect > itr->first && !subtractFeeFromAmount) { + nValueToSelect = mintedValue; mintedValue -= nFeeRet; - nValueToSelect = mintedValue + nFeeRet; } if (!MoneyRange(mintedValue) || mintedValue == 0) { @@ -777,6 +781,23 @@ bool CSparkWallet::CreateSparkMintTransactions( } } + if (subtractFeeFromAmount) { + CAmount singleFee = nFeeRet / singleTxOutputs.size(); + CAmount reminder = nFeeRet % singleTxOutputs.size(); + for (size_t i = 0; i < singleTxOutputs.size(); ++i) { + if (singleTxOutputs[i].v <= singleFee) { + singleTxOutputs.erase(singleTxOutputs.begin() + i); + reminder += singleTxOutputs[i].v - singleFee; + --i; + } + singleTxOutputs[i].v -= singleFee; + if (reminder > 0 && singleTxOutputs[i].v > nFeeRet % singleTxOutputs.size()) {// first receiver pays the remainder not divisible by output count + singleTxOutputs[i].v -= reminder; + reminder = 0; + } + } + } + // Generate dummy mint coins to save time std::vector serial_context; std::vector recipients = CSparkWallet::CreateSparkMintRecipients(singleTxOutputs, serial_context, false); @@ -1638,6 +1659,4 @@ std::list> CSparkWallet::GetAvailableSpar }); return coins; -} - -//TODO levon implement wallet scanning when restoring wallet, or opening wallet file witg synced chain \ No newline at end of file +} \ No newline at end of file diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 1eb96c5fef..8e5899826a 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -109,6 +109,7 @@ class CSparkWallet { CAmount& nAllFeeRet, std::list& reservekeys, int& nChangePosInOut, + bool subtractFeeFromAmount, std::string& strFailReason, const CCoinControl *coinControl, bool autoMintAll = false); diff --git a/src/test/fixtures.cpp b/src/test/fixtures.cpp index f80e501d29..fbc7fe490d 100644 --- a/src/test/fixtures.cpp +++ b/src/test/fixtures.cpp @@ -369,7 +369,7 @@ std::vector SparkTestingSetup::GenerateMints( data.address = address; outputs.push_back(data); - auto result = pwalletMain->MintAndStoreSpark(outputs, wtxAndFee); + auto result = pwalletMain->MintAndStoreSpark(outputs, wtxAndFee, false); if (result != "") { throw std::runtime_error(_("Fail to generate mints, ") + result); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 6d9281a021..aa3e3ac529 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3510,7 +3510,7 @@ UniValue mintspark(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 1) + if (request.fHelp || request.params.size() == 0 || request.params.size() > 2) throw std::runtime_error( "mintspark {\"address\":amount,memo...}\n" + HelpRequiringPassphrase(pwallet) + "\n" @@ -3579,9 +3579,11 @@ UniValue mintspark(const JSONRPCRequest& request) data.v = nAmount; outputs.push_back(data); } - + bool subtractFeeFromAmount = false; + if (request.params.size() > 1) + subtractFeeFromAmount = request.params[1].get_bool(); std::vector> wtxAndFee; - std::string strError = pwallet->MintAndStoreSpark(outputs, wtxAndFee); + std::string strError = pwallet->MintAndStoreSpark(outputs, wtxAndFee, subtractFeeFromAmount); if (strError != "") throw JSONRPCError(RPC_WALLET_ERROR, strError); diff --git a/src/wallet/test/spark_tests.cpp b/src/wallet/test/spark_tests.cpp index f764f0349c..7f075d0dfe 100644 --- a/src/wallet/test/spark_tests.cpp +++ b/src/wallet/test/spark_tests.cpp @@ -76,7 +76,7 @@ BOOST_AUTO_TEST_CASE(mint_and_store_spark) std::vector mintedCoins; mintedCoins.push_back(data); - std::string result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee); + std::string result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee, false); BOOST_CHECK_EQUAL(result, ""); size_t mintAmount = 0; @@ -101,6 +101,51 @@ BOOST_AUTO_TEST_CASE(mint_and_store_spark) sparkState->Reset(); } +BOOST_AUTO_TEST_CASE(mint_subtract_fee) +{ + pwalletMain->SetBroadcastTransactions(true); + GenerateBlocks(1001); + + std::vector> wtxAndFee; + + const uint64_t v = 1 * COIN; + spark::Address sparkAddress = pwalletMain->sparkWallet->getDefaultAddress(); + + spark::MintedCoinData data; + data.address = sparkAddress; + data.v = v; + data.memo = "Test memo"; + + std::vector mintedCoins; + mintedCoins.push_back(data); + + std::string result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee, true); + BOOST_CHECK_EQUAL(result, ""); + + size_t mintAmount = 0; + size_t fee = 0; + for (const auto& wtx : wtxAndFee) { + auto tx = wtx.first.tx.get(); + + BOOST_CHECK(tx->IsSparkMint()); + BOOST_CHECK(tx->IsSparkTransaction()); + + for (const auto& out : tx->vout) { + if (out.scriptPubKey.IsSparkMint()) { + mintAmount += out.nValue; + } + } + CMutableTransaction mtx(*tx); + BOOST_CHECK(GenerateBlock({mtx})); + fee += wtx.second; + } + + BOOST_CHECK_EQUAL(data.v, mintAmount + fee); + + auto sparkState = spark::CSparkState::GetState(); + sparkState->Reset(); +} + BOOST_AUTO_TEST_CASE(list_spark_mints) { GenerateBlocks(1001); @@ -162,10 +207,10 @@ BOOST_AUTO_TEST_CASE(spend) mintedCoins.push_back(data); std::vector> wtxAndFee; - std::string result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee); + std::string result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee, false); std::vector> wtxAndFee2; - pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee2); + pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee2, false); BOOST_CHECK_EQUAL("", result); @@ -264,7 +309,7 @@ BOOST_AUTO_TEST_CASE(mintspark_and_mint_all) std::vector mintedCoins; mintedCoins.push_back(data); - auto result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee); + auto result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee, false); BOOST_CHECK_EQUAL("", result); BOOST_CHECK_EQUAL(1, wtxAndFee.size()); BOOST_CHECK_EQUAL(10 * COIN, countMintsInBalance(wtxAndFee)); @@ -275,7 +320,7 @@ BOOST_AUTO_TEST_CASE(mintspark_and_mint_all) mintedCoins.clear(); mintedCoins.push_back(data); - result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee); + result = pwalletMain->MintAndStoreSpark(mintedCoins, wtxAndFee, false); BOOST_CHECK_EQUAL("", result); BOOST_CHECK_GT(wtxAndFee.size(), 1); BOOST_CHECK_EQUAL(600 * COIN, countMintsInBalance(wtxAndFee)); @@ -286,7 +331,7 @@ BOOST_AUTO_TEST_CASE(mintspark_and_mint_all) auto balance = getAvailableCoinsForMintBalance(); BOOST_CHECK_GT(balance, 0); - result = pwalletMain->MintAndStoreSpark({}, wtxAndFee, true); + result = pwalletMain->MintAndStoreSpark({}, wtxAndFee, false, true); BOOST_CHECK_EQUAL("", result); BOOST_CHECK_GT(balance, countMintsInBalance(wtxAndFee)); BOOST_CHECK_EQUAL(balance, countMintsInBalance(wtxAndFee, true)); @@ -300,7 +345,7 @@ BOOST_AUTO_TEST_CASE(mintspark_and_mint_all) balance = getAvailableCoinsForMintBalance(); BOOST_CHECK_GT(balance, 0); - result = pwalletMain->MintAndStoreSpark({ }, wtxAndFee, true); + result = pwalletMain->MintAndStoreSpark({ }, wtxAndFee, false, true); BOOST_CHECK_EQUAL("", result); BOOST_CHECK_GT(balance, countMintsInBalance(wtxAndFee)); BOOST_CHECK_EQUAL(balance, countMintsInBalance(wtxAndFee, true)); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1e9875c6ee..2cfcfc8577 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5423,6 +5423,7 @@ std::string CWallet::MintAndStoreLelantus(const CAmount& value, std::string CWallet::MintAndStoreSpark( const std::vector& outputs, std::vector>& wtxAndFee, + bool subtractFeeFromAmount, bool autoMintAll, bool fAskFee, const CCoinControl *coinControl) { @@ -5449,7 +5450,7 @@ std::string CWallet::MintAndStoreSpark( int nChangePosRet = -1; std::list reservekeys; - if (!sparkWallet->CreateSparkMintTransactions(outputs, wtxAndFee, nFeeRequired, reservekeys, nChangePosRet, strError, coinControl, autoMintAll)) { + if (!sparkWallet->CreateSparkMintTransactions(outputs, wtxAndFee, nFeeRequired, reservekeys, nChangePosRet, subtractFeeFromAmount, strError, coinControl, autoMintAll)) { return strError; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 0418fb0c4f..88b3b00d65 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1061,6 +1061,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::string MintAndStoreSpark( const std::vector& outputs, std::vector>& wtxAndFee, + bool subtractFeeFromAmount, bool autoMintAll = false, bool fAskFee = false, const CCoinControl *coinControl = NULL); From a725327ff89e768258a4c8b74f19b1d116aa8939 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 8 May 2023 09:06:51 +0400 Subject: [PATCH 109/197] Spend input number limit removed --- src/chainparams.cpp | 8 - src/consensus/params.h | 6 - src/firo_params.h | 6 - src/miner.cpp | 10 +- src/spark/sparkwallet.cpp | 398 ++++++++++++++---------------- src/spark/sparkwallet.h | 4 +- src/spark/state.cpp | 20 +- src/test/fixtures.cpp | 11 +- src/test/fixtures.h | 2 +- src/test/spark_mintspend_test.cpp | 6 +- src/test/spark_tests.cpp | 14 +- src/wallet/rpcwallet.cpp | 11 +- src/wallet/test/spark_tests.cpp | 3 +- src/wallet/wallet.cpp | 32 ++- src/wallet/wallet.h | 4 +- 15 files changed, 229 insertions(+), 306 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 475086cd63..45d15acc82 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -416,8 +416,6 @@ class CMainParams : public CChainParams { consensus.nMaxLelantusInputPerTransaction = ZC_LELANTUS_INPUT_LIMIT_PER_TRANSACTION; consensus.nMaxValueLelantusSpendPerTransaction = ZC_LELANTUS_VALUE_SPEND_LIMIT_PER_TRANSACTION; consensus.nMaxValueLelantusMint = ZC_LELANTUS_MAX_MINT; - consensus.nMaxSparkInputPerTransaction = SPARK_INPUT_LIMIT_PER_TRANSACTION; - consensus.nMaxSparkInputPerBlock = SPARK_INPUT_LIMIT_PER_BLOCK; consensus.nMaxValueSparkSpendPerTransaction = SPARK_VALUE_SPEND_LIMIT_PER_TRANSACTION; consensus.nMaxValueSparkSpendPerBlock = SPARK_VALUE_SPEND_LIMIT_PER_BLOCK; consensus.nMaxSparkOutLimitPerTx = SPARK_OUT_LIMIT_PER_TX; @@ -721,8 +719,6 @@ class CTestNetParams : public CChainParams { consensus.nMaxLelantusInputPerTransaction = ZC_LELANTUS_INPUT_LIMIT_PER_TRANSACTION; consensus.nMaxValueLelantusSpendPerTransaction = 1001 * COIN; consensus.nMaxValueLelantusMint = 1001 * COIN; - consensus.nMaxSparkInputPerTransaction = SPARK_INPUT_LIMIT_PER_TRANSACTION; - consensus.nMaxSparkInputPerBlock = SPARK_INPUT_LIMIT_PER_BLOCK; consensus.nMaxValueSparkSpendPerTransaction = SPARK_VALUE_SPEND_LIMIT_PER_TRANSACTION; consensus.nMaxValueSparkSpendPerBlock = SPARK_VALUE_SPEND_LIMIT_PER_BLOCK; consensus.nMaxSparkOutLimitPerTx = SPARK_OUT_LIMIT_PER_TX; @@ -967,8 +963,6 @@ class CDevNetParams : public CChainParams { consensus.nMaxLelantusInputPerTransaction = ZC_LELANTUS_INPUT_LIMIT_PER_TRANSACTION; consensus.nMaxValueLelantusSpendPerTransaction = 1001 * COIN; consensus.nMaxValueLelantusMint = 1001 * COIN; - consensus.nMaxSparkInputPerTransaction = SPARK_INPUT_LIMIT_PER_TRANSACTION; - consensus.nMaxSparkInputPerBlock = SPARK_INPUT_LIMIT_PER_BLOCK; consensus.nMaxValueSparkSpendPerTransaction = SPARK_VALUE_SPEND_LIMIT_PER_TRANSACTION; consensus.nMaxValueSparkSpendPerBlock = SPARK_VALUE_SPEND_LIMIT_PER_BLOCK; consensus.nMaxSparkOutLimitPerTx = SPARK_OUT_LIMIT_PER_TX; @@ -1202,8 +1196,6 @@ class CRegTestParams : public CChainParams { consensus.nMaxLelantusInputPerTransaction = ZC_LELANTUS_INPUT_LIMIT_PER_TRANSACTION; consensus.nMaxValueLelantusSpendPerTransaction = ZC_LELANTUS_VALUE_SPEND_LIMIT_PER_TRANSACTION; consensus.nMaxValueLelantusMint = ZC_LELANTUS_MAX_MINT; - consensus.nMaxSparkInputPerTransaction = SPARK_INPUT_LIMIT_PER_TRANSACTION; - consensus.nMaxSparkInputPerBlock = SPARK_INPUT_LIMIT_PER_BLOCK; consensus.nMaxValueSparkSpendPerTransaction = SPARK_VALUE_SPEND_LIMIT_PER_TRANSACTION; consensus.nMaxValueSparkSpendPerBlock = SPARK_VALUE_SPEND_LIMIT_PER_BLOCK; consensus.nMaxSparkOutLimitPerTx = SPARK_OUT_LIMIT_PER_TX; diff --git a/src/consensus/params.h b/src/consensus/params.h index 95eb51a21d..fd19b1c63f 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -322,12 +322,6 @@ struct Params { // Value of maximum lelantus mint. int64_t nMaxValueLelantusMint; - // Amount of maximum spark spend per transaction. - unsigned nMaxSparkInputPerTransaction; - - // Amount of maximum spark spend per block. - unsigned nMaxSparkInputPerBlock; - // Value of maximum spark spend per transaction int64_t nMaxValueSparkSpendPerTransaction; diff --git a/src/firo_params.h b/src/firo_params.h index 63ab3485f0..18db8827c8 100644 --- a/src/firo_params.h +++ b/src/firo_params.h @@ -143,12 +143,6 @@ static const int64_t DUST_HARD_LIMIT = 1000; // 0.00001 FIRO mininput // Amount of lelantus spends allowed per transaction #define ZC_LELANTUS_INPUT_LIMIT_PER_TRANSACTION 50 -// Amount of spark spends allowed per transaction -#define SPARK_INPUT_LIMIT_PER_TRANSACTION 50 - -// Amount of saprk spends allowed per block -#define SPARK_INPUT_LIMIT_PER_BLOCK 100 - // Bumner of shielded spark outputs pet tx #define SPARK_OUT_LIMIT_PER_TX 16 diff --git a/src/miner.cpp b/src/miner.cpp index 7d52d22c7d..ff58dd334f 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -463,13 +463,9 @@ bool BlockAssembler::TestForBlock(CTxMemPool::txiter iter) // Check transaction against spark limits if(tx.IsSparkSpend()) { CAmount spendAmount = spark::GetSpendTransparentAmount(tx); - size_t spendNumber = spark::GetSpendInputs(tx); const auto ¶ms = chainparams.GetConsensus(); - if (spendNumber > params.nMaxLelantusInputPerTransaction || spendAmount > params.nMaxValueLelantusSpendPerTransaction) - return false; - - if (spendNumber + nSparkSpendInputs > params.nMaxSparkInputPerBlock) + if (spendAmount > params.nMaxValueSparkSpendPerTransaction) return false; if (spendAmount + nSparkSpendAmount > params.nMaxValueSparkSpendPerBlock) @@ -510,7 +506,6 @@ void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) if(tx.IsSparkSpend()) { CAmount spendAmount = spark::GetSpendTransparentAmount(tx); - size_t spendNumber = spark::GetSpendInputs(tx); const auto ¶ms = chainparams.GetConsensus(); if (spendAmount > params.nMaxValueLelantusSpendPerTransaction) @@ -518,9 +513,6 @@ void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) if ((nSparkSpendAmount += spendAmount) > params.nMaxValueSparkSpendPerBlock) return; - - if ((nSparkSpendInputs += spendNumber) > params.nMaxSparkInputPerBlock) - return; } pblock->vtx.emplace_back(iter->GetSharedTx()); diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 19185da36e..7c4d6531f6 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -1111,7 +1111,7 @@ bool getIndex(const spark::Coin& coin, const std::vector& anonymity return false; } -std::vector CSparkWallet::CreateSparkSpendTransaction( +CWalletTx CSparkWallet::CreateSparkSpendTransaction( const std::vector& recipients, const std::vector>& privateRecipients, CAmount &fee, @@ -1196,14 +1196,14 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( assert(tx.nLockTime < LOCKTIME_THRESHOLD); std::list> coins = GetAvailableSparkCoins(coinControl); - std::vector>>> estimated = + std::pair>> estimated = SelectSparkCoins(vOut + mintVOut, recipientsToSubtractFee, coins, privateRecipients.size(), recipients.size(), coinControl); std::vector recipients_ = recipients; std::vector> privateRecipients_ = privateRecipients; - for (auto& feeAndSpendCoins : estimated) { + { bool remainderSubtracted = false; - auto& fee = feeAndSpendCoins.first; + fee = estimated.first; for (size_t i = 0; i < recipients_.size(); i++) { auto &recipient = recipients_[i]; @@ -1252,202 +1252,192 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( throw std::runtime_error(_("Unable to generate spend key, looks wallet locked.")); - for (auto& feeAndSpendCoins : estimated) { - tx.vin.clear(); - tx.vout.clear(); - wtxNew.fFromMe = true; - wtxNew.changes.clear(); - - CAmount spendInCurrentTx = 0; - for (auto& spendCoin : feeAndSpendCoins.second) - spendInCurrentTx += spendCoin.second.v; - CAmount fee = feeAndSpendCoins.first; - spendInCurrentTx -= fee; + tx.vin.clear(); + tx.vout.clear(); + wtxNew.fFromMe = true; + wtxNew.changes.clear(); - uint64_t transparentOut = 0; - // fill outputs - for (size_t i = 0; i < recipients_.size(); i++) { - auto& recipient = recipients_[i]; - if (recipient.nAmount == 0) - continue; + CAmount spendInCurrentTx = 0; + for (auto& spendCoin : estimated.second) + spendInCurrentTx += spendCoin.second.v; + spendInCurrentTx -= fee; - if (spendInCurrentTx <= 0) - break; + uint64_t transparentOut = 0; + // fill outputs + for (size_t i = 0; i < recipients_.size(); i++) { + auto& recipient = recipients_[i]; + if (recipient.nAmount == 0) + continue; - CAmount recipientAmount = std::min(recipient.nAmount, spendInCurrentTx); - spendInCurrentTx -= recipientAmount; - recipient.nAmount -= recipientAmount; - CTxOut vout(recipientAmount, recipient.scriptPubKey); + CTxOut vout(recipient.nAmount, recipient.scriptPubKey); - if (vout.IsDust(minRelayTxFee)) { - std::string err; + if (vout.IsDust(minRelayTxFee)) { + std::string err; - if (recipient.fSubtractFeeFromAmount && fee > 0) { - if (vout.nValue < 0) { - err = boost::str(boost::format(_("Amount for recipient %1% is too small to pay the fee")) % i); - } else { - err = boost::str(boost::format(_("Amount for recipient %1% is too small to send after the fee has been deducted")) % i); - } + if (recipient.fSubtractFeeFromAmount && fee > 0) { + if (vout.nValue < 0) { + err = boost::str(boost::format(_("Amount for recipient %1% is too small to pay the fee")) % i); } else { - err = boost::str(boost::format(_("Amount for recipient %1% is too small")) % i); + err = boost::str(boost::format(_("Amount for recipient %1% is too small to send after the fee has been deducted")) % i); } - - throw std::runtime_error(err); + } else { + err = boost::str(boost::format(_("Amount for recipient %1% is too small")) % i); } - transparentOut += vout.nValue; - tx.vout.push_back(vout); + throw std::runtime_error(err); } - std::vector privOutputs; - // fill outputs - for (size_t i = 0; i < privateRecipients_.size(); i++) { - auto& recipient = privateRecipients_[i]; - if (recipient.first.v == 0) - continue; + transparentOut += vout.nValue; + tx.vout.push_back(vout); + } - if (spendInCurrentTx <= 0) - break; + spendInCurrentTx -= transparentOut; + std::vector privOutputs; + // fill outputs + for (size_t i = 0; i < privateRecipients_.size(); i++) { + auto& recipient = privateRecipients_[i]; + if (recipient.first.v == 0) + continue; - CAmount recipientAmount = recipient.first.v; - recipientAmount = std::min(recipientAmount, spendInCurrentTx); - spendInCurrentTx -= recipientAmount; - recipient.first.v -= recipientAmount; - spark::OutputCoinData output = recipient.first; - output.v = recipientAmount; - privOutputs.push_back(output); - } + CAmount recipientAmount = recipient.first.v; + spendInCurrentTx -= recipientAmount; + spark::OutputCoinData output = recipient.first; + output.v = recipientAmount; + privOutputs.push_back(output); + } - if (!privOutputs.size() || spendInCurrentTx > 0) { - spark::OutputCoinData output; - output.address = getDefaultAddress(); - output.memo = ""; - if (spendInCurrentTx > 0) - output.v = spendInCurrentTx; - else - output.v = 0; - wtxNew.changes.insert(static_cast(tx.vout.size() + privOutputs.size())); - privOutputs.push_back(output); + if (spendInCurrentTx <= 0) + throw std::invalid_argument(_("Unable to create spend transaction."));; + + if (!privOutputs.size() || spendInCurrentTx > 0) { + spark::OutputCoinData output; + output.address = getDefaultAddress(); + output.memo = ""; + if (spendInCurrentTx > 0) + output.v = spendInCurrentTx; + else + output.v = 0; + wtxNew.changes.insert(static_cast(tx.vout.size() + privOutputs.size())); + privOutputs.push_back(output); + } + + + // fill inputs + uint32_t sequence = CTxIn::SEQUENCE_FINAL; + CScript script; + script << OP_SPARKSPEND; + tx.vin.emplace_back(COutPoint(), script, sequence); + + // clear vExtraPayload to calculate metadata hash correctly + tx.vExtraPayload.clear(); + + // set correct type of transaction (this affects metadata hash) + tx.nVersion = 3; + tx.nType = TRANSACTION_SPARK; + + // now every field is populated then we can sign transaction + // We will write this into cover set representation, with anonymity set hash + uint256 sig = tx.GetHash(); + + std::vector inputs; + std::map idAndBlockHashes; + std::unordered_map cover_set_data; + for (auto& coin : estimated.second) { + spark::CSparkState::SparkCoinGroupInfo nextCoinGroupInfo; + uint64_t groupId = coin.second.nId; + if (sparkState->GetLatestCoinID() > groupId && sparkState->GetCoinGroupInfo(groupId + 1, nextCoinGroupInfo)) { + if (nextCoinGroupInfo.firstBlock->nHeight <= coin.second.nHeight) + groupId += 1; } + if (cover_set_data.count(groupId) == 0) { + std::vector set; + uint256 blockHash; + std::vector setHash; + if (sparkState->GetCoinSetForSpend( + &chainActive, + chainActive.Height() - + (ZC_MINT_CONFIRMATIONS - 1), // required 1 confirmation for mint to spend + groupId, + blockHash, + set, + setHash) < 2) + throw std::runtime_error( + _("Has to have at least two mint coins with at least 1 confirmation in order to spend a coin")); + + spark::CoverSetData coverSetData; + coverSetData.cover_set = set; + coverSetData.cover_set_representation = setHash; + coverSetData.cover_set_representation.insert(coverSetData.cover_set_representation.end(), sig.begin(), sig.end()); + cover_set_data[groupId] = coverSetData; + idAndBlockHashes[groupId] = blockHash; + } - // fill inputs - uint32_t sequence = CTxIn::SEQUENCE_FINAL; - CScript script; - script << OP_SPARKSPEND; - tx.vin.emplace_back(COutPoint(), script, sequence); - - // clear vExtraPayload to calculate metadata hash correctly - tx.vExtraPayload.clear(); - - // set correct type of transaction (this affects metadata hash) - tx.nVersion = 3; - tx.nType = TRANSACTION_SPARK; - - // now every field is populated then we can sign transaction - // We will write this into cover set representation, with anonymity set hash - uint256 sig = tx.GetHash(); - - std::vector inputs; - std::map idAndBlockHashes; - std::unordered_map cover_set_data; - for (auto& coin : feeAndSpendCoins.second) { - spark::CSparkState::SparkCoinGroupInfo nextCoinGroupInfo; - uint64_t groupId = coin.second.nId; - if (sparkState->GetLatestCoinID() > groupId && sparkState->GetCoinGroupInfo(groupId + 1, nextCoinGroupInfo)) { - if (nextCoinGroupInfo.firstBlock->nHeight <= coin.second.nHeight) - groupId += 1; - } - if (cover_set_data.count(groupId) == 0) { - std::vector set; - uint256 blockHash; - std::vector setHash; - if (sparkState->GetCoinSetForSpend( - &chainActive, - chainActive.Height() - - (ZC_MINT_CONFIRMATIONS - 1), // required 1 confirmation for mint to spend - groupId, - blockHash, - set, - setHash) < 2) - throw std::runtime_error( - _("Has to have at least two mint coins with at least 1 confirmation in order to spend a coin")); - - spark::CoverSetData coverSetData; - coverSetData.cover_set = set; - coverSetData.cover_set_representation = setHash; - coverSetData.cover_set_representation.insert(coverSetData.cover_set_representation.end(), sig.begin(), sig.end()); - cover_set_data[groupId] = coverSetData; - idAndBlockHashes[groupId] = blockHash; - } + spark::InputCoinData inputCoinData; + inputCoinData.cover_set_id = groupId; + std::size_t index = 0; + if (!getIndex(coin.first, cover_set_data[groupId].cover_set, index)) + throw std::runtime_error( + _("No such coin in set")); + inputCoinData.index = index; + inputCoinData.v = coin.second.v; + inputCoinData.k = coin.second.k; + + spark::IdentifiedCoinData identifiedCoinData; + identifiedCoinData.i = coin.second.i; + identifiedCoinData.d = coin.second.d; + identifiedCoinData.v = coin.second.v; + identifiedCoinData.k = coin.second.k; + identifiedCoinData.memo = coin.second.memo; + spark::RecoveredCoinData recoveredCoinData = coin.first.recover(fullViewKey, identifiedCoinData); + + inputCoinData.T = recoveredCoinData.T; + inputCoinData.s = recoveredCoinData.s; + inputs.push_back(inputCoinData); + } - spark::InputCoinData inputCoinData; - inputCoinData.cover_set_id = groupId; - std::size_t index = 0; - if (!getIndex(coin.first, cover_set_data[groupId].cover_set, index)) - throw std::runtime_error( - _("No such coin in set")); - inputCoinData.index = index; - inputCoinData.v = coin.second.v; - inputCoinData.k = coin.second.k; - - spark::IdentifiedCoinData identifiedCoinData; - identifiedCoinData.i = coin.second.i; - identifiedCoinData.d = coin.second.d; - identifiedCoinData.v = coin.second.v; - identifiedCoinData.k = coin.second.k; - identifiedCoinData.memo = coin.second.memo; - spark::RecoveredCoinData recoveredCoinData = coin.first.recover(fullViewKey, identifiedCoinData); - - inputCoinData.T = recoveredCoinData.T; - inputCoinData.s = recoveredCoinData.s; - inputs.push_back(inputCoinData); + spark::SpendTransaction spendTransaction(params, fullViewKey, spendKey, inputs, cover_set_data, fee, transparentOut, privOutputs); + spendTransaction.setBlockHashes(idAndBlockHashes); + CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); + serialized << spendTransaction; + tx.vExtraPayload.assign(serialized.begin(), serialized.end()); - } - spark::SpendTransaction spendTransaction(params, fullViewKey, spendKey, inputs, cover_set_data, fee, transparentOut, privOutputs); - spendTransaction.setBlockHashes(idAndBlockHashes); + const std::vector& outCoins = spendTransaction.getOutCoins(); + for (auto& outCoin : outCoins) { + // construct spend script CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); - serialized << spendTransaction; - tx.vExtraPayload.assign(serialized.begin(), serialized.end()); - - - const std::vector& outCoins = spendTransaction.getOutCoins(); - for (auto& outCoin : outCoins) { - // construct spend script - CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); - serialized << outCoin; - CScript script; - script << OP_SPARKSMINT; - script.insert(script.end(), serialized.begin(), serialized.end()); - tx.vout.push_back(CTxOut(0, script)); - } + serialized << outCoin; + CScript script; + script << OP_SPARKSMINT; + script.insert(script.end(), serialized.begin(), serialized.end()); + tx.vout.push_back(CTxOut(0, script)); + } - // check fee - wtxNew.SetTx(MakeTransactionRef(std::move(tx))); + // check fee + wtxNew.SetTx(MakeTransactionRef(std::move(tx))); - if (GetTransactionWeight(tx) >= MAX_NEW_TX_WEIGHT) { - throw std::runtime_error(_("Transaction too large")); - } + if (GetTransactionWeight(tx) >= MAX_NEW_TX_WEIGHT) { + throw std::runtime_error(_("Transaction too large")); + } - // check fee - unsigned size = GetVirtualTransactionSize(tx); - CAmount feeNeeded = CWallet::GetMinimumFee(size, nTxConfirmTarget, mempool); + // check fee + unsigned size = GetVirtualTransactionSize(tx); + CAmount feeNeeded = CWallet::GetMinimumFee(size, nTxConfirmTarget, mempool); - // If we made it here and we aren't even able to meet the relay fee on the next pass, give up - // because we must be at the maximum allowed fee. - if (feeNeeded < minRelayTxFee.GetFee(size)) { - throw std::invalid_argument(_("Transaction too large for fee policy")); - } + // If we made it here and we aren't even able to meet the relay fee on the next pass, give up + // because we must be at the maximum allowed fee. + if (feeNeeded < minRelayTxFee.GetFee(size)) { + throw std::invalid_argument(_("Transaction too large for fee policy")); + } - if (fee < feeNeeded) { - throw std::invalid_argument(_("Not enough fee estimated")); - } + if (fee < feeNeeded) { + throw std::invalid_argument(_("Not enough fee estimated")); + } result.push_back(wtxNew); - } } } @@ -1470,7 +1460,7 @@ std::vector CSparkWallet::CreateSparkSpendTransaction( } } - return result; + return wtxNew; } template @@ -1487,7 +1477,6 @@ bool GetCoinsToSpend( std::vector>& coinsToSpend_out, std::list> coins, int64_t& changeToMint, - const size_t coinsToSpendLimit, const CCoinControl *coinControl) { CAmount availableBalance = CalculateBalance(coins.begin(), coins.end()); @@ -1548,9 +1537,6 @@ bool GetCoinsToSpend( spend_val += choosen.second.v; coinsToSpend.push_back(choosen); - - if (coinsToSpend.size() == coinsToSpendLimit) // if we pass input number limit, we stop and try to spend remaining part with another transaction - break; } } @@ -1566,7 +1552,7 @@ bool GetCoinsToSpend( return true; } -std::vector>>> CSparkWallet::SelectSparkCoins( +std::pair>> CSparkWallet::SelectSparkCoins( CAmount required, bool subtractFeeFromAmount, std::list> coins, @@ -1574,48 +1560,40 @@ std::vector>>> result; + CAmount fee; + unsigned size; + int64_t changeToMint = 0; // this value can be negative, that means we need to spend remaining part of required value with another transaction (nMaxInputPerTransaction exceeded) - while (required > 0) { - CAmount fee; - unsigned size; - int64_t changeToMint = 0; // this value can be negative, that means we need to spend remaining part of required value with another transaction (nMaxInputPerTransaction exceeded) + std::vector> spendCoins; + for (fee = payTxFee.GetFeePerK();;) { + CAmount currentRequired = required; - std::vector> spendCoins; - for (fee = payTxFee.GetFeePerK();;) { - CAmount currentRequired = required; + if (!subtractFeeFromAmount) + currentRequired += fee; + spendCoins.clear(); + if (!GetCoinsToSpend(currentRequired, spendCoins, coins, changeToMint, coinControl)) { + throw std::invalid_argument(_("Unable to select cons for spend")); + } - if (!subtractFeeFromAmount) - currentRequired += fee; - spendCoins.clear(); - const auto &consensusParams = Params().GetConsensus(); - if (!GetCoinsToSpend(currentRequired, spendCoins, coins, changeToMint, - consensusParams.nMaxSparkInputPerTransaction, coinControl)) { - throw std::invalid_argument(_("Unable to select cons for spend")); - } + // 924 is constant part, mainly Schnorr and Range proofs, 2535 is for each grootle proof/aux data + // 213 for each private output, 144 other parts of tx, + size = 924 + 2535 * (spendCoins.size()) + 213 * mintNum + 144; //TODO (levon) take in account also utxoNum + CAmount feeNeeded = CWallet::GetMinimumFee(size, nTxConfirmTarget, mempool); - // 924 is constant part, mainly Schnorr and Range proofs, 2535 is for each grootle proof/aux data - // 213 for each private output, 144 other parts of tx, - size = 924 + 2535 * (spendCoins.size()) + 213 * mintNum + 144; //TODO (levon) take in account also utxoNum - CAmount feeNeeded = CWallet::GetMinimumFee(size, nTxConfirmTarget, mempool); + if (fee >= feeNeeded) { + break; + } - if (fee >= feeNeeded) { - break; - } + fee = feeNeeded; - fee = feeNeeded; + if (subtractFeeFromAmount) + break; + } - if (subtractFeeFromAmount) - break; - } + if (changeToMint < 0) + throw std::invalid_argument(_("Unable to select cons for spend")); - result.push_back({fee, spendCoins}); - if (changeToMint < 0) - required = - changeToMint; - else - required = 0; - } - return result; + return std::make_pair(fee, spendCoins); } std::list> CSparkWallet::GetAvailableSparkCoins(const CCoinControl *coinControl) const { diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 8e5899826a..7153f0b360 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -114,13 +114,13 @@ class CSparkWallet { const CCoinControl *coinControl, bool autoMintAll = false); - std::vector CreateSparkSpendTransaction( + CWalletTx CreateSparkSpendTransaction( const std::vector& recipients, const std::vector>& privateRecipients, CAmount &fee, const CCoinControl *coinControl = NULL); - std::vector>>> SelectSparkCoins( + std::pair>> SelectSparkCoins( CAmount required, bool subtractFeeFromAmount, std::list> coins, diff --git a/src/spark/state.cpp b/src/spark/state.cpp index e1d9ee05ba..b25c553595 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -365,26 +365,19 @@ void DisconnectTipSpark(CBlock& block, CBlockIndex *pindexDelete) { bool CheckSparkBlock(CValidationState &state, const CBlock& block) { auto& consensus = ::Params().GetConsensus(); - size_t blockSpendsAmount = 0; + size_t blockSpendsValue = 0; for (const auto& tx : block.vtx) { auto txSpendsValue = GetSpendTransparentAmount(*tx); - size_t txSpendNumber = GetSpendInputs(*tx); - - if (txSpendNumber > consensus.nMaxSparkInputPerTransaction) { - return state.DoS(100, false, REJECT_INVALID, - "bad-txns-spark-spend-invalid"); - } if (txSpendsValue > consensus.nMaxValueSparkSpendPerTransaction) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-spark-spend-invalid"); } - - blockSpendsAmount += txSpendNumber; + blockSpendsValue += txSpendsValue; } - if (blockSpendsAmount > consensus.nMaxLelantusInputPerBlock) { + if (blockSpendsValue > consensus.nMaxValueSparkSpendPerBlock) { return state.DoS(100, false, REJECT_INVALID, "bad-txns-spark-spend-invalid"); } @@ -787,13 +780,6 @@ bool CheckSparkTransaction( // Check Spark Spend if (tx.IsSparkSpend()) { - // First check number of inputs does not exceed transaction limit - if (GetSpendInputs(tx) > consensus.nMaxSparkInputPerTransaction) { - return state.DoS(100, false, - REJECT_INVALID, - "bad-txns-spend-invalid"); - } - if (GetSpendTransparentAmount(tx) > consensus.nMaxValueSparkSpendPerTransaction) { return state.DoS(100, false, REJECT_INVALID, diff --git a/src/test/fixtures.cpp b/src/test/fixtures.cpp index fbc7fe490d..65b9297a43 100644 --- a/src/test/fixtures.cpp +++ b/src/test/fixtures.cpp @@ -395,7 +395,7 @@ std::vector SparkTestingSetup::GenerateMints( return mints; } -std::vector SparkTestingSetup::GenerateSparkSpend( +CTransaction SparkTestingSetup::GenerateSparkSpend( std::vector const &outs, std::vector const &mints, CCoinControl const *coinControl = nullptr) { @@ -414,15 +414,10 @@ std::vector SparkTestingSetup::GenerateSparkSpend( } CAmount fee; - auto txs = pwalletMain->SpendAndStoreSpark( + auto wtx = pwalletMain->SpendAndStoreSpark( vecs, {}, fee, coinControl); - std::vector result; - for (auto& itr : txs) { - result.push_back(*itr.tx); - } - - return result; + return *wtx.tx; } diff --git a/src/test/fixtures.h b/src/test/fixtures.h index 57e2696ec1..7b21ab2a27 100644 --- a/src/test/fixtures.h +++ b/src/test/fixtures.h @@ -115,7 +115,7 @@ struct SparkTestingSetup : public TestChain100Setup std::vector const &amounts, std::vector &txs); - std::vector GenerateSparkSpend( + CTransaction GenerateSparkSpend( std::vector const &outs, std::vector const &mints, CCoinControl const *coinControl ); diff --git a/src/test/spark_mintspend_test.cpp b/src/test/spark_mintspend_test.cpp index a972dcb3ed..a3c4625c3e 100644 --- a/src/test/spark_mintspend_test.cpp +++ b/src/test/spark_mintspend_test.cpp @@ -62,13 +62,13 @@ BOOST_AUTO_TEST_CASE(spark_mintspend_test) BOOST_CHECK_MESSAGE(mempool.size() == 1, "SparkSpend is not added into mempool"); previousHeight = chainActive.Height(); - GenerateBlock({CMutableTransaction(wtx[0])}); + GenerateBlock({CMutableTransaction(wtx)}); BOOST_CHECK_MESSAGE(previousHeight + 1 == chainActive.Height(), "Block not added to chain"); BOOST_CHECK_MESSAGE(mempool.size() == 0, "SparkSpend is not removed from mempool"); GenerateBlocks(6); CAmount fee; - auto result = pwalletMain->CreateSparkSpendTransaction(recipients, {}, fee, nullptr)[0]; + auto result = pwalletMain->CreateSparkSpendTransaction(recipients, {}, fee, nullptr); CWallet* wallet = pwalletMain; CReserveKey reserveKey(wallet); CValidationState state; @@ -102,7 +102,7 @@ BOOST_AUTO_TEST_CASE(spark_mintspend_test) CAmount fee; result.Init(NULL); - result = pwalletMain->CreateSparkSpendTransaction(recipients, {}, fee, &coinControl)[0]; + result = pwalletMain->CreateSparkSpendTransaction(recipients, {}, fee, &coinControl); CReserveKey reserveKey(pwalletMain); CValidationState state; pwalletMain->CommitTransaction(result, reserveKey, g_connman.get(), state); diff --git a/src/test/spark_tests.cpp b/src/test/spark_tests.cpp index 7ea628d53f..16ff210bf8 100644 --- a/src/test/spark_tests.cpp +++ b/src/test/spark_tests.cpp @@ -394,17 +394,17 @@ BOOST_AUTO_TEST_CASE(connect_and_disconnect_block) std::vector dupNewCoins1; std::vector dupTags1; - ExtractSpend(dupTx1[0], dupNewCoins1, dupTags1); + ExtractSpend(dupTx1, dupNewCoins1, dupTags1); std::vector newCoins1; std::vector tags1; - ExtractSpend(sTx1[0], newCoins1, tags1); + ExtractSpend(sTx1, newCoins1, tags1); BOOST_CHECK_EQUAL(1, newCoins1.size()); BOOST_CHECK_EQUAL(1, tags1.size()); BOOST_CHECK(dupTags1[0] == tags1[0]); mempool.clear(); - auto blockIdx2 = GenerateBlock({sTx1[0]}); + auto blockIdx2 = GenerateBlock({sTx1}); BOOST_CHECK(blockIdx2); auto block2 = GetCBlock(blockIdx2); @@ -430,14 +430,14 @@ BOOST_AUTO_TEST_CASE(connect_and_disconnect_block) std::vector newCoins2; std::vector tags2; - ExtractSpend(sTx2[0], newCoins2, tags2); + ExtractSpend(sTx2, newCoins2, tags2); BOOST_CHECK_EQUAL(1, newCoins2.size()); BOOST_CHECK_EQUAL(1, tags2.size()); BOOST_CHECK(mempool.size() == 1); mempool.clear(); std::vector blockTX; - auto blockIdx3 = GenerateBlock({mintTxs2[0], sTx2[0]}); + auto blockIdx3 = GenerateBlock({mintTxs2[0], sTx2}); BOOST_CHECK(blockIdx3); auto block3 = GetCBlock(blockIdx3); @@ -472,7 +472,7 @@ BOOST_AUTO_TEST_CASE(connect_and_disconnect_block) // double spend auto currentBlock = chainActive.Tip()->nHeight; - BOOST_CHECK(!GenerateBlock({dupTx1[0]})); + BOOST_CHECK(!GenerateBlock({dupTx1})); BOOST_CHECK_EQUAL(currentBlock, chainActive.Tip()->nHeight); mempool.clear(); sparkState->Reset(); @@ -510,7 +510,7 @@ BOOST_AUTO_TEST_CASE(checktransaction) auto outputAmount = 1 * COIN; auto mintAmount = 2 * CENT - CENT; // a cent as fee CAmount fee; - CWalletTx wtx = pwalletMain->SpendAndStoreSpark({{script, outputAmount, false}}, {}, fee)[0]; + CWalletTx wtx = pwalletMain->SpendAndStoreSpark({{script, outputAmount, false}}, {}, fee); CMutableTransaction spendTx(wtx); auto spend = ParseSparkSpend(spendTx); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index aa3e3ac529..4c1831a0bb 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3722,19 +3722,14 @@ UniValue spendspark(const JSONRPCRequest& request) } CAmount fee; - std::vector wtxs; + CWalletTx wtx; try { - wtxs = pwallet->SpendAndStoreSpark(recipients, privateRecipients, fee); + wtx = pwallet->SpendAndStoreSpark(recipients, privateRecipients, fee); } catch (...) { throw JSONRPCError(RPC_WALLET_ERROR, "Spark spend creation failed."); } - UniValue result(UniValue::VARR); - for(const auto& wtx : wtxs) { - result.push_back(wtx.GetHash().GetHex()); - } - - return result; + return wtx.GetHash().GetHex(); } UniValue lelantustospark(const JSONRPCRequest& request) { diff --git a/src/wallet/test/spark_tests.cpp b/src/wallet/test/spark_tests.cpp index 7f075d0dfe..7ed925fb2d 100644 --- a/src/wallet/test/spark_tests.cpp +++ b/src/wallet/test/spark_tests.cpp @@ -222,11 +222,10 @@ BOOST_AUTO_TEST_CASE(spend) wtxAndFee.clear(); auto spTx = GenerateSparkSpend({1 * COIN}, {}, nullptr); - BOOST_CHECK(!(spTx.empty())); std::vector coins; std::vector tags; - ExtractSpend(spTx[0], coins, tags); + ExtractSpend(spTx, coins, tags); BOOST_CHECK_EQUAL(1, coins.size()); BOOST_CHECK_EQUAL(1, tags.size()); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 2cfcfc8577..a605c90e30 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5623,7 +5623,7 @@ CWalletTx CWallet::CreateLelantusJoinSplitTransaction( return tx; } -std::vector CWallet::CreateSparkSpendTransaction( +CWalletTx CWallet::CreateSparkSpendTransaction( const std::vector& recipients, const std::vector>& privateRecipients, CAmount &fee, @@ -5639,7 +5639,7 @@ std::vector CWallet::CreateSparkSpendTransaction( return sparkWallet->CreateSparkSpendTransaction(recipients, privateRecipients, fee, coinControl); } -std::vector CWallet::SpendAndStoreSpark( +CWalletTx CWallet::SpendAndStoreSpark( const std::vector& recipients, const std::vector>& privateRecipients, CAmount &fee, @@ -5649,21 +5649,19 @@ std::vector CWallet::SpendAndStoreSpark( auto result = CreateSparkSpendTransaction(recipients, privateRecipients, fee, coinControl); // commit - for (auto& wtxNew : result) { - try { - CValidationState state; - CReserveKey reserveKey(this); - CommitTransaction(wtxNew, reserveKey, g_connman.get(), state); - } catch (...) { - auto error = _( - "Error: The transaction was rejected! This might happen if some of " - "the coins in your wallet were already spent, such as if you used " - "a copy of wallet.dat and coins were spent in the copy but not " - "marked as spent here." - ); - - std::throw_with_nested(std::runtime_error(error)); - } + try { + CValidationState state; + CReserveKey reserveKey(this); + CommitTransaction(result, reserveKey, g_connman.get(), state); + } catch (...) { + auto error = _( + "Error: The transaction was rejected! This might happen if some of " + "the coins in your wallet were already spent, such as if you used " + "a copy of wallet.dat and coins were spent in the copy but not " + "marked as spent here." + ); + + std::throw_with_nested(std::runtime_error(error)); } return result; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 88b3b00d65..33148312bc 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1066,13 +1066,13 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool fAskFee = false, const CCoinControl *coinControl = NULL); - std::vector CreateSparkSpendTransaction( + CWalletTx CreateSparkSpendTransaction( const std::vector& recipients, const std::vector>& privateRecipients, CAmount &fee, const CCoinControl *coinControl = NULL); - std::vector SpendAndStoreSpark( + CWalletTx SpendAndStoreSpark( const std::vector& recipients, const std::vector>& privateRecipients, CAmount &fee, From b3bff1b41809f7ebe29d3c53c6d6f16e06474754 Mon Sep 17 00:00:00 2001 From: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Date: Mon, 8 May 2023 00:08:39 -0500 Subject: [PATCH 110/197] Offloaded signing (#1237) * Use `SHA-512` for broader hash function support * Use pre-hashed auxiliary data for binding hash * Simplify binding hash --- src/libspark/f4grumble.cpp | 18 +++----- src/libspark/hash.cpp | 30 +++++++++---- src/libspark/hash.h | 1 + src/libspark/kdf.cpp | 6 +-- src/libspark/spend_transaction.cpp | 72 +++++++++++++++++------------- src/libspark/spend_transaction.h | 11 ++--- src/libspark/transcript.cpp | 10 ++--- src/libspark/util.cpp | 12 ++--- src/libspark/util.h | 1 + 9 files changed, 91 insertions(+), 70 deletions(-) diff --git a/src/libspark/f4grumble.cpp b/src/libspark/f4grumble.cpp index dc7e94dd50..839c27e9de 100644 --- a/src/libspark/f4grumble.cpp +++ b/src/libspark/f4grumble.cpp @@ -1,11 +1,7 @@ // A design for address scrambling based on `f4jumble`: https://zips.z.cash/zip-0316#jumbling -// This design differs from `f4jumble` to account for OpenSSL limitations on Blake2b +// This design differs from `f4jumble` to account for limitations on SHA512 // These limitations are unfortunate, but such is life sometimes // -// Namely, we have the following dependency limitations on Blake2b: -// - Output is fixed at either 256 or 512 bits -// - Personalization is not supported -// // To account for these limitations, we do the following: // - Place extra restrictions on length to avoid XOF input encoding (and because we don't need it) // - Replace personalization with fixed-length inputs; note that length is NOT prepended @@ -36,13 +32,13 @@ std::vector F4Grumble::vec_xor(const std::vector& // Return the maximum allowed input size in bytes std::size_t F4Grumble::get_max_size() { - return 2 * EVP_MD_size(EVP_blake2b512()); + return 2 * EVP_MD_size(EVP_sha512()); } // Instantiate with a given network identifier and expected input length F4Grumble::F4Grumble(const unsigned char network, const int l_M) { // Assert the length is valid - if (l_M > 2 * EVP_MD_size(EVP_blake2b512())) { + if (l_M > 2 * EVP_MD_size(EVP_sha512())) { throw std::invalid_argument("Bad address size"); } @@ -101,7 +97,7 @@ std::vector F4Grumble::decode(const std::vector& i // Feistel round functions std::vector F4Grumble::G(const unsigned char i, const std::vector& u) { EVP_MD_CTX* ctx = EVP_MD_CTX_new(); - EVP_DigestInit_ex(ctx, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); // Bind the domain separator and network std::vector domain(LABEL_F4GRUMBLE_G.begin(), LABEL_F4GRUMBLE_G.end()); @@ -116,7 +112,7 @@ std::vector F4Grumble::G(const unsigned char i, const std::vector // Finalize the hash and resize std::vector result; - result.resize(EVP_MD_size(EVP_blake2b512())); + result.resize(EVP_MD_size(EVP_sha512())); unsigned int TEMP; EVP_DigestFinal_ex(ctx, result.data(), &TEMP); @@ -128,7 +124,7 @@ std::vector F4Grumble::G(const unsigned char i, const std::vector std::vector F4Grumble::H(const unsigned char i, const std::vector& u) { EVP_MD_CTX* ctx = EVP_MD_CTX_new(); - EVP_DigestInit_ex(ctx, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); // Bind the domain separator and network std::vector domain(LABEL_F4GRUMBLE_H.begin(), LABEL_F4GRUMBLE_H.end()); @@ -143,7 +139,7 @@ std::vector F4Grumble::H(const unsigned char i, const std::vector // Finalize the hash and resize std::vector result; - result.resize(EVP_MD_size(EVP_blake2b512())); + result.resize(EVP_MD_size(EVP_sha512())); unsigned int TEMP; EVP_DigestFinal_ex(ctx, result.data(), &TEMP); diff --git a/src/libspark/hash.cpp b/src/libspark/hash.cpp index 050b8e1c8d..f481519c31 100644 --- a/src/libspark/hash.cpp +++ b/src/libspark/hash.cpp @@ -7,7 +7,7 @@ using namespace secp_primitives; // Set up a labeled hash function Hash::Hash(const std::string label) { this->ctx = EVP_MD_CTX_new(); - EVP_DigestInit_ex(this->ctx, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(this->ctx, EVP_sha512(), NULL); // Write the protocol and mode information std::vector protocol(LABEL_PROTOCOL.begin(), LABEL_PROTOCOL.end()); @@ -31,24 +31,36 @@ void Hash::include(CDataStream& data) { EVP_DigestUpdate(this->ctx, reinterpret_cast(data.data()), data.size()); } +// Finalize the hash function to a byte array +std::vector Hash::finalize() { + // Use the full output size of the hash function + std::vector result; + result.resize(EVP_MD_size(EVP_sha512())); + + unsigned int TEMP; + EVP_DigestFinal_ex(this->ctx, result.data(), &TEMP); + + return result; +} + // Finalize the hash function to a scalar Scalar Hash::finalize_scalar() { // Ensure we can properly populate a scalar - if (EVP_MD_size(EVP_blake2b512()) < SCALAR_ENCODING) { + if (EVP_MD_size(EVP_sha512()) < SCALAR_ENCODING) { throw std::runtime_error("Bad hash size!"); } std::vector hash; - hash.resize(EVP_MD_size(EVP_blake2b512())); + hash.resize(EVP_MD_size(EVP_sha512())); unsigned char counter = 0; EVP_MD_CTX* state_counter; state_counter = EVP_MD_CTX_new(); - EVP_DigestInit_ex(state_counter, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(state_counter, EVP_sha512(), NULL); EVP_MD_CTX* state_finalize; state_finalize = EVP_MD_CTX_new(); - EVP_DigestInit_ex(state_finalize, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(state_finalize, EVP_sha512(), NULL); while (1) { // Prepare temporary state for counter testing @@ -83,21 +95,21 @@ GroupElement Hash::finalize_group() { const unsigned char ZERO = 0; // Ensure we can properly populate a - if (EVP_MD_size(EVP_blake2b512()) < GROUP_ENCODING) { + if (EVP_MD_size(EVP_sha512()) < GROUP_ENCODING) { throw std::runtime_error("Bad hash size!"); } std::vector hash; - hash.resize(EVP_MD_size(EVP_blake2b512())); + hash.resize(EVP_MD_size(EVP_sha512())); unsigned char counter = 0; EVP_MD_CTX* state_counter; state_counter = EVP_MD_CTX_new(); - EVP_DigestInit_ex(state_counter, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(state_counter, EVP_sha512(), NULL); EVP_MD_CTX* state_finalize; state_finalize = EVP_MD_CTX_new(); - EVP_DigestInit_ex(state_finalize, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(state_finalize, EVP_sha512(), NULL); while (1) { // Prepare temporary state for counter testing diff --git a/src/libspark/hash.h b/src/libspark/hash.h index dfd63ccb5e..39cd250bfa 100644 --- a/src/libspark/hash.h +++ b/src/libspark/hash.h @@ -12,6 +12,7 @@ class Hash { Hash(const std::string label); ~Hash(); void include(CDataStream& data); + std::vector finalize(); Scalar finalize_scalar(); GroupElement finalize_group(); diff --git a/src/libspark/kdf.cpp b/src/libspark/kdf.cpp index c397eab071..9110411fa7 100644 --- a/src/libspark/kdf.cpp +++ b/src/libspark/kdf.cpp @@ -5,7 +5,7 @@ namespace spark { // Set up a labeled KDF KDF::KDF(const std::string label, std::size_t derived_key_size) { this->ctx = EVP_MD_CTX_new(); - EVP_DigestInit_ex(this->ctx, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(this->ctx, EVP_sha512(), NULL); // Write the protocol and mode information std::vector protocol(LABEL_PROTOCOL.begin(), LABEL_PROTOCOL.end()); @@ -18,7 +18,7 @@ KDF::KDF(const std::string label, std::size_t derived_key_size) { EVP_DigestUpdate(this->ctx, label_bytes.data(), label_bytes.size()); // Embed and set the derived key size - if (derived_key_size > EVP_MD_size(EVP_blake2b512())) { + if (derived_key_size > EVP_MD_size(EVP_sha512())) { throw std::invalid_argument("Requested KDF size is too large"); } include_size(derived_key_size); @@ -39,7 +39,7 @@ void KDF::include(CDataStream& data) { // Finalize the KDF with arbitrary size std::vector KDF::finalize() { std::vector result; - result.resize(EVP_MD_size(EVP_blake2b512())); + result.resize(EVP_MD_size(EVP_sha512())); unsigned int TEMP; EVP_DigestFinal_ex(this->ctx, result.data(), &TEMP); diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index 51fa81056a..c689ca2d14 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -176,15 +176,15 @@ SpendTransaction::SpendTransaction( // Compute the binding hash Scalar mu = hash_bind( + hash_bind_inner( + this->cover_set_representations, + this->C1, + this->grootle_proofs, + this->balance_proof, + this->range_proof + ), this->out_coins, - this->f + vout, - this->cover_set_representations, - this->S1, - this->C1, - this->T, - this->grootle_proofs, - this->balance_proof, - this->range_proof + this->f + vout ); // Compute the authorizing Chaum proof @@ -293,15 +293,15 @@ bool SpendTransaction::verify( // Compute the binding hash Scalar mu = hash_bind( + hash_bind_inner( + tx.cover_set_representations, + tx.C1, + tx.grootle_proofs, + tx.balance_proof, + tx.range_proof + ), tx.out_coins, - tx.f + tx.vout, - tx.cover_set_representations, - tx.S1, - tx.C1, - tx.T, - tx.grootle_proofs, - tx.balance_proof, - tx.range_proof + tx.f + tx.vout ); // Verify the authorizing Chaum-Pedersen proof @@ -403,25 +403,18 @@ bool SpendTransaction::verify( return true; } -// Hash-to-scalar function H_bind -Scalar SpendTransaction::hash_bind( - const std::vector& out_coins, - const uint64_t f_, +// Hash function H_bind_inner +// This function pre-hashes auxiliary data that makes things easier for a limited signer who cannot process the data directly +// Its value is then used as part of the binding hash, which a limited signer can verify as part of the signing process +std::vector SpendTransaction::hash_bind_inner( const std::unordered_map>& cover_set_representations, - const std::vector& S1, - const std::vector& C1, - const std::vector& T, - const std::vector& grootle_proofs, - const SchnorrProof& balance_proof, + const std::vector& C1, + const std::vector& grootle_proofs, + const SchnorrProof& balance_proof, const BPPlusProof& range_proof ) { - Hash hash(LABEL_HASH_BIND); - + Hash hash(LABEL_HASH_BIND_INNER); CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); - - // Perform the serialization and hashing - stream << out_coins; - stream << f_; stream << cover_set_representations; stream << S1; stream << C1; @@ -431,6 +424,23 @@ Scalar SpendTransaction::hash_bind( stream << range_proof; hash.include(stream); + return hash.finalize(); +} + +// Hash-to-scalar function H_bind +// This function must accept pre-hashed data from `H_bind_inner` intended to correspond to the signing operation +Scalar SpendTransaction::hash_bind( + const std::vector hash_bind_inner, + const std::vector& out_coins, + const uint64_t f_ +) { + Hash hash(LABEL_HASH_BIND); + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << hash_bind_inner, + stream << out_coins; + stream << f_; + hash.include(stream); + return hash.finalize_scalar(); } diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h index 167ff7843f..30c481876f 100644 --- a/src/libspark/spend_transaction.h +++ b/src/libspark/spend_transaction.h @@ -60,17 +60,18 @@ class SpendTransaction { static bool verify(const Params* params, const std::vector& transactions, const std::unordered_map>& cover_sets); static bool verify(const SpendTransaction& transaction, const std::unordered_map>& cover_sets); - static Scalar hash_bind( - const std::vector& out_coins, - const uint64_t f_, + static std::vector hash_bind_inner( const std::unordered_map>& cover_set_representations, - const std::vector& S1, const std::vector& C1, - const std::vector& T, const std::vector& grootle_proofs, const SchnorrProof& balance_proof, const BPPlusProof& range_proof ); + static Scalar hash_bind( + const std::vector hash_bind_inner, + const std::vector& out_coins, + const uint64_t f_ + ); ADD_SERIALIZE_METHODS; template diff --git a/src/libspark/transcript.cpp b/src/libspark/transcript.cpp index 238e2d3ae9..8cada15b2e 100644 --- a/src/libspark/transcript.cpp +++ b/src/libspark/transcript.cpp @@ -14,7 +14,7 @@ const unsigned char FLAG_CHALLENGE = 3; Transcript::Transcript(const std::string domain) { // Prepare the state this->ctx = EVP_MD_CTX_new(); - EVP_DigestInit_ex(this->ctx, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(this->ctx, EVP_sha512(), NULL); // Write the protocol and mode information std::vector protocol(LABEL_PROTOCOL.begin(), LABEL_PROTOCOL.end()); @@ -98,21 +98,21 @@ void Transcript::add(const std::string label, const std::vector& // Produce a challenge Scalar Transcript::challenge(const std::string label) { // Ensure we can properly populate a scalar - if (EVP_MD_size(EVP_blake2b512()) < SCALAR_ENCODING) { + if (EVP_MD_size(EVP_sha512()) < SCALAR_ENCODING) { throw std::runtime_error("Bad hash size!"); } std::vector hash; - hash.resize(EVP_MD_size(EVP_blake2b512())); + hash.resize(EVP_MD_size(EVP_sha512())); unsigned char counter = 0; EVP_MD_CTX* state_counter; state_counter = EVP_MD_CTX_new(); - EVP_DigestInit_ex(state_counter, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(state_counter, EVP_sha512(), NULL); EVP_MD_CTX* state_finalize; state_finalize = EVP_MD_CTX_new(); - EVP_DigestInit_ex(state_finalize, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(state_finalize, EVP_sha512(), NULL); include_flag(FLAG_CHALLENGE); include_label(label); diff --git a/src/libspark/util.cpp b/src/libspark/util.cpp index 2e785f2879..faf3b67870 100644 --- a/src/libspark/util.cpp +++ b/src/libspark/util.cpp @@ -56,14 +56,14 @@ GroupElement SparkUtils::hash_generator(const std::string label) { const int GROUP_ENCODING = 34; const unsigned char ZERO = 0; - // Ensure we can properly populate a - if (EVP_MD_size(EVP_blake2b512()) < GROUP_ENCODING) { + // Ensure we can properly populate a group element encoding + if (EVP_MD_size(EVP_sha512()) < GROUP_ENCODING) { throw std::runtime_error("Bad hash size!"); } EVP_MD_CTX* ctx; ctx = EVP_MD_CTX_new(); - EVP_DigestInit_ex(ctx, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(ctx, EVP_sha512(), NULL); // Write the protocol and mode std::vector protocol(LABEL_PROTOCOL.begin(), LABEL_PROTOCOL.end()); @@ -75,16 +75,16 @@ GroupElement SparkUtils::hash_generator(const std::string label) { EVP_DigestUpdate(ctx, bytes.data(), bytes.size()); std::vector hash; - hash.resize(EVP_MD_size(EVP_blake2b512())); + hash.resize(EVP_MD_size(EVP_sha512())); unsigned char counter = 0; EVP_MD_CTX* state_counter; state_counter = EVP_MD_CTX_new(); - EVP_DigestInit_ex(state_counter, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(state_counter, EVP_sha512(), NULL); EVP_MD_CTX* state_finalize; state_finalize = EVP_MD_CTX_new(); - EVP_DigestInit_ex(state_finalize, EVP_blake2b512(), NULL); + EVP_DigestInit_ex(state_finalize, EVP_sha512(), NULL); // Finalize the hash while (1) { diff --git a/src/libspark/util.h b/src/libspark/util.h index e95c6ded6f..4d4e015936 100644 --- a/src/libspark/util.h +++ b/src/libspark/util.h @@ -48,6 +48,7 @@ const std::string LABEL_HASH_SER = "SER"; const std::string LABEL_HASH_VAL = "VAL"; const std::string LABEL_HASH_SER1 = "SER1"; const std::string LABEL_HASH_VAL1 = "VAL1"; +const std::string LABEL_HASH_BIND_INNER = "BIND_INNER"; const std::string LABEL_HASH_BIND = "BIND"; const std::string LABEL_F4GRUMBLE_G = "SPARK_F4GRUMBLE_G"; const std::string LABEL_F4GRUMBLE_H = "SPARK_F4GRUMBLE_H"; From 1fcd28146205fb76f5a7a873cda98050c225d090 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 8 May 2023 09:10:16 +0400 Subject: [PATCH 111/197] Version bump --- configure.ac | 2 +- src/clientversion.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 18a7a0581f..0f87fc5fed 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 14) define(_CLIENT_VERSION_REVISION, 12) -define(_CLIENT_VERSION_BUILD, 2) +define(_CLIENT_VERSION_BUILD, 3) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2022) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/src/clientversion.h b/src/clientversion.h index fe1154d62e..1564d2d236 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -17,7 +17,7 @@ #define CLIENT_VERSION_MAJOR 0 #define CLIENT_VERSION_MINOR 14 #define CLIENT_VERSION_REVISION 12 -#define CLIENT_VERSION_BUILD 2 +#define CLIENT_VERSION_BUILD 3 //! Set to true for release, false for prerelease or test build #define CLIENT_VERSION_IS_RELEASE true From 510fc4a673f4b6c332f22f96ff402b4a75034864 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 8 May 2023 09:44:11 +0400 Subject: [PATCH 112/197] Spark address prefix changed --- src/libspark/util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libspark/util.h b/src/libspark/util.h index 4d4e015936..015c67e074 100644 --- a/src/libspark/util.h +++ b/src/libspark/util.h @@ -65,7 +65,7 @@ const int AEAD_TAG_SIZE = 16; // byte length of the tag const int AEAD_COMMIT_SIZE = 32; // byte length of the key commitment // Address encoding prefix -const unsigned char ADDRESS_ENCODING_PREFIX = 'p'; +const unsigned char ADDRESS_ENCODING_PREFIX = 's'; // Address encoding network identifiers // TODO: Extend/update/replace these as needed! These are just initial examples From f21a851c7e536f52b9d1d7c0e7e6724cd5d48786 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sat, 13 May 2023 08:00:37 +0400 Subject: [PATCH 113/197] Compile error fixed --- src/libspark/spend_transaction.cpp | 2 +- src/libspark/spend_transaction.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index c689ca2d14..dbd48ebcd4 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -293,7 +293,7 @@ bool SpendTransaction::verify( // Compute the binding hash Scalar mu = hash_bind( - hash_bind_inner( + tx.hash_bind_inner( tx.cover_set_representations, tx.C1, tx.grootle_proofs, diff --git a/src/libspark/spend_transaction.h b/src/libspark/spend_transaction.h index 30c481876f..6b2803da5a 100644 --- a/src/libspark/spend_transaction.h +++ b/src/libspark/spend_transaction.h @@ -60,7 +60,7 @@ class SpendTransaction { static bool verify(const Params* params, const std::vector& transactions, const std::unordered_map>& cover_sets); static bool verify(const SpendTransaction& transaction, const std::unordered_map>& cover_sets); - static std::vector hash_bind_inner( + std::vector hash_bind_inner( const std::unordered_map>& cover_set_representations, const std::vector& C1, const std::vector& grootle_proofs, From 0716c956f696f6710780dff031e84047a86c32c0 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 14 May 2023 06:08:01 +0400 Subject: [PATCH 114/197] GetChange for spark spend --- src/wallet/wallet.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a605c90e30..6336e47fe6 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1852,6 +1852,20 @@ CAmount CWallet::GetChange(const uint256& tx, const CTxOut &txout) const { if (!MoneyRange(txout.nValue)) throw std::runtime_error(std::string(__func__) + ": value out of range"); + if (txout.scriptPubKey.IsSparkSMint()) { + if (IsChange(tx, txout)) { + std::vector serial_context = spark::getSerialContext(*GetWalletTx(tx)->tx); + spark::Coin coin(spark::Params::get_default()); + try { + spark::ParseSparkMintCoin(txout.scriptPubKey, coin); + coin.setSerialContext(serial_context); + } catch (...) { + return 0; + } + return sparkWallet->getMyCoinV(coin); + } else + return 0; + } return (IsChange(tx, txout) ? txout.nValue : 0); } From 0b5299981a128af685a8439e25db527c2060aeba Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 15 May 2023 06:46:51 +0400 Subject: [PATCH 115/197] Rpc tests fixed --- qa/rpc-tests/spark_mint.py | 8 ++--- qa/rpc-tests/spark_mintspend.py | 30 +++++++++---------- .../spark_setmintstatus_validation.py | 2 +- src/primitives/transaction.cpp | 2 +- src/wallet/wallet.cpp | 6 ++-- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/qa/rpc-tests/spark_mint.py b/qa/rpc-tests/spark_mint.py index 8106d55b8f..a901251750 100755 --- a/qa/rpc-tests/spark_mint.py +++ b/qa/rpc-tests/spark_mint.py @@ -21,13 +21,13 @@ def run_test(self): # 10 confirmations address = self.nodes[0].getnewsparkaddress()[0] - self.nodes[0].mintspark({address: [amounts[0], "Test memo"]}) - self.nodes[0].mintspark({address: [amounts[1], "Test memo"]}) + self.nodes[0].mintspark({address: {"amount": amounts[0], "memo":"Test memo"}}) + self.nodes[0].mintspark({address: {"amount": amounts[1], "memo": "Test memo"}}) self.nodes[0].generate(5) # 5 confirmations - self.nodes[0].mintspark({address: [amounts[2], "Test memo"]}) - self.nodes[0].mintspark({address: [amounts[3], "Test memo"]}) + self.nodes[0].mintspark({address: {"amount": amounts[2], "memo": "Test memo"}}) + self.nodes[0].mintspark({address: {"amount": amounts[3], "memo": "Test memo"}}) self.nodes[0].generate(5) # get all mints and utxos diff --git a/qa/rpc-tests/spark_mintspend.py b/qa/rpc-tests/spark_mintspend.py index 4341b884ad..f7a183c484 100755 --- a/qa/rpc-tests/spark_mintspend.py +++ b/qa/rpc-tests/spark_mintspend.py @@ -19,11 +19,11 @@ def run_test(self): start_bal = self.nodes[0].getbalance() - sparkAddress = self.nodes[0].getnewsparkaddress()[0] + sparkAddress = self.nodes[0].getsparkdefaultaddress()[0] mint_trans = list() - mint_trans.append(self.nodes[0].mintspark({sparkAddress: [1, "Test memo"]})) - mint_trans.append(self.nodes[0].mintspark({sparkAddress: [2, "Test memo"]})) + mint_trans.append(self.nodes[0].mintspark({sparkAddress: {"amount": 1, "memo": "Test memo"}})) + mint_trans.append(self.nodes[0].mintspark({sparkAddress: {"amount": 2, "memo":"Test memo"}})) # Get last added transaction and fee for it info = self.nodes[0].gettransaction(mint_trans[-1][0]) @@ -47,20 +47,20 @@ def run_test(self): 'but was {}'.format(0, 0, confrms) tr_type = info['details'][0]['category'] - assert tr_type == 'send', 'Unexpected transaction type: {}'.format(tr_type) + assert tr_type == 'mint', 'Unexpected transaction type: {}'.format(tr_type) # assert(self.wait_for_instantlock(tr, self.nodes[0])) - self.nodes[0].generate(2) + self.nodes[0].generate(1) self.sync_all() res = False firoAddress = self.nodes[0].getnewaddress() try: - res = self.nodes[0].spendspark({firoAddress: [1, False]}, {}) + res = self.nodes[0].spendspark({firoAddress: {"amount": 1, "subtractFee": False}}, {}) except JSONRPCException as ex: - assert ex.error['message'] == 'Insufficient funds' + assert ex.error['message'] == 'Spark spend creation failed.' - assert res, 'spendspark successfully called' + assert not res, 'Did not raise spend exception, but should be.' # generate last confirmation block - now all transactions should be confimed self.nodes[0].generate(1) @@ -74,16 +74,16 @@ def run_test(self): 'due to 3 blocks was generated after transaction was created,' \ 'but was {}.'.format(confrms) tr_type = info['details'][0]['category'] - assert tr_type == 'send', 'Unexpected transaction type' + assert tr_type == 'mint', 'Unexpected transaction type' spend_trans = list() spend_total = Decimal(0) self.sync_all() - spend_trans.append(self.nodes[0].spendspark({firoAddress: [1, False]}, {})) + spend_trans.append(self.nodes[0].spendspark({firoAddress: {"amount": 1, "subtractFee": False}}, {})) - info = self.nodes[0].gettransaction(spend_trans[-1][0]) + info = self.nodes[0].gettransaction(spend_trans[-1]) confrms = info['confirmations'] tr_type = info['details'][0]['category'] @@ -92,10 +92,10 @@ def run_test(self): 'due to 0 blocks was generated after transaction was created,' \ 'but was {}.'.format(confrms) - assert tr_type == 'send', 'Unexpected transaction type' + assert tr_type == 'spend', 'Unexpected transaction type' before_bal = self.nodes[0].getbalance() - self.nodes[0].generate(1) + self.nodes[0].generate(2) self.sync_all() after_new = self.nodes[0].getbalance() delta = after_new - before_bal @@ -108,7 +108,7 @@ def run_test(self): 'but start was {}'.format(cur_bal, spend_total, start_bal) for tr in spend_trans: - info = self.nodes[0].gettransaction(tr[0]) + info = self.nodes[0].gettransaction(tr) confrms = info['confirmations'] tr_type = info['details'][0]['category'] @@ -116,7 +116,7 @@ def run_test(self): 'Confirmations should be 1 or more, ' \ 'due to 1 blocks was generated after transaction was created,' \ 'but was {}.'.format(confrms) - assert tr_type == 'send', 'Unexpected transaction type' + assert tr_type == 'spend', 'Unexpected transaction type' if __name__ == '__main__': diff --git a/qa/rpc-tests/spark_setmintstatus_validation.py b/qa/rpc-tests/spark_setmintstatus_validation.py index 84c61af5db..6f3ea77607 100755 --- a/qa/rpc-tests/spark_setmintstatus_validation.py +++ b/qa/rpc-tests/spark_setmintstatus_validation.py @@ -19,7 +19,7 @@ def run_test(self): sparkAddress = self.nodes[0].getnewsparkaddress()[0] txid = list() - txid.append(self.nodes[0].mintspark({sparkAddress: [1, "Test memo"]})) + txid.append(self.nodes[0].mintspark({sparkAddress: {"amount": 1, "memo":"Test memo"}})) spark_mint = self.nodes[0].listsparkmints() diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index efed5881c9..f108c03b19 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -225,7 +225,7 @@ bool CTransaction::IsSparkSpend() const bool CTransaction::IsSparkMint() const { for (const CTxOut &txout: vout) { - if (txout.scriptPubKey.IsSparkMint()) + if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) return true; } return false; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6336e47fe6..e7f161c4c0 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2498,7 +2498,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, bool fExcludeLocked) const return 0; // We cannot use cache if vout contains mints due to it will not update when it spend - if (fUseCache && fAvailableCreditCached && !tx->IsZerocoinMint() && !tx->IsSigmaMint() && !tx->IsLelantusMint() && !fExcludeLocked) + if (fUseCache && fAvailableCreditCached && !tx->IsZerocoinMint() && !tx->IsSigmaMint() && !tx->IsLelantusMint() && !tx->IsSparkMint() && !fExcludeLocked) return nAvailableCreditCached; CAmount nCredit = 0; @@ -2508,7 +2508,9 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, bool fExcludeLocked) const if (!pwallet->IsSpent(hashTx, i)) { const CTxOut &txout = tx->vout[i]; - bool isPrivate = txout.scriptPubKey.IsZerocoinMint() || txout.scriptPubKey.IsSigmaMint() || txout.scriptPubKey.IsLelantusMint() || txout.scriptPubKey.IsLelantusJMint(); + bool isPrivate = txout.scriptPubKey.IsZerocoinMint() || txout.scriptPubKey.IsSigmaMint() + || txout.scriptPubKey.IsLelantusMint() || txout.scriptPubKey.IsLelantusJMint() + || txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint(); bool condition = isPrivate; if (fExcludeLocked) condition = (isPrivate || pwallet->IsLockedCoin(hashTx, i)); From 9937c98ffa4f9b6a94ff85b7ed9848e22ad7c5a5 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Tue, 16 May 2023 03:24:45 +0400 Subject: [PATCH 116/197] Failing unitests fixed --- src/primitives/transaction.cpp | 2 +- src/wallet/wallet.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index f108c03b19..efed5881c9 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -225,7 +225,7 @@ bool CTransaction::IsSparkSpend() const bool CTransaction::IsSparkMint() const { for (const CTxOut &txout: vout) { - if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) + if (txout.scriptPubKey.IsSparkMint()) return true; } return false; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e7f161c4c0..222adbedc3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2498,7 +2498,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, bool fExcludeLocked) const return 0; // We cannot use cache if vout contains mints due to it will not update when it spend - if (fUseCache && fAvailableCreditCached && !tx->IsZerocoinMint() && !tx->IsSigmaMint() && !tx->IsLelantusMint() && !tx->IsSparkMint() && !fExcludeLocked) + if (fUseCache && fAvailableCreditCached && !tx->IsZerocoinMint() && !tx->IsSigmaMint() && !tx->IsLelantusMint() && !tx->IsSparkMint() && !tx->IsSparkSpend() && !fExcludeLocked) return nAvailableCreditCached; CAmount nCredit = 0; From 14fc1ef793e43c63055aa60a317ca7de94790063 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Tue, 16 May 2023 14:05:25 +0400 Subject: [PATCH 117/197] Rpc test failing --- qa/rpc-tests/spark_spend_gettransaction.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qa/rpc-tests/spark_spend_gettransaction.py b/qa/rpc-tests/spark_spend_gettransaction.py index bbd11f88b2..d8ec7a8924 100755 --- a/qa/rpc-tests/spark_spend_gettransaction.py +++ b/qa/rpc-tests/spark_spend_gettransaction.py @@ -29,7 +29,7 @@ def run_test(self): valid_address = self.nodes[0].getnewaddress() for _ in range(10): - self.nodes[0].mintspark({sparkAddress: [1, "Test memo"]}) + self.nodes[0].mintspark({sparkAddress: {"amount": 1, "memo": "Test memo"}}) self.nodes[0].generate(1) self.sync_all() @@ -38,8 +38,8 @@ def run_test(self): assert balance['availableBalance'] / 1e8 == 10 # case 1: Spend many with watchonly address - spendto_wo_id = self.nodes[0].spendspark({watchonly_address: [1, False]}, {}) - spendto_wo_tx = self.nodes[0].gettransaction(spendto_wo_id[0]) + spendto_wo_id = self.nodes[0].spendspark({watchonly_address: {"amount": 1, "subtractFee": False}}, {}) + spendto_wo_tx = self.nodes[0].gettransaction(spendto_wo_id) assert int(spendto_wo_tx['amount']) == int('-1') assert spendto_wo_tx['fee'] < Decimal('0') @@ -48,8 +48,8 @@ def run_test(self): assert spendto_wo_tx['details'][0]['involvesWatchonly'] # case 2: Spend many with watchonly address and valid address - spendto_wo_and_valid_id = self.nodes[0].spendspark({watchonly_address: [1, False]}, {sparkAddress: [0.01, "Test", False]}) - spendto_wo_and_valid_tx = self.nodes[0].gettransaction(spendto_wo_and_valid_id[0]) + spendto_wo_and_valid_id = self.nodes[0].spendspark({watchonly_address: {"amount": 1, "subtractFee": False}}, {sparkAddress: {"amount": 0.01, "memo": "Test", "subtractFee": False}}) + spendto_wo_and_valid_tx = self.nodes[0].gettransaction(spendto_wo_and_valid_id) assert int(spendto_wo_and_valid_tx['amount']) == int(-1) assert spendto_wo_and_valid_tx['fee'] < Decimal('0') From 6ad10e7a01d5f311bcbc81cb1d300107e99beaa5 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 19 May 2023 22:08:50 +0400 Subject: [PATCH 118/197] Improve GetAvailableCredit performance --- src/wallet/wallet.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 222adbedc3..c2bd08c339 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2505,16 +2505,15 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache, bool fExcludeLocked) const uint256 hashTx = GetHash(); for (unsigned int i = 0; i < tx->vout.size(); i++) { + const CTxOut &txout = tx->vout[i]; + + bool isPrivate = txout.scriptPubKey.IsZerocoinMint() || txout.scriptPubKey.IsSigmaMint() || txout.scriptPubKey.IsLelantusMint() || txout.scriptPubKey.IsLelantusJMint() || txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint(); + if (isPrivate) continue; + if (fExcludeLocked && pwallet->IsLockedCoin(hashTx, i)) continue; + if (!pwallet->IsSpent(hashTx, i)) { - const CTxOut &txout = tx->vout[i]; - bool isPrivate = txout.scriptPubKey.IsZerocoinMint() || txout.scriptPubKey.IsSigmaMint() - || txout.scriptPubKey.IsLelantusMint() || txout.scriptPubKey.IsLelantusJMint() - || txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint(); - bool condition = isPrivate; - if (fExcludeLocked) - condition = (isPrivate || pwallet->IsLockedCoin(hashTx, i)); - nCredit += condition ? 0 : pwallet->GetCredit(txout, ISMINE_SPENDABLE); + nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); if (!MoneyRange(nCredit)) throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range"); } From cbed25719897d4a364236503d116fbe3ceceee51 Mon Sep 17 00:00:00 2001 From: Peter Shugalev Date: Tue, 28 Mar 2023 21:48:56 +0200 Subject: [PATCH 119/197] Fixed undefined behavior and compilation for macOS --- src/libspark/hash.cpp | 2 +- src/libspark/kdf.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libspark/hash.cpp b/src/libspark/hash.cpp index f481519c31..c37d29a1ea 100644 --- a/src/libspark/hash.cpp +++ b/src/libspark/hash.cpp @@ -153,7 +153,7 @@ GroupElement Hash::finalize_group() { // Include a serialized size in the hash function void Hash::include_size(std::size_t size) { CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); - stream << size; + stream << (uint64_t)size; EVP_DigestUpdate(this->ctx, reinterpret_cast(stream.data()), stream.size()); } diff --git a/src/libspark/kdf.cpp b/src/libspark/kdf.cpp index 9110411fa7..238b922df4 100644 --- a/src/libspark/kdf.cpp +++ b/src/libspark/kdf.cpp @@ -51,7 +51,7 @@ std::vector KDF::finalize() { // Include a serialized size in the KDF void KDF::include_size(std::size_t size) { CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); - stream << size; + stream << (uint64_t)size; EVP_DigestUpdate(this->ctx, reinterpret_cast(stream.data()), stream.size()); } From 3530d430f310ca00ff34fcd5c23da5dfd928976b Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sat, 20 May 2023 03:07:40 +0400 Subject: [PATCH 120/197] Rebase finished --- src/libspark/mint_transaction.h | 2 +- src/libspark/spend_transaction.cpp | 1 - src/wallet/rpcwallet.cpp | 7 ++++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libspark/mint_transaction.h b/src/libspark/mint_transaction.h index fc537197f9..4a41b00f42 100644 --- a/src/libspark/mint_transaction.h +++ b/src/libspark/mint_transaction.h @@ -22,7 +22,7 @@ class MintTransaction { const Params* params, const std::vector& outputs, const std::vector& serial_context, - bool generate = true + bool generate = true ); bool verify(); diff --git a/src/libspark/spend_transaction.cpp b/src/libspark/spend_transaction.cpp index dbd48ebcd4..b2d3561e79 100644 --- a/src/libspark/spend_transaction.cpp +++ b/src/libspark/spend_transaction.cpp @@ -324,7 +324,6 @@ bool SpendTransaction::verify( for (std::size_t j = 0; j < t; j++) { balance_statement += tx.out_coins[j].C.inverse(); } - balance_statement += (tx.params->get_G()*Scalar(tx.f + tx.vout)).inverse(); if(!schnorr.verify( diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 4c1831a0bb..aab51b2bca 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3189,7 +3189,7 @@ UniValue listunspentsparkmints(const JSONRPCRequest& request) { "listunspentsparkmints \n" "Returns array of unspent mints coins\n" "Results are an array of Objects, each of which has:\n" - "{txid, nHeight, scriptPubKey, amount}"); + "{txid, nHeight, memo, scriptPubKey, amount}"); } if (pwallet->IsLocked()) { @@ -3236,7 +3236,7 @@ UniValue listsparkmints(const JSONRPCRequest& request) { "listsparkmints \n" "Returns array of mint coins\n" "Results are an array of Objects, each of which has:\n" - "{txid, nHeight, nId, isUsed, lTagHash, scriptPubKey, amount}"); + "{txid, nHeight, nId, isUsed, lTagHash, memo, scriptPubKey, amount}"); } if (pwallet->IsLocked()) { @@ -3455,7 +3455,8 @@ UniValue resetsparkmints(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 0) throw std::runtime_error( "resetsparkmints" - + HelpRequiringPassphrase(pwallet)); + + HelpRequiringPassphrase(pwallet) + "\nResets all your Spark mints' status to unsued and unconfirmed. To make it valid again, you have to rescan or reindex.\n" + "WARNING: Run this only for testing and if you fully understand what it does.\n"); EnsureSparkWalletIsAvailable(); From 2596e4f4ec5f1537c40d549b7ee62ff96f9c5dc3 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 22 May 2023 06:58:56 +0400 Subject: [PATCH 121/197] Spend conflict issue fixed --- src/wallet/wallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c2bd08c339..c29a7d11e3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -803,7 +803,7 @@ void CWallet::AddToSpends(const uint256& wtxid) return; BOOST_FOREACH(const CTxIn& txin, thisTx.tx->vin) { - if (!txin.IsZerocoinSpend() && !txin.IsSigmaSpend() && !txin.IsLelantusJoinSplit()) { + if (!txin.IsZerocoinSpend() && !txin.IsSigmaSpend() && !txin.IsLelantusJoinSplit() && !thisTx.tx->IsSparkSpend()) { AddToSpends(txin.prevout, wtxid); } } @@ -3636,7 +3636,7 @@ void CWallet::AvailableCoins(std::vector &vCoins, bool fOnlyConfirmed, if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && (!IsLockedCoin((*it).first, i) || nCoinType == CoinType::ONLY_1000) && - (pcoin->tx->vout[i].nValue > 0 || fIncludeZeroValue || (pcoin->tx->vout[i].scriptPubKey.IsLelantusJMint() && GetCredit(pcoin->tx->vout[i], ISMINE_SPENDABLE) > 0)) && + (pcoin->tx->vout[i].nValue > 0 || fIncludeZeroValue || ((pcoin->tx->vout[i].scriptPubKey.IsLelantusJMint() || pcoin->tx->vout[i].scriptPubKey.IsSparkSMint()) && GetCredit(pcoin->tx->vout[i], ISMINE_SPENDABLE) > 0)) && (!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected(COutPoint((*it).first, i)))) { vCoins.push_back(COutput(pcoin, i, nDepth, ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || From bccaab1dfc16dba4c49e9e3847161105f40b2e4f Mon Sep 17 00:00:00 2001 From: Peter Shugalev Date: Mon, 22 May 2023 10:32:24 +0200 Subject: [PATCH 122/197] devnet restart --- src/chainparams.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 45d15acc82..f8ef8588a5 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -778,22 +778,22 @@ class CDevNetParams : public CChainParams { consensus.chainType = Consensus::chainDevnet; - consensus.nSubsidyHalvingFirst = 120; + consensus.nSubsidyHalvingFirst = 1; consensus.nSubsidyHalvingSecond = 100000; consensus.nSubsidyHalvingInterval = 100000; consensus.nSubsidyHalvingStopBlock = 1000000; consensus.stage2DevelopmentFundShare = 15; consensus.stage2ZnodeShare = 35; - consensus.stage2DevelopmentFundAddress = "TixHByoJ21dmx5xfMAXTVC4V7k53U7RncU"; + consensus.stage2DevelopmentFundAddress = "Tq99tes2sRbQ1yNUJPJ7BforYnKcitgwWq"; consensus.stage3StartTime = 1653382800; consensus.stage3StartBlock = 1514; consensus.stage3DevelopmentFundShare = 15; consensus.stage3CommunityFundShare = 10; consensus.stage3MasternodeShare = 50; - consensus.stage3DevelopmentFundAddress = "TepVKkmUo1N6sazuM2wWwV7aiG4m1BUShU"; - consensus.stage3CommunityFundAddress = "TZpbhfvQE61USHsxd55XdPpWBqu3SXB1EP"; + consensus.stage3DevelopmentFundAddress = "TfvbHyGTo8hexoKBBS8fz9Gq7g9VZQQpcg"; + consensus.stage3CommunityFundAddress = "TgoL9nh8vDTz7UB5WkBbknBksBdUaD9qbT"; consensus.nStartBlacklist = 0; consensus.nStartDuplicationCheck = 0; @@ -898,17 +898,17 @@ class CDevNetParams : public CChainParams { nPruneAfterHeight = 1000; std::vector extraNonce(4); - extraNonce[0] = 0x0a; + extraNonce[0] = 0x1a; extraNonce[1] = 0x00; extraNonce[2] = 0x00; extraNonce[3] = 0x00; - genesis = CreateGenesisBlock(ZC_GENESIS_BLOCK_TIME, 459834, 0x1e0ffff0, 2, 0 * COIN, extraNonce); + genesis = CreateGenesisBlock(ZC_GENESIS_BLOCK_TIME, 440914, 0x1e0ffff0, 2, 0 * COIN, extraNonce); consensus.hashGenesisBlock = genesis.GetHash(); assert(consensus.hashGenesisBlock == - uint256S("0x1fcfe26873831662874b5358c4a28611be641fbb997e62d8bf9c80f799f5caff")); + uint256S("0xc4c408cfedb0a03a259d4b3046425a0ac9582f4a33960d6a34d1555538621961")); assert(genesis.hashMerkleRoot == - uint256S("0x3a0d54ae5549a8d75cd8d0cb73c6e3577ae6be8d5706fc9411bdebbe75c97210")); + uint256S("0xb84e4b6a3743eb4f24ed7e4b88355d7d5fc0aba0cbe8f04e96556ad35c52c873")); vFixedSeeds.clear(); vSeeds.clear(); // firo test seeds @@ -951,7 +951,7 @@ class CDevNetParams : public CChainParams { consensus.nLelantusStartBlock = 1; consensus.nLelantusFixesStartBlock = 1; - consensus.nSparkStartBlock = 2000; + consensus.nSparkStartBlock = 1500; consensus.nLelantusGracefulPeriod = 6000; consensus.nMaxSigmaInputPerBlock = ZC_SIGMA_INPUT_LIMIT_PER_BLOCK; @@ -968,7 +968,7 @@ class CDevNetParams : public CChainParams { consensus.nMaxSparkOutLimitPerTx = SPARK_OUT_LIMIT_PER_TX; consensus.nZerocoinToSigmaRemintWindowSize = 0; - consensus.evoSporkKeyID = "TdxR3tfoHiQUkowcfjEGiMBfk6GXFdajUA"; + consensus.evoSporkKeyID = "Tg6CSyHKVTUhMGGNzUQMMDRk88nWW1MdHz"; consensus.nEvoSporkStartBlock = 1; consensus.nEvoSporkStopBlock = 40000; consensus.nEvoSporkStopBlockExtensionVersion = 0; From 4ba6f0adbe135d156859cdc6258cfefe27141e83 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 25 May 2023 07:09:16 +0400 Subject: [PATCH 123/197] Smarter change recognition scheme implemented --- src/spark/sparkwallet.cpp | 19 ++++++++++++++++++- src/spark/sparkwallet.h | 4 ++++ src/wallet/wallet.cpp | 12 ++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 7c4d6531f6..5f321cf6a1 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -185,6 +185,10 @@ spark::Address CSparkWallet::getDefaultAddress() { return spark::Address(viewKey, lastDiversifier); } +spark::Address CSparkWallet::getChangeAddress() { + return spark::Address(viewKey, SPARK_CHANGE_D); +} + spark::SpendKey CSparkWallet::generateSpendKey(const spark::Params* params) { if (pwalletMain->IsLocked()) { LogPrintf("Spark spend key generation FAILED, wallet is locked\n"); @@ -263,6 +267,10 @@ bool CSparkWallet::isAddressMine(const std::string& encodedAddr) { return false; } +bool CSparkWallet::isChangeAddress(const uint64_t& i) const { + return i == SPARK_CHANGE_D; +} + std::vector CSparkWallet::ListSparkMints(bool fUnusedOnly, bool fMatureOnly) const { std::vector setMints; LOCK(cs_spark_wallet); @@ -489,6 +497,15 @@ CAmount CSparkWallet::getMyCoinV(spark::Coin coin) const { return v; } +bool CSparkWallet::getMyCoinIsChange(spark::Coin coin) const { + try { + spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); + return isChangeAddress(identifiedCoinData.i); + } catch (const std::runtime_error& e) { + return false; + } +} + CAmount CSparkWallet::getMySpendAmount(const std::vector& lTags) const { CAmount result = 0; LOCK(cs_spark_wallet); @@ -1311,7 +1328,7 @@ CWalletTx CSparkWallet::CreateSparkSpendTransaction( if (!privOutputs.size() || spendInCurrentTx > 0) { spark::OutputCoinData output; - output.address = getDefaultAddress(); + output.address = getChangeAddress(); output.memo = ""; if (spendInCurrentTx > 0) output.v = spendInCurrentTx; diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 7153f0b360..61190883de 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -18,6 +18,7 @@ class CCoinControl; extern CChain chainActive; const uint32_t BIP44_SPARK_INDEX = 0x6; +const uint32_t SPARK_CHANGE_D = 0x270F; class CSparkWallet { public: @@ -27,6 +28,7 @@ class CSparkWallet { spark::Address generateNextAddress(); spark::Address generateNewAddress(); spark::Address getDefaultAddress(); + spark::Address getChangeAddress(); // assign difersifier to the value from db void resetDiversifierFromDB(CWalletDB& walletdb); // assign diversifier in to to current value @@ -42,6 +44,7 @@ class CSparkWallet { // get address for a diversifier spark::Address getAddress(const int32_t& i); bool isAddressMine(const std::string& encodedAddr); + bool isChangeAddress(const uint64_t& i) const; // list spark mint, mint metadata in memory and in db should be the same at this moment, so get from memory std::vector ListSparkMints(bool fUnusedOnly = false, bool fMatureOnly = false) const; @@ -82,6 +85,7 @@ class CSparkWallet { CAmount getMyCoinV(spark::Coin coin) const; CAmount getMySpendAmount(const std::vector& lTags) const; + bool getMyCoinIsChange(spark::Coin coin) const; void UpdateSpendState(const GroupElement& lTag, const uint256& lTagHash, const uint256& txHash, bool fUpdateMint = true); void UpdateSpendState(const GroupElement& lTag, const uint256& txHash, bool fUpdateMint = true); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c29a7d11e3..2b6110447e 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2647,6 +2647,18 @@ bool CWalletTx::IsChange(uint32_t out) const { return true; } + if (tx->IsSparkSpend()) { + std::vector serial_context = spark::getSerialContext(*tx); + spark::Coin coin(spark::Params::get_default()); + try { + spark::ParseSparkMintCoin(tx->vout[out].scriptPubKey, coin); + coin.setSerialContext(serial_context); + } catch (...) { + return false; + } + return pwallet->sparkWallet->getMyCoinIsChange(coin); + } + // Legacy transaction handling. // Zerocoin spend have one special output mode to spend to yourself with change address, // we don't want to identify that output as change. From 6de1a79b305deead48e226e587e913c8342eb4e7 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 26 May 2023 05:22:44 +0400 Subject: [PATCH 124/197] Typo fixed --- src/wallet/wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 2b6110447e..d683de093c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1294,7 +1294,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex AssertLockHeld(cs_wallet); if (posInBlock != -1) { - if(!(tx.IsCoinBase() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsZerocoinSpend()) || tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) { + if(!(tx.IsCoinBase() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsZerocoinSpend() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend())) { BOOST_FOREACH(const CTxIn& txin, tx.vin) { std::pair range = mapTxSpends.equal_range(txin.prevout); while (range.first != range.second) { From b959b415708bbef1aa8c2e12a5b2ba000087129f Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 26 May 2023 05:38:31 +0400 Subject: [PATCH 125/197] LelantusToSpark fixed --- src/wallet/wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d683de093c..6a8175532d 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5748,7 +5748,7 @@ bool CWallet::LelantusToSpark(std::string& strFailReason) { COutPoint outPoint(result.GetHash(), i); coinControl.Select(outPoint); std::vector> wtxAndFee; - MintAndStoreSpark({}, wtxAndFee, true, false, &coinControl); + MintAndStoreSpark({}, wtxAndFee, true, true, false, &coinControl); } return true; From 4b6bd298fc915f7fa905c75a780a7a0ade8dd8c9 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sat, 27 May 2023 04:16:15 +0400 Subject: [PATCH 126/197] Added anonymize all rpc for spark --- src/wallet/rpcwallet.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index aab51b2bca..8d28fe138c 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3596,6 +3596,41 @@ UniValue mintspark(const JSONRPCRequest& request) return result; } +UniValue autoMintspark(const JSONRPCRequest& request) { + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 0) + throw std::runtime_error( + "This function automatically mints all unspent transparent funds\n" + ); + + EnsureWalletIsUnlocked(pwallet); + EnsureSparkWalletIsAvailable(); + + // Ensure spark mints is already accepted by network so users will not lost their coins + // due to other nodes will treat it as garbage data. + if (!spark::IsSparkAllowed()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Spark is not activated yet"); + } + + std::vector> wtxAndFee; + std::vector outputs; + std::string strError = pwallet->MintAndStoreSpark(outputs, wtxAndFee, true, true); + + if (strError != "") + throw JSONRPCError(RPC_WALLET_ERROR, strError); + + UniValue result(UniValue::VARR); + for(const auto& wtx : wtxAndFee) { + result.push_back(wtx.first.GetHash().GetHex()); + } + + return result; +} + UniValue spendspark(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -5508,6 +5543,7 @@ static const CRPCCommand commands[] = { "wallet", "resetsparkmints", &resetsparkmints, false }, { "wallet", "setsparkmintstatus", &setsparkmintstatus, false }, { "wallet", "mintspark", &mintspark, false }, + { "wallet", "autoMintspark", &autoMintspark, false }, { "wallet", "spendspark", &spendspark, false }, { "wallet", "lelantustospark", &lelantustospark, false }, From 33d6d5ca237556a102c1184d2bdefde819d57004 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 5 Jun 2023 02:25:28 +0400 Subject: [PATCH 127/197] fix spendspark corner case crash --- src/txmempool.cpp | 8 ++++---- src/validation.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/txmempool.cpp b/src/txmempool.cpp index b52f859a0d..052215645b 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -1139,13 +1139,13 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const assert(linksiter != mapLinks.end()); const TxLinks &links = linksiter->second; innerUsage += memusage::DynamicUsage(links.children); - if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit()) + if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) innerUsage += memusage::DynamicUsage(links.parents); bool fDependsWait = false; setEntries setParentCheck; int64_t parentSizes = 0; int64_t parentSigOpCost = 0; - if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit()) { + if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { BOOST_FOREACH(const CTxIn &txin, tx.vin) { // Check that every mempool transaction's inputs refer to available coins, or other mempool tx's. indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash); @@ -1210,7 +1210,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const waitingOnDependants.push_back(&(*it)); else { CValidationState state; - bool fCheckResult = tx.IsCoinBase() || tx.IsSigmaSpend() || tx.IsLelantusJoinSplit() || + bool fCheckResult = tx.IsCoinBase() || tx.IsSigmaSpend() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend() || Consensus::CheckTxInputs(tx, state, mempoolDuplicate, nSpendHeight); assert(fCheckResult); @@ -1228,7 +1228,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const assert(stepsSinceLastRemove < waitingOnDependants.size()); } else { const CTransaction &tx = entry->GetTx(); - bool fCheckResult = tx.IsCoinBase() || tx.IsSigmaSpend() || tx.IsLelantusJoinSplit() || + bool fCheckResult = tx.IsCoinBase() || tx.IsSigmaSpend() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend() || Consensus::CheckTxInputs(entry->GetTx(), state, mempoolDuplicate, nSpendHeight); assert(fCheckResult); UpdateCoins(entry->GetTx(), mempoolDuplicate, 1000000); diff --git a/src/validation.cpp b/src/validation.cpp index 7d35fda36e..409b0532c0 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -615,7 +615,7 @@ bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fChe bool const check_di = nHeight != INT_MAX && nHeight > ::Params().GetConsensus().nStartDuplicationCheck; if (fCheckDuplicateInputs || check_di) { std::set vInOutPoints; - if (tx.IsSigmaSpend() || tx.IsLelantusJoinSplit()) { + if (tx.IsSigmaSpend() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) { std::set spendScripts; for (const auto& txin: tx.vin) { From e946fceae15d72fcf50c9dc7d4ee53ff82da53a9 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 5 Jun 2023 05:46:06 +0400 Subject: [PATCH 128/197] Fix one more minor bug in spark spend verification --- src/spark/state.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/spark/state.cpp b/src/spark/state.cpp index b25c553595..40bbcf8445 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -638,6 +638,9 @@ bool CheckSparkSpendTransaction( while (index != coinGroup.firstBlock && index->GetBlockHash() != idAndHash.second) index = index->pprev; + // take the hash from last block of anonymity set + std::vector set_hash = GetAnonymitySetHash(index, idAndHash.first); + std::vector cover_set; // Build a vector with all the public coins with given id before // the block on which the spend occurred. @@ -664,8 +667,6 @@ bool CheckSparkSpendTransaction( index = index->pprev; } - // take the hash from last block of anonymity set - std::vector set_hash = GetAnonymitySetHash(coinGroup.lastBlock, idAndHash.first); CoverSetData setData; setData.cover_set = cover_set; if (!set_hash.empty()) From 6a7c0f9d7acf4b5ab33e7cb88542f31939db0fc6 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 8 Jun 2023 02:04:35 +0400 Subject: [PATCH 129/197] Add getMintAmount function --- src/spark/sparkwallet.cpp | 11 +++++++++++ src/spark/sparkwallet.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 5f321cf6a1..b4af6b49c9 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -400,6 +400,17 @@ CSparkMintMeta CSparkWallet::getMintMeta(const secp_primitives::Scalar& nonce) { return CSparkMintMeta(); } +bool CSparkWallet::getMintAmount(spark::Coin coin, CAmount& amount) { + spark::IdentifiedCoinData identifiedCoinData; + try { + identifiedCoinData = coin.identify(this->viewKey); + } catch (...) { + return false; + } + amount = identifiedCoinData.v; + return true; +} + void CSparkWallet::UpdateSpendState(const GroupElement& lTag, const uint256& lTagHash, const uint256& txHash, bool fUpdateMint) { if (coinMeta.count(lTagHash)) { auto mintMeta = coinMeta[lTagHash]; diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 61190883de..15b4fbc1d1 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -80,6 +80,8 @@ class CSparkWallet { // get mint tag from nonce CSparkMintMeta getMintMeta(const secp_primitives::Scalar& nonce); + bool getMintAmount(spark::Coin coin, CAmount& amount); + bool isMine(spark::Coin coin) const; bool isMine(const std::vector& lTags) const; From 6338e4b5f0fe892910fe0d37d231e346c1157dd2 Mon Sep 17 00:00:00 2001 From: justanwar <42809091+justanwar@users.noreply.github.com> Date: Thu, 8 Jun 2023 20:14:52 +0800 Subject: [PATCH 130/197] Fix typo in automintspark RPC call (#1253) --- src/wallet/rpcwallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 8d28fe138c..0e4fd89093 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3596,7 +3596,7 @@ UniValue mintspark(const JSONRPCRequest& request) return result; } -UniValue autoMintspark(const JSONRPCRequest& request) { +UniValue automintspark(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; @@ -5543,7 +5543,7 @@ static const CRPCCommand commands[] = { "wallet", "resetsparkmints", &resetsparkmints, false }, { "wallet", "setsparkmintstatus", &setsparkmintstatus, false }, { "wallet", "mintspark", &mintspark, false }, - { "wallet", "autoMintspark", &autoMintspark, false }, + { "wallet", "automintspark", &automintspark, false }, { "wallet", "spendspark", &spendspark, false }, { "wallet", "lelantustospark", &lelantustospark, false }, From d8059eccc636e3e72f931049c5ee109fcee9d94a Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 12 Jun 2023 07:52:35 +0400 Subject: [PATCH 131/197] Possible fix for crash on windows --- src/libspark/util.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/libspark/util.cpp b/src/libspark/util.cpp index faf3b67870..cb3bed31fe 100644 --- a/src/libspark/util.cpp +++ b/src/libspark/util.cpp @@ -22,7 +22,10 @@ std::vector SparkUtils::diversifier_encrypt(const std::vector(i_stream.data()), i_stream.size(), ciphertext.data()); + std::vector plaintext; + plaintext.insert(plaintext.begin(), i_stream.begin(), i_stream.end()); + plaintext.resize(AES_BLOCKSIZE); + aes.Encrypt(plaintext.data(), i_stream.size(), ciphertext.data()); return ciphertext; } @@ -34,16 +37,17 @@ uint64_t SparkUtils::diversifier_decrypt(const std::vector& key, throw std::invalid_argument("Bad diversifier decryption key size"); } - // Decrypt using padded AES-256 (CBC) using a zero IV - CDataStream i_stream(SER_NETWORK, PROTOCOL_VERSION); - i_stream.resize(sizeof(uint64_t)); - std::vector iv; iv.resize(AES_BLOCKSIZE); AES256CBCDecrypt aes(key.data(), iv.data(), true); - aes.Decrypt(d.data(), d.size(), reinterpret_cast(i_stream.data())); + std::vector plaintext; + plaintext.resize(AES_BLOCKSIZE); + aes.Decrypt(d.data(), d.size(), plaintext.data()); + // Decrypt using padded AES-256 (CBC) using a zero IV + CDataStream i_stream(SER_NETWORK, PROTOCOL_VERSION); + i_stream.write((const char *)plaintext.data(), sizeof(uint64_t)); // Deserialize the diversifier uint64_t i; i_stream >> i; From 94ed1a7c1c977abf9dba76c5a0e17cd290677fcf Mon Sep 17 00:00:00 2001 From: justanwar <42809091+justanwar@users.noreply.github.com> Date: Tue, 13 Jun 2023 23:17:11 +0800 Subject: [PATCH 132/197] Update Spark addresses in RPC help with new format (#1256) --- src/wallet/rpcwallet.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0e4fd89093..e6bf9d9994 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3526,9 +3526,9 @@ UniValue mintspark(const JSONRPCRequest& request) " the number of addresses.\n" "\nExamples:\n" "\nSend two amounts to two different spark addresses:\n" - + HelpExampleCli("mintspark", "\"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\"},\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"\\\"}}\"") + + + HelpExampleCli("mintspark", "\"{\\\"sr1xtw3yd6v4ghgz873exv2r5nzfwryufxjzzz4xr48gl4jmh7fxml4568xr0nsdd7s4l5as2h50gakzjqrqpm7yrecne8ut8ylxzygj8klttsgm37tna4jk06acl2azph0dq4yxdqqgwa60\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\"},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"\\\"}}\"") + "\nSend two amounts to two different spark addresses setting memo:\n" - + HelpExampleRpc("mintspark", "\"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":{\\\"amount\\\":1},\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo2\\\"}}\"") + + HelpExampleRpc("mintspark", "\"{\\\"sr1xtw3yd6v4ghgz873exv2r5nzfwryufxjzzz4xr48gl4jmh7fxml4568xr0nsdd7s4l5as2h50gakzjqrqpm7yrecne8ut8ylxzygj8klttsgm37tna4jk06acl2azph0dq4yxdqqgwa60\\\":{\\\"amount\\\":1},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo2\\\"}}\"") ); EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); @@ -3660,9 +3660,9 @@ UniValue spendspark(const JSONRPCRequest& request) "\nSend an amount to transparent address:\n" + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\" \"{}\"") + "\nSend an amount to a transparent address and two different private addresses:\n" - + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\" \"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false},\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\"") + + + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\" \"{\\\"sr1hk87wuh660mss6vnxjf0syt4p6r6ptew97de3dvz698tl7p5p3w7h4m4hcw74mxnqhtz70r7gyydcx6pmkfmnew9q4z0c0muga3sd83h786znjx74ccsjwm284aswppqf2jd0sssendlj\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\"") + "\nSend two amounts to two different transparent addresses and two different private addresses:\n" - + HelpExampleRpc("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false},\\\"TuzUyNtTznSNnT2rPXG6Mk7hHG8Svuuoci\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": true}}\" \"{\\\"pr18qqntc8e60x0ygnv0ey7skekve73tmvhhlkaehka6qfv56zc4w2j75jldrf8wjf8dy0hu33vsww7fj34fd3k7rnwgv7jdvtmgv2g37xqmm59krmgycgkdes37jqupc62s7khafqynlxsy\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"\\\", \\\"subtractFee\\\": false},\\\"pr1t0l6vu9h9a8nr203tfcesps46agtm0aa9uzsty0tp4wqqrg42rg35yf4r839t3fenlfmsgkpwwklxg5r68tvenn5uy29wwykany3t0qrkjy2res6thzwx90nha6wpkegwrm0n8g2cjawq\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false}}\"") + + HelpExampleRpc("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false},\\\"TuzUyNtTznSNnT2rPXG6Mk7hHG8Svuuoci\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": true}}\" \"{\\\"sr1hk87wuh660mss6vnxjf0syt4p6r6ptew97de3dvz698tl7p5p3w7h4m4hcw74mxnqhtz70r7gyydcx6pmkfmnew9q4z0c0muga3sd83h786znjx74ccsjwm284aswppqf2jd0sssendlj\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"\\\", \\\"subtractFee\\\": false},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false}}\"") ); EnsureWalletIsUnlocked(pwallet); From b92b5ec9f984a3fef5c58d008feeefa0e757c823 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 18 Jun 2023 07:26:30 +0400 Subject: [PATCH 133/197] Wallet refactoring --- src/libspark/coin.cpp | 29 ++++++++++++--- src/libspark/coin.h | 1 + src/spark/primitives.h | 2 + src/spark/sparkwallet.cpp | 78 +++++++++++++++++++-------------------- src/spark/sparkwallet.h | 6 +-- src/spark/state.cpp | 50 +------------------------ src/test/spark_tests.cpp | 2 +- src/wallet/rpcwallet.cpp | 12 +++--- src/wallet/wallet.cpp | 2 +- src/wallet/wallet.h | 2 +- 10 files changed, 80 insertions(+), 104 deletions(-) diff --git a/src/libspark/coin.cpp b/src/libspark/coin.cpp index 6ced04d761..aa75bffc2a 100644 --- a/src/libspark/coin.cpp +++ b/src/libspark/coin.cpp @@ -166,16 +166,35 @@ std::size_t Coin::memoryRequired() { } bool Coin::operator==(const Coin& other) const { - return this->S == other.S; + if(this->S != other.S) + return false; + + if(this->K != other.K) + return false; + + if(this->C != other.C) + return false; + + if(this->r_.ciphertext != other.r_.ciphertext) + return false; + + if(this->r_.key_commitment != other.r_.key_commitment) + return false; + + if(this->r_.tag != other.r_.tag) + return false; + + return true; +} + +bool Coin::operator!=(const Coin& right) const { + return !operator==(right); } uint256 Coin::getHash() const { CDataStream ss(SER_GETHASH, 0); ss << "coin_hash"; - ss << S; - ss << K; - ss << C; - ss << r_; + ss << *this; return ::Hash(ss.begin(), ss.end()); } diff --git a/src/libspark/coin.h b/src/libspark/coin.h index 39989c669a..f050c4c240 100644 --- a/src/libspark/coin.h +++ b/src/libspark/coin.h @@ -86,6 +86,7 @@ class Coin { static std::size_t memoryRequired(); bool operator==(const Coin& other) const; + bool operator!=(const Coin& other) const; // type and v are not included in hash uint256 getHash() const; diff --git a/src/spark/primitives.h b/src/spark/primitives.h index 669730c672..4fe81e8ae9 100644 --- a/src/spark/primitives.h +++ b/src/spark/primitives.h @@ -18,6 +18,7 @@ struct CSparkMintMeta std::string memo; // memo std::vector serial_context; char type; + spark::Coin coin; mutable boost::optional nonceHash; uint256 GetNonceHash() const; @@ -48,6 +49,7 @@ struct CSparkMintMeta READWRITE(memo); READWRITE(serial_context); READWRITE(type); + READWRITE(coin); }; }; diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index b4af6b49c9..f0d7aba922 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -304,6 +304,9 @@ std::unordered_map CSparkWallet::getMintMap() const { spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta) const { const spark::Params* params = spark::Params::get_default(); + if (meta.coin != spark::Coin()) + return meta.coin; + spark::Address address(viewKey, meta.i); return spark::Coin(params, meta.type, meta.k, address, meta.v, meta.memo, meta.serial_context); } @@ -548,6 +551,7 @@ void CSparkWallet::UpdateMintState(const std::vector& coins, const mintMeta.k = identifiedCoinData.k; mintMeta.memo = identifiedCoinData.memo; mintMeta.serial_context = coin.serial_context; + mintMeta.coin = coin; mintMeta.type = coin.type; //! Check whether this mint has been spent and is considered 'pending' or 'confirmed' { @@ -1222,9 +1226,9 @@ CWalletTx CSparkWallet::CreateSparkSpendTransaction( assert(tx.nLockTime <= static_cast(chainActive.Height())); assert(tx.nLockTime < LOCKTIME_THRESHOLD); - std::list> coins = GetAvailableSparkCoins(coinControl); + std::list coins = GetAvailableSparkCoins(coinControl); - std::pair>> estimated = + std::pair> estimated = SelectSparkCoins(vOut + mintVOut, recipientsToSubtractFee, coins, privateRecipients.size(), recipients.size(), coinControl); std::vector recipients_ = recipients; @@ -1287,7 +1291,7 @@ CWalletTx CSparkWallet::CreateSparkSpendTransaction( CAmount spendInCurrentTx = 0; for (auto& spendCoin : estimated.second) - spendInCurrentTx += spendCoin.second.v; + spendInCurrentTx += spendCoin.v; spendInCurrentTx -= fee; uint64_t transparentOut = 0; @@ -1372,9 +1376,9 @@ CWalletTx CSparkWallet::CreateSparkSpendTransaction( std::unordered_map cover_set_data; for (auto& coin : estimated.second) { spark::CSparkState::SparkCoinGroupInfo nextCoinGroupInfo; - uint64_t groupId = coin.second.nId; + uint64_t groupId = coin.nId; if (sparkState->GetLatestCoinID() > groupId && sparkState->GetCoinGroupInfo(groupId + 1, nextCoinGroupInfo)) { - if (nextCoinGroupInfo.firstBlock->nHeight <= coin.second.nHeight) + if (nextCoinGroupInfo.firstBlock->nHeight <= coin.nHeight) groupId += 1; } @@ -1405,20 +1409,20 @@ CWalletTx CSparkWallet::CreateSparkSpendTransaction( spark::InputCoinData inputCoinData; inputCoinData.cover_set_id = groupId; std::size_t index = 0; - if (!getIndex(coin.first, cover_set_data[groupId].cover_set, index)) + if (!getIndex(coin.coin, cover_set_data[groupId].cover_set, index)) throw std::runtime_error( _("No such coin in set")); inputCoinData.index = index; - inputCoinData.v = coin.second.v; - inputCoinData.k = coin.second.k; + inputCoinData.v = coin.v; + inputCoinData.k = coin.k; spark::IdentifiedCoinData identifiedCoinData; - identifiedCoinData.i = coin.second.i; - identifiedCoinData.d = coin.second.d; - identifiedCoinData.v = coin.second.v; - identifiedCoinData.k = coin.second.k; - identifiedCoinData.memo = coin.second.memo; - spark::RecoveredCoinData recoveredCoinData = coin.first.recover(fullViewKey, identifiedCoinData); + identifiedCoinData.i = coin.i; + identifiedCoinData.d = coin.d; + identifiedCoinData.v = coin.v; + identifiedCoinData.k = coin.k; + identifiedCoinData.memo = coin.memo; + spark::RecoveredCoinData recoveredCoinData = coin.coin.recover(fullViewKey, identifiedCoinData); inputCoinData.T = recoveredCoinData.T; inputCoinData.s = recoveredCoinData.s; @@ -1495,15 +1499,15 @@ template static CAmount CalculateBalance(Iterator begin, Iterator end) { CAmount balance(0); for (auto itr = begin; itr != end; itr++) { - balance += itr->second.v; + balance += itr->v; } return balance; } bool GetCoinsToSpend( CAmount required, - std::vector>& coinsToSpend_out, - std::list> coins, + std::vector& coinsToSpend_out, + std::list coins, int64_t& changeToMint, const CCoinControl *coinControl) { @@ -1513,17 +1517,15 @@ bool GetCoinsToSpend( throw InsufficientFunds(); } - typedef std::pair CoinData; - // sort by biggest amount. if it is same amount we will prefer the older block - auto comparer = [](const CoinData& a, const CoinData& b) -> bool { - return a.second.v != b.second.v ? a.second.v > b.second.v : a.second.nHeight < b.second.nHeight; + auto comparer = [](const CSparkMintMeta& a, const CSparkMintMeta& b) -> bool { + return a.v != b.v ? a.v > b.v : a.nHeight < b.nHeight; }; coins.sort(comparer); CAmount spend_val(0); - std::list coinsToSpend; + std::list coinsToSpend; // If coinControl, want to use all inputs bool coinControlUsed = false; @@ -1531,7 +1533,7 @@ bool GetCoinsToSpend( if (coinControl->HasSelected()) { auto coinIt = coins.rbegin(); for (; coinIt != coins.rend(); coinIt++) { - spend_val += coinIt->second.v; + spend_val += coinIt->v; } coinControlUsed = true; coinsToSpend.insert(coinsToSpend.begin(), coins.begin(), coins.end()); @@ -1543,11 +1545,11 @@ bool GetCoinsToSpend( if (coins.empty()) break; - CoinData choosen; + CSparkMintMeta choosen; CAmount need = required - spend_val; auto itr = coins.begin(); - if (need >= itr->second.v) { + if (need >= itr->v) { choosen = *itr; coins.erase(itr); } else { @@ -1555,7 +1557,7 @@ bool GetCoinsToSpend( auto nextItr = coinIt; nextItr++; - if (coinIt->second.v >= need && (nextItr == coins.rend() || nextItr->second.v != coinIt->second.v)) { + if (coinIt->v >= need && (nextItr == coins.rend() || nextItr->v != coinIt->v)) { choosen = *coinIt; coins.erase(std::next(coinIt).base()); break; @@ -1563,14 +1565,14 @@ bool GetCoinsToSpend( } } - spend_val += choosen.second.v; + spend_val += choosen.v; coinsToSpend.push_back(choosen); } } // sort by group id ay ascending order. it is mandatory for creting proper joinsplit - auto idComparer = [](const CoinData& a, const CoinData& b) -> bool { - return a.second.nId < b.second.nId; + auto idComparer = [](const CSparkMintMeta& a, const CSparkMintMeta& b) -> bool { + return a.nId < b.nId; }; coinsToSpend.sort(idComparer); @@ -1580,10 +1582,10 @@ bool GetCoinsToSpend( return true; } -std::pair>> CSparkWallet::SelectSparkCoins( +std::pair> CSparkWallet::SelectSparkCoins( CAmount required, bool subtractFeeFromAmount, - std::list> coins, + std::list coins, std::size_t mintNum, std::size_t utxoNum, const CCoinControl *coinControl) { @@ -1592,7 +1594,7 @@ std::pair>> CSparkWa unsigned size; int64_t changeToMint = 0; // this value can be negative, that means we need to spend remaining part of required value with another transaction (nMaxInputPerTransaction exceeded) - std::vector> spendCoins; + std::vector spendCoins; for (fee = payTxFee.GetFeePerK();;) { CAmount currentRequired = required; @@ -1624,26 +1626,24 @@ std::pair>> CSparkWa return std::make_pair(fee, spendCoins); } -std::list> CSparkWallet::GetAvailableSparkCoins(const CCoinControl *coinControl) const { - std::list> coins; +std::list CSparkWallet::GetAvailableSparkCoins(const CCoinControl *coinControl) const { + std::list coins; // get all unsued coins from spark wallet std::vector vecMints = this->ListSparkMints(true, true); for (const auto& mint : vecMints) { if (mint.v == 0) // ignore 0 mints which where created to increase privacy continue; - - spark::Coin coin = this->getCoinFromMeta(mint); - coins.push_back(std::make_pair(coin, mint)); + coins.push_back(mint); } std::set lockedCoins = pwalletMain->setLockedCoins; // Filter out coins that have not been selected from CoinControl should that be used - coins.remove_if([lockedCoins, coinControl](const std::pair& coin) { + coins.remove_if([lockedCoins, coinControl](const CSparkMintMeta& coin) { COutPoint outPoint; // ignore if the coin is not actually on chain - if (!spark::GetOutPoint(outPoint, coin.first)) { + if (!spark::GetOutPoint(outPoint, coin.coin)) { return true; } diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 15b4fbc1d1..18204281a9 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -126,16 +126,16 @@ class CSparkWallet { CAmount &fee, const CCoinControl *coinControl = NULL); - std::pair>> SelectSparkCoins( + std::pair> SelectSparkCoins( CAmount required, bool subtractFeeFromAmount, - std::list> coins, + std::list< CSparkMintMeta> coins, std::size_t mintNum, std::size_t utxoNum, const CCoinControl *coinControl); // Returns the list of pairs of coins and metadata for that coin, - std::list> GetAvailableSparkCoins(const CCoinControl *coinControl = NULL) const; + std::list GetAvailableSparkCoins(const CCoinControl *coinControl = NULL) const; public: // to protect coinMeta diff --git a/src/spark/state.cpp b/src/spark/state.cpp index 40bbcf8445..f91adff21d 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -427,8 +427,6 @@ bool CheckSparkMintTransaction( "CheckSparkMintTransaction : mintTransaction parsing failed"); - - bool hasCoin = false; for (size_t i = 0; i < coins.size(); i++) { auto& coin = coins[i]; if (coin.v != txOuts[i].nValue) @@ -443,31 +441,11 @@ bool CheckSparkMintTransaction( // REJECT_INVALID, // "CTransaction::CheckTransaction() : Spark Mint is out of limit."); - hasCoin = sparkState.HasCoin(coin); - - if (!hasCoin && sparkTxInfo != NULL && !sparkTxInfo->fInfoIsComplete) { - for (const auto& mint : sparkTxInfo->mints) { - if (mint == coin) { - hasCoin = true; - break; - } - } - + if (sparkTxInfo != NULL && !sparkTxInfo->fInfoIsComplete) { // Update coin list in the info sparkTxInfo->mints.push_back(coin); sparkTxInfo->spTransactions.insert(hashTx); } - - if (hasCoin && fStatefulSigmaCheck) - break; - } - - if (hasCoin && fStatefulSigmaCheck) { - LogPrintf("CheckSparkMintTransaction: double mint, tx=%s\n", hashTx.GetHex()); - return state.DoS(100, - false, - PUBCOIN_NOT_VALIDATE, - "CheckSparkMintTransaction: double mint"); } return true; @@ -499,35 +477,11 @@ bool CheckSparkSMintTransaction( } } - bool hasCoin = false; for (auto& coin : out_coins) { - - hasCoin = sparkState.HasCoin(coin); - - if (!hasCoin && sparkTxInfo != NULL && !sparkTxInfo->fInfoIsComplete) { - for (const auto& mint : sparkTxInfo->mints) { - if (mint == coin) { - hasCoin = true; - break; - } - } - + if (sparkTxInfo != NULL && !sparkTxInfo->fInfoIsComplete) { // Update coin list in the info sparkTxInfo->mints.push_back(coin); - sparkTxInfo->spTransactions.insert(hashTx); } - - if (hasCoin && fStatefulSigmaCheck) - break; - - } - - if (hasCoin && fStatefulSigmaCheck) { - LogPrintf("CheckSparkMintTransaction: double mint, tx=%s\n", hashTx.GetHex()); - return state.DoS(100, - false, - PUBCOIN_NOT_VALIDATE, - "CheckSparkMintTransaction: double mint"); } return true; diff --git a/src/test/spark_tests.cpp b/src/test/spark_tests.cpp index 16ff210bf8..ae4962a327 100644 --- a/src/test/spark_tests.cpp +++ b/src/test/spark_tests.cpp @@ -538,7 +538,7 @@ BOOST_AUTO_TEST_CASE(checktransaction) BOOST_CHECK(CheckSparkTransaction( spendTx, state, spendTx.GetHash(), false, chainActive.Height(), false, true, &info)); - + info.spTransactions.clear(); BOOST_CHECK(!CheckSparkTransaction( spendTx, state, spendTx.GetHash(), false, chainActive.Height(), false, true, &info)); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e6bf9d9994..6891e7baac 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3202,23 +3202,23 @@ UniValue listunspentsparkmints(const JSONRPCRequest& request) { UniValue results(UniValue::VARR);; assert(pwallet != NULL); - std::list> coins = pwallet->sparkWallet->GetAvailableSparkCoins(); + std::list coins = pwallet->sparkWallet->GetAvailableSparkCoins(); LogPrintf("coins.size()=%s\n", coins.size()); BOOST_FOREACH(const auto& coin, coins) { UniValue entry(UniValue::VOBJ); - entry.push_back(Pair("txid", coin.second.txid.GetHex())); - entry.push_back(Pair("nHeight", coin.second.nHeight)); - entry.push_back(Pair("memo", coin.second.memo)); + entry.push_back(Pair("txid", coin.txid.GetHex())); + entry.push_back(Pair("nHeight", coin.nHeight)); + entry.push_back(Pair("memo", coin.memo)); CDataStream serialized(SER_NETWORK, PROTOCOL_VERSION); - serialized << coin.first; + serialized << coin.coin; CScript script; // opcode is inserted as 1 byte according to file script/script.h script << OP_SPARKMINT; script.insert(script.end(), serialized.begin(), serialized.end()); entry.push_back(Pair("scriptPubKey", HexStr(script.begin(), script.end()))); - entry.push_back(Pair("amount", ValueFromAmount(coin.second.v))); + entry.push_back(Pair("amount", ValueFromAmount(coin.v))); results.push_back(entry); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6a8175532d..59e2a804cc 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2941,7 +2941,7 @@ CRecipient CWallet::CreateLelantusMintRecipient( } } -std::list> CWallet::GetAvailableSparkCoins(const CCoinControl *coinControl) const { +std::list CWallet::GetAvailableSparkCoins(const CCoinControl *coinControl) const { EnsureSparkWalletAvailable(); LOCK2(cs_main, cs_wallet); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 33148312bc..58ddbe2d8b 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -957,7 +957,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::list GetAvailableLelantusCoins(const CCoinControl *coinControl = NULL, bool includeUnsafe = false, bool forEstimation = false) const; // Returns the list of pairs of coins and meta data for that coin, - std::list> GetAvailableSparkCoins(const CCoinControl *coinControl = NULL) const; + std::list GetAvailableSparkCoins(const CCoinControl *coinControl = NULL) const; std::vector EncryptMintAmount(uint64_t amount, const secp_primitives::GroupElement& pubcoin) const; From 37cb204956372bbe4aea66d5db040c6e46923f98 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 19 Jun 2023 03:17:15 +0400 Subject: [PATCH 134/197] Abandon spark transactions --- src/libspark/coin.cpp | 4 ++++ src/libspark/coin.h | 1 + src/spark/sparkwallet.cpp | 22 ++++++++++++++++++++++ src/spark/sparkwallet.h | 3 +++ src/wallet/rpcwallet.cpp | 1 + src/wallet/wallet.cpp | 16 ++++++++++++++++ 6 files changed, 47 insertions(+) diff --git a/src/libspark/coin.cpp b/src/libspark/coin.cpp index aa75bffc2a..05f271bfb8 100644 --- a/src/libspark/coin.cpp +++ b/src/libspark/coin.cpp @@ -202,4 +202,8 @@ void Coin::setSerialContext(const std::vector& serial_context_) { serial_context = serial_context_; } +void Coin::setParams(const Params* params) { + this->params = params; +} + } diff --git a/src/libspark/coin.h b/src/libspark/coin.h index f050c4c240..cdb42d336f 100644 --- a/src/libspark/coin.h +++ b/src/libspark/coin.h @@ -91,6 +91,7 @@ class Coin { // type and v are not included in hash uint256 getHash() const; + void setParams(const Params* params); void setSerialContext(const std::vector& serial_context_); protected: bool validate(const IncomingViewKey& incoming_view_key, IdentifiedCoinData& data); diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index f0d7aba922..f4e89279d5 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -57,6 +57,11 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { { LOCK(cs_spark_wallet); coinMeta = walletdb.ListSparkMints(); + for (auto& coin : coinMeta) { + coin.second.coin.setParams(params); + coin.second.coin.setSerialContext(coin.second.serial_context); + + } } } threadPool = new ParallelOpThreadPool(boost::thread::hardware_concurrency()); @@ -637,6 +642,23 @@ void CSparkWallet::RemoveSparkSpends(const std::unordered_map } } +void CSparkWallet::AbandonSparkMints(const std::vector& mints) { + RemoveSparkMints(mints); +} + +void CSparkWallet::AbandonSpends(const std::vector& spends) { + LOCK(cs_spark_wallet); + for (const auto& spend : spends) { + uint256 lTagHash = primitives::GetLTagHash(spend); + if (coinMeta.count(lTagHash)) { + auto mintMeta = coinMeta[lTagHash]; + mintMeta.isUsed = false; + CWalletDB walletdb(strWalletFile); + addOrUpdateMint(mintMeta, lTagHash, walletdb); + walletdb.EraseSparkSpendEntry(spend); + } + } +} std::vector CSparkWallet::listAddressCoins(const int32_t& i, bool fUnusedOnly) { std::vector listMints; diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 18204281a9..0ba91c7627 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -98,6 +98,9 @@ class CSparkWallet { void UpdateMintStateFromBlock(const CBlock& block); void RemoveSparkMints(const std::vector& mints); void RemoveSparkSpends(const std::unordered_map& spends); + void AbandonSparkMints(const std::vector& mints); + void AbandonSpends(const std::vector& spends); + // get the vector of mint metadata for a single address std::vector listAddressCoins(const int32_t& i, bool fUnusedOnly = false); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 6891e7baac..d2e6f67513 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3219,6 +3219,7 @@ UniValue listunspentsparkmints(const JSONRPCRequest& request) { script.insert(script.end(), serialized.begin(), serialized.end()); entry.push_back(Pair("scriptPubKey", HexStr(script.begin(), script.end()))); entry.push_back(Pair("amount", ValueFromAmount(coin.v))); + entry.push_back(Pair("coin", (coin.coin.getHash().GetHex()))); results.push_back(entry); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 59e2a804cc..4f24d4f2f6 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1457,6 +1457,17 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) walletdb.EraseLelantusSpendSerialEntry(spendEntry); } } + } else if (wtx.tx->IsSparkSpend()) { + std::vector lTags; + try { + spark::SpendTransaction spend = spark::ParseSparkSpend(*wtx.tx); + lTags = spend.getUsedLTags(); + } + catch (...) { + continue; + } + + sparkWallet->AbandonSpends(lTags); } if (wtx.tx->IsSigmaMint()) { @@ -1495,6 +1506,11 @@ bool CWallet::AbandonTransaction(const uint256& hashTx) } } } + + if (wtx.tx->IsSparkTransaction()) { + std::vector coins = spark::GetSparkMintCoins(*wtx.tx); + sparkWallet->AbandonSparkMints(coins); + } } return true; From ae69a2343f303fdb5fc6652a7e820243f565ae37 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 19 Jun 2023 03:19:37 +0400 Subject: [PATCH 135/197] Version bump --- configure.ac | 2 +- src/clientversion.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index fbbf74fdaa..d4a8fd9732 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 14) define(_CLIENT_VERSION_REVISION, 12) -define(_CLIENT_VERSION_BUILD, 3) +define(_CLIENT_VERSION_BUILD, 4) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2023) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/src/clientversion.h b/src/clientversion.h index 1564d2d236..5169291121 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -17,7 +17,7 @@ #define CLIENT_VERSION_MAJOR 0 #define CLIENT_VERSION_MINOR 14 #define CLIENT_VERSION_REVISION 12 -#define CLIENT_VERSION_BUILD 3 +#define CLIENT_VERSION_BUILD 4 //! Set to true for release, false for prerelease or test build #define CLIENT_VERSION_IS_RELEASE true From 2952760058b95b09a545d7e2d5269826983171fa Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Wed, 21 Jun 2023 15:13:33 +0400 Subject: [PATCH 136/197] Recognize incoming spark mint --- src/wallet/wallet.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4f24d4f2f6..b5ef6febe3 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1887,6 +1887,27 @@ CAmount CWallet::GetChange(const uint256& tx, const CTxOut &txout) const bool CWallet::IsMine(const CTransaction& tx) const { + if (tx.IsSparkTransaction()) { + if (!sparkWallet) + false; + std::vector serialContext = spark::getSerialContext(tx); + for (const auto& txout : tx.vout) { + if (txout.scriptPubKey.IsSparkMint() || txout.scriptPubKey.IsSparkSMint()) { + spark::Coin coin(spark::Params::get_default()); + try { + spark::ParseSparkMintCoin(txout.scriptPubKey, coin); + } catch (std::invalid_argument &) { + return ISMINE_NO; + } + + coin.setSerialContext(serialContext); + if(sparkWallet->isMine(coin)) + return true; + } + } + return false; + } + BOOST_FOREACH(const CTxOut& txout, tx.vout) if (IsMine(txout)) return true; From dfd34b7e3832a94cc7b0095b220eaf20d762b17e Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 23 Jun 2023 04:03:17 +0400 Subject: [PATCH 137/197] Batch verification fixed --- src/spark/state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spark/state.cpp b/src/spark/state.cpp index f91adff21d..bc6e987746 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -1168,7 +1168,7 @@ void CSparkState::GetCoinSet( { const auto ¶ms = ::Params().GetConsensus(); LOCK(cs_main); - int maxHeight = chainActive.Height() - (ZC_MINT_CONFIRMATIONS - 1); + maxHeight = chainActive.Height() - (ZC_MINT_CONFIRMATIONS - 1); } GetCoinSetForSpend( &chainActive, From 9dc82bed3aaf62472344ba85b9567735090c892e Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 25 Jun 2023 00:05:50 +0400 Subject: [PATCH 138/197] Recognize transparent output fixed --- src/wallet/wallet.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b5ef6febe3..11c2589559 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1897,13 +1897,14 @@ bool CWallet::IsMine(const CTransaction& tx) const try { spark::ParseSparkMintCoin(txout.scriptPubKey, coin); } catch (std::invalid_argument &) { - return ISMINE_NO; + return false; } coin.setSerialContext(serialContext); if(sparkWallet->isMine(coin)) return true; - } + } else if (IsMine(txout)) + return true; } return false; } From a350494aff55274454b4263e96516d71bbc0c501 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 25 Jun 2023 01:48:11 +0400 Subject: [PATCH 139/197] Anonymity set size changed --- src/firo_params.h | 4 ++++ src/libspark/params.cpp | 4 ++-- src/spark/state.h | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/firo_params.h b/src/firo_params.h index 18db8827c8..fbeeddea90 100644 --- a/src/firo_params.h +++ b/src/firo_params.h @@ -98,6 +98,10 @@ static const int64_t DUST_HARD_LIMIT = 1000; // 0.00001 FIRO mininput #define ZC_LELANTUS_MAX_MINT_NUM 65000 #define ZC_LELANTUS_SET_START_SIZE 16000 +// limit of coins number per id in Spark +#define ZC_SPARK_MAX_MINT_NUM 32000 +#define ZC_SPARK_SET_START_SIZE 8000 + // Version of index that introduced storing accumulators and coin serials #define ZC_ADVANCED_INDEX_VERSION 130500 // Version of wallet.db entry that introduced storing extra information for mints diff --git a/src/libspark/params.cpp b/src/libspark/params.cpp index 3f7fb10c1e..466de7be00 100644 --- a/src/libspark/params.cpp +++ b/src/libspark/params.cpp @@ -19,8 +19,8 @@ Params const* Params::get_default() { std::size_t memo_bytes = 32; std::size_t max_M_range = 16; - std::size_t n_grootle = 16; - std::size_t m_grootle = 4; + std::size_t n_grootle = 8; + std::size_t m_grootle = 5; instance.reset(new Params(memo_bytes, max_M_range, n_grootle, m_grootle)); return instance.get(); diff --git a/src/spark/state.h b/src/spark/state.h index bdb09f5c4a..24262cb4fc 100644 --- a/src/spark/state.h +++ b/src/spark/state.h @@ -132,8 +132,8 @@ class CSparkState { public: CSparkState( - size_t maxCoinInGroup = ZC_LELANTUS_MAX_MINT_NUM, - size_t startGroupSize = ZC_LELANTUS_SET_START_SIZE); + size_t maxCoinInGroup = ZC_SPARK_MAX_MINT_NUM, + size_t startGroupSize = ZC_SPARK_SET_START_SIZE); // Reset to initial values void Reset(); From c5d398a3b08a58eeb2db780b094a4ad335b3a6a2 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 3 Jul 2023 03:40:10 +0400 Subject: [PATCH 140/197] Testnet HF blocks set --- src/firo_params.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/firo_params.h b/src/firo_params.h index fbeeddea90..1350bf7cdd 100644 --- a/src/firo_params.h +++ b/src/firo_params.h @@ -181,9 +181,9 @@ static const int64_t DUST_HARD_LIMIT = 1000; // 0.00001 FIRO mininput // Spark #define SPARK_START_BLOCK 700000 -#define SPARK_TESTNET_START_BLOCK 150000 +#define SPARK_TESTNET_START_BLOCK 107000 #define LELANTUS_GRACEFUL_PERIOD 800000 -#define LELANTUS_TESTNET_GRACEFUL_PERIOD 200000 +#define LELANTUS_TESTNET_GRACEFUL_PERIOD 140000 // Versions of zerocoin mint/spend transactions #define ZEROCOIN_TX_VERSION_3 30 From 89024560ba73b05796d08b930233e4011a727be5 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 9 Jul 2023 03:56:01 +0400 Subject: [PATCH 141/197] Typo fixed --- src/miner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/miner.cpp b/src/miner.cpp index ff58dd334f..2d9f3d95d5 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -508,7 +508,7 @@ void BlockAssembler::AddToBlock(CTxMemPool::txiter iter) CAmount spendAmount = spark::GetSpendTransparentAmount(tx); const auto ¶ms = chainparams.GetConsensus(); - if (spendAmount > params.nMaxValueLelantusSpendPerTransaction) + if (spendAmount > params.nMaxValueSparkSpendPerTransaction) return; if ((nSparkSpendAmount += spendAmount) > params.nMaxValueSparkSpendPerBlock) From e33fd67bbe4e21bcae89f1076cf3fc9447355d8c Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 9 Jul 2023 21:25:05 +0400 Subject: [PATCH 142/197] Lelantus graceful period check in rpc calls --- src/lelantus.cpp | 7 +++++++ src/lelantus.h | 1 + src/wallet/rpcwallet.cpp | 15 ++++++++++----- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/lelantus.cpp b/src/lelantus.cpp index 1e21491074..9c71202e1b 100644 --- a/src/lelantus.cpp +++ b/src/lelantus.cpp @@ -92,6 +92,13 @@ bool IsLelantusAllowed(int height) return height >= ::Params().GetConsensus().nLelantusStartBlock && height < ::Params().GetConsensus().nSparkStartBlock; } +bool IsLelantusGraceFulPeriod() +{ + LOCK(cs_main); + int height = chainActive.Height(); + return height >= ::Params().GetConsensus().nLelantusStartBlock && height < ::Params().GetConsensus().nLelantusGracefulPeriod; +} + bool IsAvailableToMint(const CAmount& amount) { return amount <= ::Params().GetConsensus().nMaxValueLelantusMint; diff --git a/src/lelantus.h b/src/lelantus.h index d86aeb1c45..9d1d35ede0 100644 --- a/src/lelantus.h +++ b/src/lelantus.h @@ -43,6 +43,7 @@ class CLelantusTxInfo { bool IsLelantusAllowed(); bool IsLelantusAllowed(int height); +bool IsLelantusGraceFulPeriod(); bool IsAvailableToMint(const CAmount& amount); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index d2e6f67513..0640049d2b 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3781,6 +3781,11 @@ UniValue lelantustospark(const JSONRPCRequest& request) { "Takes all your lelantus mints, spends all to transparent layer, takes all that UTX's and mints to Spark"); } + if (!lelantus::IsLelantusGraceFulPeriod()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus spends are not allowed anymore"); + } + + EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); @@ -3893,7 +3898,7 @@ UniValue mintlelantus(const JSONRPCRequest& request) // Ensure Lelantus mints is already accepted by network so users will not lost their coins // due to other nodes will treat it as garbage data. if (!lelantus::IsLelantusAllowed()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus is not activated yet"); + throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus is not active"); } CAmount nAmount = AmountFromValue(request.params[0]); @@ -3931,7 +3936,7 @@ UniValue autoMintlelantus(const JSONRPCRequest& request) { // Ensure Lelantus mints is already accepted by network so users will not lost their coins // due to other nodes will treat it as garbage data. if (!lelantus::IsLelantusAllowed()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus is not activated yet"); + throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus is not active"); } std::vector> wtxAndFee; @@ -4103,8 +4108,8 @@ UniValue joinsplit(const JSONRPCRequest& request) { + HelpExampleCli("joinsplit", "\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"\"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") ); - if (!lelantus::IsLelantusAllowed()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus is not activated yet"); + if (!lelantus::IsLelantusGraceFulPeriod()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus spends are not allowed anymore"); } EnsureLelantusWalletIsAvailable(); @@ -5313,7 +5318,7 @@ UniValue setupchannel(const JSONRPCRequest& request) bip47::CPaymentCode theirPcode(request.params[0].get_str()); if (!lelantus::IsLelantusAllowed()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus is not active yet"); + throw JSONRPCError(RPC_WALLET_ERROR, "Lelantus is not active"); } EnsureLelantusWalletIsAvailable(); From 0855b3474d432a002f90c33ea9a1dbac387330ec Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 10 Jul 2023 07:21:57 +0400 Subject: [PATCH 143/197] spendspark rpc refactored --- src/wallet/rpcwallet.cpp | 140 ++++++++++++++++++++------------------- 1 file changed, 73 insertions(+), 67 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0640049d2b..22a4485812 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3639,7 +3639,7 @@ UniValue spendspark(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() != 2) + if (request.fHelp || request.params.size() != 1) throw std::runtime_error( "spendspark {\"address\":amount,subtractfee...} {\"address\":amount,memo,subtractfee...}\n" + HelpRequiringPassphrase(pwallet) + "\n" @@ -3659,11 +3659,11 @@ UniValue spendspark(const JSONRPCRequest& request) " the number of addresses.\n" "\nExamples:\n" "\nSend an amount to transparent address:\n" - + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\" \"{}\"") + + + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\"") + "\nSend an amount to a transparent address and two different private addresses:\n" - + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\" \"{\\\"sr1hk87wuh660mss6vnxjf0syt4p6r6ptew97de3dvz698tl7p5p3w7h4m4hcw74mxnqhtz70r7gyydcx6pmkfmnew9q4z0c0muga3sd83h786znjx74ccsjwm284aswppqf2jd0sssendlj\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\"") + + + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}, \\\"sr1hk87wuh660mss6vnxjf0syt4p6r6ptew97de3dvz698tl7p5p3w7h4m4hcw74mxnqhtz70r7gyydcx6pmkfmnew9q4z0c0muga3sd83h786znjx74ccsjwm284aswppqf2jd0sssendlj\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\"") + "\nSend two amounts to two different transparent addresses and two different private addresses:\n" - + HelpExampleRpc("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false},\\\"TuzUyNtTznSNnT2rPXG6Mk7hHG8Svuuoci\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": true}}\" \"{\\\"sr1hk87wuh660mss6vnxjf0syt4p6r6ptew97de3dvz698tl7p5p3w7h4m4hcw74mxnqhtz70r7gyydcx6pmkfmnew9q4z0c0muga3sd83h786znjx74ccsjwm284aswppqf2jd0sssendlj\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"\\\", \\\"subtractFee\\\": false},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false}}\"") + + HelpExampleRpc("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false},\\\"TuzUyNtTznSNnT2rPXG6Mk7hHG8Svuuoci\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": true}, \\\"sr1hk87wuh660mss6vnxjf0syt4p6r6ptew97de3dvz698tl7p5p3w7h4m4hcw74mxnqhtz70r7gyydcx6pmkfmnew9q4z0c0muga3sd83h786znjx74ccsjwm284aswppqf2jd0sssendlj\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"\\\", \\\"subtractFee\\\": false},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false}}\"") ); EnsureWalletIsUnlocked(pwallet); @@ -3676,86 +3676,92 @@ UniValue spendspark(const JSONRPCRequest& request) } std::vector recipients; + std::vector> privateRecipients; + UniValue sendTo = request.params[0].get_obj(); std::vector keys = sendTo.getKeys(); - std::set setAddress; - BOOST_FOREACH(const std::string& name_, keys) - { - CBitcoinAddress address(name_); - if (!address.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Firo address: ")+name_); - - if (setAddress.count(address)) - throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ")+name_); - setAddress.insert(address); - - CScript scriptPubKey = GetScriptForDestination(address.Get()); - - UniValue amountObj = sendTo[name_].get_obj(); - CAmount nAmount(0); - if (amountObj.exists("amount")) - nAmount = AmountFromValue(amountObj["amount"]); - else - throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no amount: ")+name_); - if (nAmount <= 0) - throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); - - bool fSubtractFeeFromAmount = false; - if (amountObj.exists("subtractFee")) - fSubtractFeeFromAmount = amountObj["subtractFee"].get_bool(); - else - throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no subtractFee: ")+name_); - - CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; - recipients.push_back(recipient); - } - - UniValue privSendTo = request.params[1].get_obj(); - keys = privSendTo.getKeys(); const spark::Params* params = spark::Params::get_default(); + std::set setAddress; unsigned char network = spark::GetNetworkType(); - std::vector> privateRecipients; BOOST_FOREACH(const std::string& name_, keys) { - spark::Address address(params); + spark::Address sAddress(params); unsigned char coinNetwork; + bool isSparkAddress; try { - coinNetwork = address.decode(name_); + unsigned char coinNetwork = sAddress.decode(name_); + isSparkAddress = true; + if (coinNetwork != network) + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid address, wrong network type: ")+name_); } catch (...) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Spark address: ")+name_); + isSparkAddress = false; } - if (coinNetwork != network) - throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid address, wrong network type: ")+name_); - UniValue amountAndMemo = privSendTo[name_].get_obj(); - CAmount nAmount(0); - if (amountAndMemo.exists("amount")) - nAmount = AmountFromValue(amountAndMemo["amount"]); - else + if (isSparkAddress) { + UniValue amountAndMemo = sendTo[name_].get_obj(); + CAmount nAmount(0); + if (amountAndMemo.exists("amount")) + nAmount = AmountFromValue(amountAndMemo["amount"]); + else throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no amount: ")+name_); - if (nAmount <= 0) - throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); - std::string memo = ""; - if (amountAndMemo.exists("memo")) - memo = amountAndMemo["memo"].get_str(); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); - bool subtractFee = false; - if (amountAndMemo.exists("subtractFee")) - subtractFee = amountAndMemo["subtractFee"].get_bool(); - else - throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no subtractFee: ")+name_); + std::string memo = ""; + if (amountAndMemo.exists("memo")) + memo = amountAndMemo["memo"].get_str(); - if (nAmount <= 0) - throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); - LogPrintf("rpcWallet.mintSpark() nAmount = %d \n", nAmount); + bool subtractFee = false; + if (amountAndMemo.exists("subtractFee")) + subtractFee = amountAndMemo["subtractFee"].get_bool(); + else + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no subtractFee: ")+name_); - spark::OutputCoinData data; - data.address = address; - data.memo = memo; - data.v = nAmount; - privateRecipients.push_back(std::make_pair(data, subtractFee)); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); + LogPrintf("rpcWallet.mintSpark() nAmount = %d \n", nAmount); + + spark::OutputCoinData data; + data.address = sAddress; + data.memo = memo; + data.v = nAmount; + privateRecipients.push_back(std::make_pair(data, subtractFee)); + continue; + } + + CBitcoinAddress address(name_); + if (address.IsValid()) { + if (setAddress.count(address)) + throw JSONRPCError(RPC_INVALID_PARAMETER, + std::string("Invalid parameter, duplicated address: ") + name_); + setAddress.insert(address); + + CScript scriptPubKey = GetScriptForDestination(address.Get()); + + UniValue amountObj = sendTo[name_].get_obj(); + CAmount nAmount(0); + if (amountObj.exists("amount")) + nAmount = AmountFromValue(amountObj["amount"]); + else + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no amount: ") + name_); + if (nAmount <= 0) + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); + + bool fSubtractFeeFromAmount = false; + if (amountObj.exists("subtractFee")) + fSubtractFeeFromAmount = amountObj["subtractFee"].get_bool(); + else + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameters, no subtractFee: ") + name_); + + CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; + recipients.push_back(recipient); + + continue; + } + + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Firo address: ") + name_); } CAmount fee; From ed7a36aef24d5a292eded7ddf6404c4a0d73df88 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Tue, 11 Jul 2023 02:36:19 +0400 Subject: [PATCH 144/197] Help string updated --- src/wallet/rpcwallet.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 22a4485812..51ba6ea2cb 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3644,16 +3644,10 @@ UniValue spendspark(const JSONRPCRequest& request) "spendspark {\"address\":amount,subtractfee...} {\"address\":amount,memo,subtractfee...}\n" + HelpRequiringPassphrase(pwallet) + "\n" "\nArguments:\n" - "1. \"transparent\"\n" - " {\n" - " \"address\":amount (numeric or string), subtractfee (bool) The Firo address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value\n" - " ,...\n" - " }\n" - "2. \"private\"\n" - " {\n" - " \"address\":amount (numeric or string), memo (string, not required), subtractfee (bool) The Spark address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value\n" - " ,...\n" - " }\n" + "{\n" + " \"address\":amount (numeric or string), memo (string,only for private, not required), subtractfee (bool) The Spark address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value\n" + " ,...\n" + " }\n" "\nResult:\n" "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" " the number of addresses.\n" From 07c731d25a1ed717309ddda5316499324255b85e Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Tue, 11 Jul 2023 02:42:07 +0400 Subject: [PATCH 145/197] Include spark balance into gettotalbalance rpc result --- src/wallet/rpcwallet.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 51ba6ea2cb..0d6c5187d8 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -903,7 +903,7 @@ UniValue gettotalbalance(const JSONRPCRequest& request) "gettotalbalance\n" "\nReturns total (transparent + private) balance.\n" "Transparent balance is the sum of coin amounts received as utxo.\n" - "Private balance is the sum of all confirmed sigma/lelantus mints which are created by the wallet.\n" + "Private balance is the sum of all confirmed sigma/lelantus/spark mints which are created by the wallet.\n" "\nResult:\n" "amount (numeric) The total balance in " + CURRENCY_UNIT + " for the wallet.\n" "\nExamples:\n" @@ -913,9 +913,10 @@ UniValue gettotalbalance(const JSONRPCRequest& request) ); EnsureLelantusWalletIsAvailable(); + EnsureSparkWalletIsAvailable(); LOCK2(cs_main, pwallet->cs_wallet); - return ValueFromAmount(pwallet->GetBalance() + pwallet->GetPrivateBalance().first); + return ValueFromAmount(pwallet->GetBalance() + pwallet->GetPrivateBalance().first + pwallet->sparkWallet->getAvailableBalance()); } UniValue getunconfirmedbalance(const JSONRPCRequest &request) From c170b6ab1932f0ac4602ef5ad5320a8842473224 Mon Sep 17 00:00:00 2001 From: Peter Shugalev Date: Mon, 10 Jul 2023 13:01:38 +0200 Subject: [PATCH 146/197] Fix crash for Apple's recent compiler on ARM64 --- depends/packages/gmp.mk | 5 + depends/patches/gmp/applem1.patch | 502 ++++++++++++++++++++++++++++++ 2 files changed, 507 insertions(+) create mode 100644 depends/patches/gmp/applem1.patch diff --git a/depends/packages/gmp.mk b/depends/packages/gmp.mk index 589a70bf83..b1659aa16a 100644 --- a/depends/packages/gmp.mk +++ b/depends/packages/gmp.mk @@ -3,6 +3,7 @@ $(package)_version=6.2.1 $(package)_download_path=https://gmplib.org/download/gmp $(package)_file_name=gmp-$($(package)_version).tar.bz2 $(package)_sha256_hash=eae9326beb4158c386e39a356818031bd28f3124cf915f8c5b1dc4c7a36b4d7c +$(package)_patches=applem1.patch define $(package)_set_vars $(package)_config_opts+=--enable-cxx --enable-fat --with-pic --disable-shared @@ -14,6 +15,10 @@ define $(package)_config_cmds $($(package)_autoconf) endef +define $(package)_preprocess_cmds + patch -p1 <$($(package)_patch_dir)/applem1.patch +endef + define $(package)_build_cmds $(MAKE) endef diff --git a/depends/patches/gmp/applem1.patch b/depends/patches/gmp/applem1.patch new file mode 100644 index 0000000000..37faa4d499 --- /dev/null +++ b/depends/patches/gmp/applem1.patch @@ -0,0 +1,502 @@ +--- a/mpn/arm64/aors_n.asm Sat Nov 28 23:38:32 2020 +0100 ++++ b/mpn/arm64/aors_n.asm Sun Nov 29 22:31:40 2020 +0100 +@@ -68,7 +68,7 @@ + EPILOGUE() + PROLOGUE(func_n) + CLRCY +-L(ent): lsr x18, n, #2 ++L(ent): lsr x17, n, #2 + tbz n, #0, L(bx0) + + L(bx1): ldr x7, [up] +@@ -77,7 +77,7 @@ + str x13, [rp],#8 + tbnz n, #1, L(b11) + +-L(b01): cbz x18, L(ret) ++L(b01): cbz x17, L(ret) + ldp x4, x5, [up,#8] + ldp x8, x9, [vp,#8] + sub up, up, #8 +@@ -88,7 +88,7 @@ + ldp x10, x11, [vp,#8] + add up, up, #8 + add vp, vp, #8 +- cbz x18, L(end) ++ cbz x17, L(end) + b L(top) + + L(bx0): tbnz n, #1, L(b10) +@@ -101,7 +101,7 @@ + + L(b10): ldp x6, x7, [up] + ldp x10, x11, [vp] +- cbz x18, L(end) ++ cbz x17, L(end) + + ALIGN(16) + L(top): ldp x4, x5, [up,#16] +@@ -114,8 +114,8 @@ + ADDSUBC x12, x4, x8 + ADDSUBC x13, x5, x9 + stp x12, x13, [rp],#16 +- sub x18, x18, #1 +- cbnz x18, L(top) ++ sub x17, x17, #1 ++ cbnz x17, L(top) + + L(end): ADDSUBC x12, x6, x10 + ADDSUBC x13, x7, x11 +--- a/mpn/arm64/aorsmul_1.asm Sat Nov 28 23:38:32 2020 +0100 ++++ b/mpn/arm64/aorsmul_1.asm Sun Nov 29 22:31:40 2020 +0100 +@@ -32,10 +32,15 @@ + + include(`../config.m4') + +-C cycles/limb +-C Cortex-A53 9.3-9.8 +-C Cortex-A57 7.0 +-C X-Gene 5.0 ++C addmul_1 submul_1 ++C cycles/limb cycles/limb ++C Cortex-A53 9.3-9.8 9.3-9.8 ++C Cortex-A55 9.0-9.5 9.3-9.8 ++C Cortex-A57 7 7 ++C Cortex-A72 ++C Cortex-A73 6 6 ++C X-Gene 5 5 ++C Apple M1 1.75 1.75 + + C NOTES + C * It is possible to keep the carry chain alive between the addition blocks +--- a/mpn/arm64/aorsorrlshC_n.asm Sat Nov 28 23:38:32 2020 +0100 ++++ b/mpn/arm64/aorsorrlshC_n.asm Sun Nov 29 22:31:40 2020 +0100 +@@ -65,14 +65,14 @@ + + ASM_START() + PROLOGUE(func_n) +- lsr x18, n, #2 ++ lsr x6, n, #2 + tbz n, #0, L(bx0) + + L(bx1): ldr x5, [up] + tbnz n, #1, L(b11) + + L(b01): ldr x11, [vp] +- cbz x18, L(1) ++ cbz x6, L(1) + ldp x8, x9, [vp,#8] + lsl x13, x11, #LSH + ADDSUB( x15, x13, x5) +@@ -94,7 +94,7 @@ + ADDSUB( x17, x13, x5) + str x17, [rp],#8 + sub up, up, #8 +- cbz x18, L(end) ++ cbz x6, L(end) + b L(top) + + L(bx0): tbnz n, #1, L(b10) +@@ -107,7 +107,7 @@ + L(b10): CLRRCY( x9) + ldp x10, x11, [vp] + sub up, up, #16 +- cbz x18, L(end) ++ cbz x6, L(end) + + ALIGN(16) + L(top): ldp x4, x5, [up,#16] +@@ -124,8 +124,8 @@ + ADDSUBC(x16, x12, x4) + ADDSUBC(x17, x13, x5) + stp x16, x17, [rp],#16 +- sub x18, x18, #1 +- cbnz x18, L(top) ++ sub x6, x6, #1 ++ cbnz x6, L(top) + + L(end): ldp x4, x5, [up,#16] + extr x12, x10, x9, #RSH +--- a/mpn/arm64/cnd_aors_n.asm Sat Nov 28 23:38:32 2020 +0100 ++++ b/mpn/arm64/cnd_aors_n.asm Sun Nov 29 22:31:40 2020 +0100 +@@ -65,7 +65,7 @@ + + CLRCY + +- lsr x18, n, #2 ++ lsr x17, n, #2 + tbz n, #0, L(bx0) + + L(bx1): ldr x13, [vp] +@@ -75,7 +75,7 @@ + str x9, [rp] + tbnz n, #1, L(b11) + +-L(b01): cbz x18, L(rt) ++L(b01): cbz x17, L(rt) + ldp x12, x13, [vp,#8] + ldp x10, x11, [up,#8] + sub up, up, #8 +@@ -86,7 +86,7 @@ + L(b11): ldp x12, x13, [vp,#8]! + ldp x10, x11, [up,#8]! + sub rp, rp, #8 +- cbz x18, L(end) ++ cbz x17, L(end) + b L(top) + + L(bx0): ldp x12, x13, [vp] +@@ -99,7 +99,7 @@ + b L(mid) + + L(b10): sub rp, rp, #16 +- cbz x18, L(end) ++ cbz x17, L(end) + + ALIGN(16) + L(top): bic x6, x12, cnd +@@ -116,8 +116,8 @@ + ADDSUBC x9, x11, x7 + ldp x10, x11, [up,#32]! + stp x8, x9, [rp,#32]! +- sub x18, x18, #1 +- cbnz x18, L(top) ++ sub x17, x17, #1 ++ cbnz x17, L(top) + + L(end): bic x6, x12, cnd + bic x7, x13, cnd +--- a/mpn/arm64/logops_n.asm Sat Nov 28 23:38:32 2020 +0100 ++++ b/mpn/arm64/logops_n.asm Sun Nov 29 22:31:40 2020 +0100 +@@ -78,7 +78,7 @@ + + ASM_START() + PROLOGUE(func) +- lsr x18, n, #2 ++ lsr x17, n, #2 + tbz n, #0, L(bx0) + + L(bx1): ldr x7, [up] +@@ -88,7 +88,7 @@ + str x15, [rp],#8 + tbnz n, #1, L(b11) + +-L(b01): cbz x18, L(ret) ++L(b01): cbz x17, L(ret) + ldp x4, x5, [up,#8] + ldp x8, x9, [vp,#8] + sub up, up, #8 +@@ -99,7 +99,7 @@ + ldp x10, x11, [vp,#8] + add up, up, #8 + add vp, vp, #8 +- cbz x18, L(end) ++ cbz x17, L(end) + b L(top) + + L(bx0): tbnz n, #1, L(b10) +@@ -110,7 +110,7 @@ + + L(b10): ldp x6, x7, [up] + ldp x10, x11, [vp] +- cbz x18, L(end) ++ cbz x17, L(end) + + ALIGN(16) + L(top): ldp x4, x5, [up,#16] +@@ -127,8 +127,8 @@ + POSTOP( x12) + POSTOP( x13) + stp x12, x13, [rp],#16 +- sub x18, x18, #1 +- cbnz x18, L(top) ++ sub x17, x17, #1 ++ cbnz x17, L(top) + + L(end): LOGOP( x12, x6, x10) + LOGOP( x13, x7, x11) +--- a/mpn/arm64/lshift.asm Sat Nov 28 23:38:32 2020 +0100 ++++ b/mpn/arm64/lshift.asm Sun Nov 29 22:31:40 2020 +0100 +@@ -61,7 +61,7 @@ + add rp, rp_arg, n, lsl #3 + add up, up, n, lsl #3 + sub tnc, xzr, cnt +- lsr x18, n, #2 ++ lsr x17, n, #2 + tbz n, #0, L(bx0) + + L(bx1): ldr x4, [up,#-8] +@@ -69,7 +69,7 @@ + + L(b01): NSHIFT x0, x4, tnc + PSHIFT x2, x4, cnt +- cbnz x18, L(gt1) ++ cbnz x17, L(gt1) + str x2, [rp,#-8] + ret + L(gt1): ldp x4, x5, [up,#-24] +@@ -89,7 +89,7 @@ + PSHIFT x13, x5, cnt + NSHIFT x10, x4, tnc + PSHIFT x2, x4, cnt +- cbnz x18, L(gt2) ++ cbnz x17, L(gt2) + orr x10, x10, x13 + stp x2, x10, [rp,#-16] + ret +@@ -123,11 +123,11 @@ + orr x11, x12, x2 + stp x10, x11, [rp,#-32]! + PSHIFT x2, x4, cnt +-L(lo0): sub x18, x18, #1 ++L(lo0): sub x17, x17, #1 + L(lo3): NSHIFT x10, x6, tnc + PSHIFT x13, x7, cnt + NSHIFT x12, x7, tnc +- cbnz x18, L(top) ++ cbnz x17, L(top) + + L(end): orr x10, x10, x13 + orr x11, x12, x2 +--- a/mpn/arm64/lshiftc.asm Sat Nov 28 23:38:32 2020 +0100 ++++ b/mpn/arm64/lshiftc.asm Sun Nov 29 22:31:40 2020 +0100 +@@ -61,7 +61,7 @@ + add rp, rp_arg, n, lsl #3 + add up, up, n, lsl #3 + sub tnc, xzr, cnt +- lsr x18, n, #2 ++ lsr x17, n, #2 + tbz n, #0, L(bx0) + + L(bx1): ldr x4, [up,#-8] +@@ -69,7 +69,7 @@ + + L(b01): NSHIFT x0, x4, tnc + PSHIFT x2, x4, cnt +- cbnz x18, L(gt1) ++ cbnz x17, L(gt1) + mvn x2, x2 + str x2, [rp,#-8] + ret +@@ -90,7 +90,7 @@ + PSHIFT x13, x5, cnt + NSHIFT x10, x4, tnc + PSHIFT x2, x4, cnt +- cbnz x18, L(gt2) ++ cbnz x17, L(gt2) + eon x10, x10, x13 + mvn x2, x2 + stp x2, x10, [rp,#-16] +@@ -125,11 +125,11 @@ + eon x11, x12, x2 + stp x10, x11, [rp,#-32]! + PSHIFT x2, x4, cnt +-L(lo0): sub x18, x18, #1 ++L(lo0): sub x17, x17, #1 + L(lo3): NSHIFT x10, x6, tnc + PSHIFT x13, x7, cnt + NSHIFT x12, x7, tnc +- cbnz x18, L(top) ++ cbnz x17, L(top) + + L(end): eon x10, x10, x13 + eon x11, x12, x2 +--- a/mpn/arm64/mul_1.asm Sat Nov 28 23:38:32 2020 +0100 ++++ b/mpn/arm64/mul_1.asm Sun Nov 29 22:31:40 2020 +0100 +@@ -56,7 +56,7 @@ + + PROLOGUE(mpn_mul_1) + adds x4, xzr, xzr C clear register and cy flag +-L(com): lsr x18, n, #2 ++L(com): lsr x17, n, #2 + tbnz n, #0, L(bx1) + + L(bx0): mov x11, x4 +@@ -65,7 +65,7 @@ + L(b10): ldp x4, x5, [up] + mul x8, x4, v0 + umulh x10, x4, v0 +- cbz x18, L(2) ++ cbz x17, L(2) + ldp x6, x7, [up,#16]! + mul x9, x5, v0 + b L(mid)-8 +@@ -80,7 +80,7 @@ + str x9, [rp],#8 + tbnz n, #1, L(b10) + +-L(b01): cbz x18, L(1) ++L(b01): cbz x17, L(1) + + L(b00): ldp x6, x7, [up] + mul x8, x6, v0 +@@ -90,8 +90,8 @@ + adcs x12, x8, x11 + umulh x11, x7, v0 + add rp, rp, #16 +- sub x18, x18, #1 +- cbz x18, L(end) ++ sub x17, x17, #1 ++ cbz x17, L(end) + + ALIGN(16) + L(top): mul x8, x4, v0 +@@ -110,8 +110,8 @@ + stp x12, x13, [rp],#32 + adcs x12, x8, x11 + umulh x11, x7, v0 +- sub x18, x18, #1 +- cbnz x18, L(top) ++ sub x17, x17, #1 ++ cbnz x17, L(top) + + L(end): mul x8, x4, v0 + adcs x13, x9, x10 +--- a/mpn/arm64/rsh1aors_n.asm Sat Nov 28 23:38:32 2020 +0100 ++++ b/mpn/arm64/rsh1aors_n.asm Sun Nov 29 22:31:40 2020 +0100 +@@ -59,7 +59,7 @@ + + ASM_START() + PROLOGUE(func_n) +- lsr x18, n, #2 ++ lsr x6, n, #2 + + tbz n, #0, L(bx0) + +@@ -69,7 +69,7 @@ + + L(b01): ADDSUB x13, x5, x9 + and x10, x13, #1 +- cbz x18, L(1) ++ cbz x6, L(1) + ldp x4, x5, [up],#48 + ldp x8, x9, [vp],#48 + ADDSUBC x14, x4, x8 +@@ -80,8 +80,8 @@ + ADDSUBC x12, x4, x8 + ADDSUBC x13, x5, x9 + str x17, [rp], #24 +- sub x18, x18, #1 +- cbz x18, L(end) ++ sub x6, x6, #1 ++ cbz x6, L(end) + b L(top) + + L(1): cset x14, COND +@@ -97,7 +97,7 @@ + ldp x8, x9, [vp],#32 + ADDSUBC x12, x4, x8 + ADDSUBC x13, x5, x9 +- cbz x18, L(3) ++ cbz x6, L(3) + ldp x4, x5, [up,#-16] + ldp x8, x9, [vp,#-16] + extr x17, x12, x15, #1 +@@ -117,7 +117,7 @@ + ADDSUB x12, x4, x8 + ADDSUBC x13, x5, x9 + and x10, x12, #1 +- cbz x18, L(2) ++ cbz x6, L(2) + ldp x4, x5, [up,#-16] + ldp x8, x9, [vp,#-16] + ADDSUBC x14, x4, x8 +@@ -134,8 +134,8 @@ + ADDSUBC x12, x4, x8 + ADDSUBC x13, x5, x9 + add rp, rp, #16 +- sub x18, x18, #1 +- cbz x18, L(end) ++ sub x6, x6, #1 ++ cbz x6, L(end) + + ALIGN(16) + L(top): ldp x4, x5, [up,#-16] +@@ -152,8 +152,8 @@ + ADDSUBC x12, x4, x8 + ADDSUBC x13, x5, x9 + stp x16, x17, [rp],#32 +- sub x18, x18, #1 +- cbnz x18, L(top) ++ sub x6, x6, #1 ++ cbnz x6, L(top) + + L(end): extr x16, x15, x14, #1 + extr x17, x12, x15, #1 +--- a/mpn/arm64/rshift.asm Sat Nov 28 23:38:32 2020 +0100 ++++ b/mpn/arm64/rshift.asm Sun Nov 29 22:31:40 2020 +0100 +@@ -60,7 +60,7 @@ + PROLOGUE(mpn_rshift) + mov rp, rp_arg + sub tnc, xzr, cnt +- lsr x18, n, #2 ++ lsr x17, n, #2 + tbz n, #0, L(bx0) + + L(bx1): ldr x5, [up] +@@ -68,7 +68,7 @@ + + L(b01): NSHIFT x0, x5, tnc + PSHIFT x2, x5, cnt +- cbnz x18, L(gt1) ++ cbnz x17, L(gt1) + str x2, [rp] + ret + L(gt1): ldp x4, x5, [up,#8] +@@ -89,7 +89,7 @@ + PSHIFT x13, x4, cnt + NSHIFT x10, x5, tnc + PSHIFT x2, x5, cnt +- cbnz x18, L(gt2) ++ cbnz x17, L(gt2) + orr x10, x10, x13 + stp x10, x2, [rp] + ret +@@ -121,11 +121,11 @@ + orr x11, x12, x2 + stp x11, x10, [rp,#32]! + PSHIFT x2, x5, cnt +-L(lo0): sub x18, x18, #1 ++L(lo0): sub x17, x17, #1 + L(lo3): NSHIFT x10, x7, tnc + NSHIFT x12, x6, tnc + PSHIFT x13, x6, cnt +- cbnz x18, L(top) ++ cbnz x17, L(top) + + L(end): orr x10, x10, x13 + orr x11, x12, x2 +--- a/mpn/arm64/sqr_diag_addlsh1.asm Sat Nov 28 23:38:32 2020 +0100 ++++ b/mpn/arm64/sqr_diag_addlsh1.asm Sun Nov 29 22:31:40 2020 +0100 +@@ -47,7 +47,7 @@ + ASM_START() + PROLOGUE(mpn_sqr_diag_addlsh1) + ldr x15, [up],#8 +- lsr x18, n, #1 ++ lsr x14, n, #1 + tbz n, #0, L(bx0) + + L(bx1): adds x7, xzr, xzr +@@ -62,8 +62,8 @@ + ldr x17, [up],#16 + ldp x6, x7, [tp],#32 + umulh x11, x15, x15 +- sub x18, x18, #1 +- cbz x18, L(end) ++ sub x14, x14, #1 ++ cbz x14, L(end) + + ALIGN(16) + L(top): extr x9, x6, x5, #63 +@@ -84,8 +84,8 @@ + extr x8, x5, x4, #63 + stp x12, x13, [rp],#16 + adcs x12, x8, x10 +- sub x18, x18, #1 +- cbnz x18, L(top) ++ sub x14, x14, #1 ++ cbnz x14, L(top) + + L(end): extr x9, x6, x5, #63 + mul x10, x17, x17 + From ce29de550ae7e37241d5221dc830c2d37e6b23e7 Mon Sep 17 00:00:00 2001 From: Peter Shugalev Date: Mon, 10 Jul 2023 17:32:52 +0200 Subject: [PATCH 147/197] Fix for compilation with recent Apple compiler --- src/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index d993ffbebf..69e55d3d9f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -716,8 +716,8 @@ liblelantus_a_SOURCES = \ liblelantus/params.h \ liblelantus/params.cpp -libsigma_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -Werror -libsigma_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) -Werror +libsigma_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +libsigma_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libsigma_a_SOURCES = \ sigma/r1_proof.h \ sigma/r1_proof_generator.h \ From 7f39073f63e82535ccbe08254dd566cee2d72a1f Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 17 Jul 2023 14:18:12 +0400 Subject: [PATCH 148/197] rpc tests fixed --- qa/rpc-tests/lelantus_mint.py | 2 +- qa/rpc-tests/spark_spend_gettransaction.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qa/rpc-tests/lelantus_mint.py b/qa/rpc-tests/lelantus_mint.py index eedc04a38d..929567b33b 100755 --- a/qa/rpc-tests/lelantus_mint.py +++ b/qa/rpc-tests/lelantus_mint.py @@ -13,7 +13,7 @@ def run_test(self): assert_raises_message( JSONRPCException, - "Lelantus is not activated yet", + "Lelantus is not active", self.nodes[0].mintlelantus, 1) self.nodes[0].generate(activation_block - self.nodes[0].getblockcount()) diff --git a/qa/rpc-tests/spark_spend_gettransaction.py b/qa/rpc-tests/spark_spend_gettransaction.py index d8ec7a8924..1af5301a71 100755 --- a/qa/rpc-tests/spark_spend_gettransaction.py +++ b/qa/rpc-tests/spark_spend_gettransaction.py @@ -38,7 +38,7 @@ def run_test(self): assert balance['availableBalance'] / 1e8 == 10 # case 1: Spend many with watchonly address - spendto_wo_id = self.nodes[0].spendspark({watchonly_address: {"amount": 1, "subtractFee": False}}, {}) + spendto_wo_id = self.nodes[0].spendspark({watchonly_address: {"amount": 1, "subtractFee": False}}) spendto_wo_tx = self.nodes[0].gettransaction(spendto_wo_id) assert int(spendto_wo_tx['amount']) == int('-1') @@ -48,7 +48,7 @@ def run_test(self): assert spendto_wo_tx['details'][0]['involvesWatchonly'] # case 2: Spend many with watchonly address and valid address - spendto_wo_and_valid_id = self.nodes[0].spendspark({watchonly_address: {"amount": 1, "subtractFee": False}}, {sparkAddress: {"amount": 0.01, "memo": "Test", "subtractFee": False}}) + spendto_wo_and_valid_id = self.nodes[0].spendspark({watchonly_address: {"amount": 1, "subtractFee": False}, sparkAddress: {"amount": 0.01, "memo": "Test", "subtractFee": False}}) spendto_wo_and_valid_tx = self.nodes[0].gettransaction(spendto_wo_and_valid_id) assert int(spendto_wo_and_valid_tx['amount']) == int(-1) From f72a9c59c2c20d8d685d69be6a528876520a1323 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 28 Jul 2023 06:58:52 +0400 Subject: [PATCH 149/197] getusedcoinserials fixed --- src/rpc/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 70585e5bc8..2876b145fa 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1092,7 +1092,7 @@ UniValue getusedcoinserials(const JSONRPCRequest& request) UniValue serializedSerials(UniValue::VARR); int i = 0; for ( auto it = serials.begin(); it != serials.end(); ++it, ++i) { - if (i < startNumber) + if ((serials.size() - i - 1) < startNumber) continue; std::vector serialized; serialized.resize(32); From f463bda3d6e8586d21376970ca5167d7175dfba2 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Fri, 28 Jul 2023 07:22:26 +0400 Subject: [PATCH 150/197] getusedcoinstags fixed --- src/rpc/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 2876b145fa..6c0c3cac9d 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -1307,7 +1307,7 @@ UniValue getusedcoinstags(const JSONRPCRequest& request) UniValue serializedTags(UniValue::VARR); int i = 0; for ( auto it = tags.begin(); it != tags.end(); ++it, ++i) { - if (i < startNumber) + if ((tags.size() - i - 1) < startNumber) continue; std::vector serialized; serialized.resize(34); From da8a7c13d27137d2dbdde9b263677fa5f5beb091 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 31 Jul 2023 06:18:40 +0400 Subject: [PATCH 151/197] Add spark mempool clear --- src/txmempool.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 052215645b..cad8e00c11 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -1103,6 +1103,7 @@ void CTxMemPool::_clear() blockSinceLastRollingFeeBump = false; rollingMinimumFeeRate = 0; lelantusState.Reset(); + sparkState.Reset(); ++nTransactionsUpdated; } From 4490b381e7d9bb191125747bf398993cb2adb595 Mon Sep 17 00:00:00 2001 From: levoncrypto <95240473+levoncrypto@users.noreply.github.com> Date: Wed, 2 Aug 2023 20:44:28 +0400 Subject: [PATCH 152/197] fix RPC syntax in help (#1289) --- src/wallet/rpcwallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 0d6c5187d8..1bf120b17d 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3515,7 +3515,7 @@ UniValue mintspark(const JSONRPCRequest& request) if (request.fHelp || request.params.size() == 0 || request.params.size() > 2) throw std::runtime_error( - "mintspark {\"address\":amount,memo...}\n" + "mintspark {\"address\":{amount,memo...}}\n" + HelpRequiringPassphrase(pwallet) + "\n" "\nArguments:\n" " {\n" @@ -3642,7 +3642,7 @@ UniValue spendspark(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 1) throw std::runtime_error( - "spendspark {\"address\":amount,subtractfee...} {\"address\":amount,memo,subtractfee...}\n" + "spendspark {\"address\":{amount,subtractfee...}, \"address\":{amount,memo,subtractfee...}}\n" + HelpRequiringPassphrase(pwallet) + "\n" "\nArguments:\n" "{\n" From ddb04d87601fecb802236774cfd1e5295d432a0a Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 3 Aug 2023 02:35:20 +0400 Subject: [PATCH 153/197] Locked Wallet case covered --- src/spark/sparkwallet.cpp | 54 +++++++++++++++++++++++++++++++++++++++ src/spark/sparkwallet.h | 4 +++ src/wallet/crypter.cpp | 3 +++ src/wallet/rpcwallet.cpp | 35 +++++++++++++++++++++++++ 4 files changed, 96 insertions(+) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index f4e89279d5..740a0b2b6e 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -22,6 +22,7 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { fullViewKey = spark::FullViewKey(params); viewKey = spark::IncomingViewKey(params); + isSet = false; // try to get incoming view key from db, if it fails, that means it is first start if (!walletdb.readFullViewKey(fullViewKey)) { @@ -31,6 +32,7 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { } // Generating spark key set first time spark::SpendKey spendKey = generateSpendKey(params); + isSet = true; fullViewKey = generateFullViewKey(spendKey); viewKey = generateIncomingViewKey(fullViewKey); @@ -42,6 +44,7 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { // set 0 as last diversifier into db, we will update it later, in case coin comes, or user manually generates new address walletdb.writeDiversifier(lastDiversifier); } else { + isSet = true; viewKey = generateIncomingViewKey(fullViewKey); int32_t diversifierInDB = 0; // read diversifier from db @@ -63,6 +66,7 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { } } + } threadPool = new ParallelOpThreadPool(boost::thread::hardware_concurrency()); } @@ -79,6 +83,10 @@ void CSparkWallet::updatetDiversifierInDB(CWalletDB& walletdb) { walletdb.writeDiversifier(lastDiversifier); } +bool CSparkWallet::getIsSet() { + return isSet; +} + CAmount CSparkWallet::getFullBalance() { return getAvailableBalance() + getUnconfirmedBalance(); } @@ -169,11 +177,17 @@ CAmount CSparkWallet::getAddressUnconfirmedBalance(const spark::Address& address } spark::Address CSparkWallet::generateNextAddress() { + if (!isSet) + throw std::runtime_error("No Spark wallet."); + lastDiversifier++; return spark::Address(viewKey, lastDiversifier); } spark::Address CSparkWallet::generateNewAddress() { + if (!isSet) + throw std::runtime_error("No Spark wallet."); + lastDiversifier++; spark::Address address(viewKey, lastDiversifier); @@ -184,6 +198,9 @@ spark::Address CSparkWallet::generateNewAddress() { } spark::Address CSparkWallet::getDefaultAddress() { + if (!isSet) + throw std::runtime_error("No Spark wallet."); + if (addresses.count(0)) return addresses[0]; lastDiversifier = 0; @@ -191,6 +208,9 @@ spark::Address CSparkWallet::getDefaultAddress() { } spark::Address CSparkWallet::getChangeAddress() { + if (!isSet) + throw std::runtime_error("No Spark wallet."); + return spark::Address(viewKey, SPARK_CHANGE_D); } @@ -224,10 +244,16 @@ spark::SpendKey CSparkWallet::generateSpendKey(const spark::Params* params) { } spark::FullViewKey CSparkWallet::generateFullViewKey(const spark::SpendKey& spend_key) { + if (!isSet) + throw std::runtime_error("No Spark wallet."); + return spark::FullViewKey(spend_key); } spark::IncomingViewKey CSparkWallet::generateIncomingViewKey(const spark::FullViewKey& full_view_key) { + if (!isSet) + throw std::runtime_error("No Spark wallet."); + viewKey = spark::IncomingViewKey(full_view_key); return viewKey; } @@ -237,6 +263,9 @@ std::unordered_map CSparkWallet::getAllAddresses() { } spark::Address CSparkWallet::getAddress(const int32_t& i) { + if (!isSet) + throw std::runtime_error("No Spark wallet."); + if (lastDiversifier < i || addresses.count(i) == 0) return spark::Address(viewKey, lastDiversifier); @@ -244,6 +273,9 @@ spark::Address CSparkWallet::getAddress(const int32_t& i) { } bool CSparkWallet::isAddressMine(const std::string& encodedAddr) { + if (!isSet) + false; + const spark::Params* params = spark::Params::get_default(); spark::Address address(params); try { @@ -308,6 +340,9 @@ std::unordered_map CSparkWallet::getMintMap() const { spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta) const { + if (!isSet) + return spark::Coin(); + const spark::Params* params = spark::Params::get_default(); if (meta.coin != spark::Coin()) return meta.coin; @@ -409,6 +444,9 @@ CSparkMintMeta CSparkWallet::getMintMeta(const secp_primitives::Scalar& nonce) { } bool CSparkWallet::getMintAmount(spark::Coin coin, CAmount& amount) { + if (!isSet) + return false; + spark::IdentifiedCoinData identifiedCoinData; try { identifiedCoinData = coin.identify(this->viewKey); @@ -484,6 +522,9 @@ void CSparkWallet::UpdateSpendStateFromBlock(const CBlock& block) { } bool CSparkWallet::isMine(spark::Coin coin) const { + if (!isSet) + return false; + try { spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); } catch (...) { @@ -506,6 +547,8 @@ bool CSparkWallet::isMine(const std::vector& lTags) const { } CAmount CSparkWallet::getMyCoinV(spark::Coin coin) const { + if (!isSet) + return 0; CAmount v(0); try { spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); @@ -517,6 +560,9 @@ CAmount CSparkWallet::getMyCoinV(spark::Coin coin) const { } bool CSparkWallet::getMyCoinIsChange(spark::Coin coin) const { + if (!isSet) + return false; + try { spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); return isChangeAddress(identifiedCoinData.i); @@ -539,6 +585,9 @@ CAmount CSparkWallet::getMySpendAmount(const std::vector& lTags) c } void CSparkWallet::UpdateMintState(const std::vector& coins, const uint256& txHash, CWalletDB& walletdb) { + if (!isSet) + return; + spark::CSparkState *sparkState = spark::CSparkState::GetState(); for (auto coin : coins) { try { @@ -612,6 +661,9 @@ void CSparkWallet::UpdateMintStateFromBlock(const CBlock& block) { } void CSparkWallet::RemoveSparkMints(const std::vector& mints) { + if (!isSet) + return; + for (auto coin : mints) { try { spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); @@ -1170,6 +1222,8 @@ CWalletTx CSparkWallet::CreateSparkSpendTransaction( const std::vector>& privateRecipients, CAmount &fee, const CCoinControl *coinControl) { + if (!isSet) + throw std::runtime_error(_("No Spark wallet.")); if (recipients.empty() && privateRecipients.empty()) { throw std::runtime_error(_("Either recipients or newMints has to be nonempty.")); diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 0ba91c7627..83cc5d7997 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -34,6 +34,8 @@ class CSparkWallet { // assign diversifier in to to current value void updatetDiversifierInDB(CWalletDB& walletdb); + bool getIsSet(); + // functions for key set generation spark::SpendKey generateSpendKey(const spark::Params* params); spark::FullViewKey generateFullViewKey(const spark::SpendKey& spend_key); @@ -145,6 +147,8 @@ class CSparkWallet { mutable CCriticalSection cs_spark_wallet; private: + bool isSet; + std::string strWalletFile; // this is latest used diversifier int32_t lastDiversifier; diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 52eb4aeebb..30814a232c 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -231,6 +231,9 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, const bool& fF pwalletMain->zwallet->SetupWallet(hashSeedMaster, false); pwalletMain->zwallet->SyncWithChain(); } + + if (pwalletMain->sparkWallet && !pwalletMain->sparkWallet->getIsSet()) + pwalletMain->sparkWallet = std::make_unique(pwalletMain->strWalletFile); } NotifyStatusChanged(this); return true; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 1bf120b17d..99ebb5c8f7 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -916,6 +916,9 @@ UniValue gettotalbalance(const JSONRPCRequest& request) EnsureSparkWalletIsAvailable(); LOCK2(cs_main, pwallet->cs_wallet); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time.\n"); + return ValueFromAmount(pwallet->GetBalance() + pwallet->GetPrivateBalance().first + pwallet->sparkWallet->getAvailableBalance()); } @@ -3203,6 +3206,9 @@ UniValue listunspentsparkmints(const JSONRPCRequest& request) { UniValue results(UniValue::VARR);; assert(pwallet != NULL); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); + std::list coins = pwallet->sparkWallet->GetAvailableSparkCoins(); LogPrintf("coins.size()=%s\n", coins.size()); BOOST_FOREACH(const auto& coin, coins) @@ -3248,6 +3254,9 @@ UniValue listsparkmints(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); + UniValue results(UniValue::VARR);; assert(pwallet != NULL); @@ -3293,6 +3302,8 @@ UniValue getsparkdefaultaddress(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); spark::Address address = pwallet->sparkWallet->getDefaultAddress(); unsigned char network = spark::GetNetworkType(); @@ -3317,6 +3328,8 @@ UniValue getnewsparkaddress(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); spark::Address address = pwallet->sparkWallet->generateNewAddress(); unsigned char network = spark::GetNetworkType(); @@ -3342,6 +3355,8 @@ UniValue getallsparkaddresses(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); std::unordered_map addresses = pwallet->sparkWallet->getAllAddresses(); unsigned char network = spark::GetNetworkType(); @@ -3370,6 +3385,8 @@ UniValue listsparkspends(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); std::list spends = pwallet->sparkWallet->ListSparkSpends(); @@ -3401,6 +3418,9 @@ UniValue getsparkbalance(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); + UniValue results(UniValue::VOBJ); results.push_back(Pair("availableBalance",pwallet->sparkWallet->getAvailableBalance())); results.push_back(Pair("unconfirmedBalance",pwallet->sparkWallet->getUnconfirmedBalance())); @@ -3425,6 +3445,9 @@ UniValue getsparkaddressbalance(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); + std::string strAddress = request.params[0].get_str(); const spark::Params* params = spark::Params::get_default(); unsigned char network = spark::GetNetworkType(); @@ -3461,6 +3484,8 @@ UniValue resetsparkmints(const JSONRPCRequest& request) { "WARNING: Run this only for testing and if you fully understand what it does.\n"); EnsureSparkWalletIsAvailable(); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); std::vector listMints; CWalletDB walletdb(pwallet->strWalletFile); @@ -3487,6 +3512,8 @@ UniValue setsparkmintstatus(const JSONRPCRequest& request) { "Set mintIsUsed status to True or False"); EnsureSparkWalletIsAvailable(); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); uint256 lTagHash; lTagHash.SetHex(request.params[0].get_str()); @@ -3534,6 +3561,8 @@ UniValue mintspark(const JSONRPCRequest& request) ); EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); // Ensure spark mints is already accepted by network so users will not lost their coins // due to other nodes will treat it as garbage data. @@ -3611,6 +3640,8 @@ UniValue automintspark(const JSONRPCRequest& request) { EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); // Ensure spark mints is already accepted by network so users will not lost their coins // due to other nodes will treat it as garbage data. @@ -3663,6 +3694,8 @@ UniValue spendspark(const JSONRPCRequest& request) EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); // Ensure spark mints is already accepted by network so users will not lost their coins // due to other nodes will treat it as garbage data. @@ -3789,6 +3822,8 @@ UniValue lelantustospark(const JSONRPCRequest& request) { EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); + if(!pwallet->sparkWallet->getIsSet()) + throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); assert(pwallet != NULL); std::string strFailReason = ""; From 6b19882d28b3c80b2763b9f1ba0a18177247d355 Mon Sep 17 00:00:00 2001 From: levoncrypto <95240473+levoncrypto@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:42:08 +0400 Subject: [PATCH 154/197] french typo issue fixed (#1295) --- src/qt/locale/bitcoin_fr_FR.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt/locale/bitcoin_fr_FR.ts b/src/qt/locale/bitcoin_fr_FR.ts index 1b7e8fef3e..22f5a83c6a 100644 --- a/src/qt/locale/bitcoin_fr_FR.ts +++ b/src/qt/locale/bitcoin_fr_FR.ts @@ -1310,7 +1310,7 @@ S&end - E&voyer + E&nvoyer Copy amount From 239edee56590a24bf0e98c3809a9c8670688fc45 Mon Sep 17 00:00:00 2001 From: levoncrypto <95240473+levoncrypto@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:47:55 +0400 Subject: [PATCH 155/197] lelantus manual-anonymize page fixed (#1293) --- src/qt/optionsdialog.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 6dca9993c8..e0307a5395 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -189,6 +189,9 @@ void OptionsDialog::setMapper() /* Lelantus */ mapper->addMapping(ui->autoAnonymize, OptionsModel::AutoAnonymize); + if (!lelantus::IsLelantusAllowed()) { + ui->lelantusPage->setVisible(false); + } mapper->addMapping(ui->lelantusPage, OptionsModel::LelantusPage); /* Network */ From bf9e797459c80fbb11a7fc5346cb5e1435bf4a4c Mon Sep 17 00:00:00 2001 From: levoncrypto <95240473+levoncrypto@users.noreply.github.com> Date: Sat, 5 Aug 2023 12:49:42 +0400 Subject: [PATCH 156/197] spark available balance included in getprivatebalance (#1296) --- src/wallet/rpcwallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 99ebb5c8f7..48a691bbb5 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -887,7 +887,7 @@ UniValue getprivatebalance(const JSONRPCRequest& request) EnsureLelantusWalletIsAvailable(); LOCK2(cs_main, pwallet->cs_wallet); - return ValueFromAmount(pwallet->GetPrivateBalance().first); + return ValueFromAmount(pwallet->GetPrivateBalance().first + pwallet->sparkWallet->getAvailableBalance()); } UniValue gettotalbalance(const JSONRPCRequest& request) From d41e5d0cf34d1729f823d92f99940221a934a647 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 7 Aug 2023 05:28:42 +0400 Subject: [PATCH 157/197] Subtract fee issue fix --- src/spark/sparkwallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 740a0b2b6e..292aab3a6d 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -1414,8 +1414,8 @@ CWalletTx CSparkWallet::CreateSparkSpendTransaction( privOutputs.push_back(output); } - if (spendInCurrentTx <= 0) - throw std::invalid_argument(_("Unable to create spend transaction."));; + if (spendInCurrentTx < 0) + throw std::invalid_argument(_("Unable to create spend transaction.")); if (!privOutputs.size() || spendInCurrentTx > 0) { spark::OutputCoinData output; From 9506e7a2497b72f61b159a40b9d02e87e4015e3c Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Thu, 10 Aug 2023 07:11:10 +0400 Subject: [PATCH 158/197] Reverting commit ddb04d8 --- src/spark/sparkwallet.cpp | 53 --------------------------------------- src/spark/sparkwallet.h | 4 --- src/wallet/crypter.cpp | 3 --- src/wallet/rpcwallet.cpp | 30 ---------------------- 4 files changed, 90 deletions(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 292aab3a6d..942f9d04b4 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -22,7 +22,6 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { fullViewKey = spark::FullViewKey(params); viewKey = spark::IncomingViewKey(params); - isSet = false; // try to get incoming view key from db, if it fails, that means it is first start if (!walletdb.readFullViewKey(fullViewKey)) { @@ -32,7 +31,6 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { } // Generating spark key set first time spark::SpendKey spendKey = generateSpendKey(params); - isSet = true; fullViewKey = generateFullViewKey(spendKey); viewKey = generateIncomingViewKey(fullViewKey); @@ -44,7 +42,6 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { // set 0 as last diversifier into db, we will update it later, in case coin comes, or user manually generates new address walletdb.writeDiversifier(lastDiversifier); } else { - isSet = true; viewKey = generateIncomingViewKey(fullViewKey); int32_t diversifierInDB = 0; // read diversifier from db @@ -83,10 +80,6 @@ void CSparkWallet::updatetDiversifierInDB(CWalletDB& walletdb) { walletdb.writeDiversifier(lastDiversifier); } -bool CSparkWallet::getIsSet() { - return isSet; -} - CAmount CSparkWallet::getFullBalance() { return getAvailableBalance() + getUnconfirmedBalance(); } @@ -177,17 +170,11 @@ CAmount CSparkWallet::getAddressUnconfirmedBalance(const spark::Address& address } spark::Address CSparkWallet::generateNextAddress() { - if (!isSet) - throw std::runtime_error("No Spark wallet."); - lastDiversifier++; return spark::Address(viewKey, lastDiversifier); } spark::Address CSparkWallet::generateNewAddress() { - if (!isSet) - throw std::runtime_error("No Spark wallet."); - lastDiversifier++; spark::Address address(viewKey, lastDiversifier); @@ -198,9 +185,6 @@ spark::Address CSparkWallet::generateNewAddress() { } spark::Address CSparkWallet::getDefaultAddress() { - if (!isSet) - throw std::runtime_error("No Spark wallet."); - if (addresses.count(0)) return addresses[0]; lastDiversifier = 0; @@ -208,9 +192,6 @@ spark::Address CSparkWallet::getDefaultAddress() { } spark::Address CSparkWallet::getChangeAddress() { - if (!isSet) - throw std::runtime_error("No Spark wallet."); - return spark::Address(viewKey, SPARK_CHANGE_D); } @@ -244,16 +225,10 @@ spark::SpendKey CSparkWallet::generateSpendKey(const spark::Params* params) { } spark::FullViewKey CSparkWallet::generateFullViewKey(const spark::SpendKey& spend_key) { - if (!isSet) - throw std::runtime_error("No Spark wallet."); - return spark::FullViewKey(spend_key); } spark::IncomingViewKey CSparkWallet::generateIncomingViewKey(const spark::FullViewKey& full_view_key) { - if (!isSet) - throw std::runtime_error("No Spark wallet."); - viewKey = spark::IncomingViewKey(full_view_key); return viewKey; } @@ -263,9 +238,6 @@ std::unordered_map CSparkWallet::getAllAddresses() { } spark::Address CSparkWallet::getAddress(const int32_t& i) { - if (!isSet) - throw std::runtime_error("No Spark wallet."); - if (lastDiversifier < i || addresses.count(i) == 0) return spark::Address(viewKey, lastDiversifier); @@ -273,9 +245,6 @@ spark::Address CSparkWallet::getAddress(const int32_t& i) { } bool CSparkWallet::isAddressMine(const std::string& encodedAddr) { - if (!isSet) - false; - const spark::Params* params = spark::Params::get_default(); spark::Address address(params); try { @@ -340,9 +309,6 @@ std::unordered_map CSparkWallet::getMintMap() const { spark::Coin CSparkWallet::getCoinFromMeta(const CSparkMintMeta& meta) const { - if (!isSet) - return spark::Coin(); - const spark::Params* params = spark::Params::get_default(); if (meta.coin != spark::Coin()) return meta.coin; @@ -444,9 +410,6 @@ CSparkMintMeta CSparkWallet::getMintMeta(const secp_primitives::Scalar& nonce) { } bool CSparkWallet::getMintAmount(spark::Coin coin, CAmount& amount) { - if (!isSet) - return false; - spark::IdentifiedCoinData identifiedCoinData; try { identifiedCoinData = coin.identify(this->viewKey); @@ -522,9 +485,6 @@ void CSparkWallet::UpdateSpendStateFromBlock(const CBlock& block) { } bool CSparkWallet::isMine(spark::Coin coin) const { - if (!isSet) - return false; - try { spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); } catch (...) { @@ -547,8 +507,6 @@ bool CSparkWallet::isMine(const std::vector& lTags) const { } CAmount CSparkWallet::getMyCoinV(spark::Coin coin) const { - if (!isSet) - return 0; CAmount v(0); try { spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); @@ -560,9 +518,6 @@ CAmount CSparkWallet::getMyCoinV(spark::Coin coin) const { } bool CSparkWallet::getMyCoinIsChange(spark::Coin coin) const { - if (!isSet) - return false; - try { spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); return isChangeAddress(identifiedCoinData.i); @@ -585,9 +540,6 @@ CAmount CSparkWallet::getMySpendAmount(const std::vector& lTags) c } void CSparkWallet::UpdateMintState(const std::vector& coins, const uint256& txHash, CWalletDB& walletdb) { - if (!isSet) - return; - spark::CSparkState *sparkState = spark::CSparkState::GetState(); for (auto coin : coins) { try { @@ -661,9 +613,6 @@ void CSparkWallet::UpdateMintStateFromBlock(const CBlock& block) { } void CSparkWallet::RemoveSparkMints(const std::vector& mints) { - if (!isSet) - return; - for (auto coin : mints) { try { spark::IdentifiedCoinData identifiedCoinData = coin.identify(this->viewKey); @@ -1222,8 +1171,6 @@ CWalletTx CSparkWallet::CreateSparkSpendTransaction( const std::vector>& privateRecipients, CAmount &fee, const CCoinControl *coinControl) { - if (!isSet) - throw std::runtime_error(_("No Spark wallet.")); if (recipients.empty() && privateRecipients.empty()) { throw std::runtime_error(_("Either recipients or newMints has to be nonempty.")); diff --git a/src/spark/sparkwallet.h b/src/spark/sparkwallet.h index 83cc5d7997..0ba91c7627 100644 --- a/src/spark/sparkwallet.h +++ b/src/spark/sparkwallet.h @@ -34,8 +34,6 @@ class CSparkWallet { // assign diversifier in to to current value void updatetDiversifierInDB(CWalletDB& walletdb); - bool getIsSet(); - // functions for key set generation spark::SpendKey generateSpendKey(const spark::Params* params); spark::FullViewKey generateFullViewKey(const spark::SpendKey& spend_key); @@ -147,8 +145,6 @@ class CSparkWallet { mutable CCriticalSection cs_spark_wallet; private: - bool isSet; - std::string strWalletFile; // this is latest used diversifier int32_t lastDiversifier; diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 30814a232c..52eb4aeebb 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -231,9 +231,6 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, const bool& fF pwalletMain->zwallet->SetupWallet(hashSeedMaster, false); pwalletMain->zwallet->SyncWithChain(); } - - if (pwalletMain->sparkWallet && !pwalletMain->sparkWallet->getIsSet()) - pwalletMain->sparkWallet = std::make_unique(pwalletMain->strWalletFile); } NotifyStatusChanged(this); return true; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 48a691bbb5..80f1cdaddb 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -916,8 +916,6 @@ UniValue gettotalbalance(const JSONRPCRequest& request) EnsureSparkWalletIsAvailable(); LOCK2(cs_main, pwallet->cs_wallet); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time.\n"); return ValueFromAmount(pwallet->GetBalance() + pwallet->GetPrivateBalance().first + pwallet->sparkWallet->getAvailableBalance()); } @@ -3206,8 +3204,6 @@ UniValue listunspentsparkmints(const JSONRPCRequest& request) { UniValue results(UniValue::VARR);; assert(pwallet != NULL); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); std::list coins = pwallet->sparkWallet->GetAvailableSparkCoins(); LogPrintf("coins.size()=%s\n", coins.size()); @@ -3254,8 +3250,6 @@ UniValue listsparkmints(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); UniValue results(UniValue::VARR);; assert(pwallet != NULL); @@ -3302,8 +3296,6 @@ UniValue getsparkdefaultaddress(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); spark::Address address = pwallet->sparkWallet->getDefaultAddress(); unsigned char network = spark::GetNetworkType(); @@ -3328,8 +3320,6 @@ UniValue getnewsparkaddress(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); spark::Address address = pwallet->sparkWallet->generateNewAddress(); unsigned char network = spark::GetNetworkType(); @@ -3355,8 +3345,6 @@ UniValue getallsparkaddresses(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); std::unordered_map addresses = pwallet->sparkWallet->getAllAddresses(); unsigned char network = spark::GetNetworkType(); @@ -3385,8 +3373,6 @@ UniValue listsparkspends(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); std::list spends = pwallet->sparkWallet->ListSparkSpends(); @@ -3418,8 +3404,6 @@ UniValue getsparkbalance(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); UniValue results(UniValue::VOBJ); results.push_back(Pair("availableBalance",pwallet->sparkWallet->getAvailableBalance())); @@ -3445,8 +3429,6 @@ UniValue getsparkaddressbalance(const JSONRPCRequest& request) { EnsureSparkWalletIsAvailable(); assert(pwallet != NULL); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); std::string strAddress = request.params[0].get_str(); const spark::Params* params = spark::Params::get_default(); @@ -3484,8 +3466,6 @@ UniValue resetsparkmints(const JSONRPCRequest& request) { "WARNING: Run this only for testing and if you fully understand what it does.\n"); EnsureSparkWalletIsAvailable(); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); std::vector listMints; CWalletDB walletdb(pwallet->strWalletFile); @@ -3512,8 +3492,6 @@ UniValue setsparkmintstatus(const JSONRPCRequest& request) { "Set mintIsUsed status to True or False"); EnsureSparkWalletIsAvailable(); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); uint256 lTagHash; lTagHash.SetHex(request.params[0].get_str()); @@ -3561,8 +3539,6 @@ UniValue mintspark(const JSONRPCRequest& request) ); EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); // Ensure spark mints is already accepted by network so users will not lost their coins // due to other nodes will treat it as garbage data. @@ -3640,8 +3616,6 @@ UniValue automintspark(const JSONRPCRequest& request) { EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); // Ensure spark mints is already accepted by network so users will not lost their coins // due to other nodes will treat it as garbage data. @@ -3694,8 +3668,6 @@ UniValue spendspark(const JSONRPCRequest& request) EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); // Ensure spark mints is already accepted by network so users will not lost their coins // due to other nodes will treat it as garbage data. @@ -3822,8 +3794,6 @@ UniValue lelantustospark(const JSONRPCRequest& request) { EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); - if(!pwallet->sparkWallet->getIsSet()) - throw std::runtime_error("No Spark Wallet, most probably your wallet is locked with password, please unclock it one time\n"); assert(pwallet != NULL); std::string strFailReason = ""; From 30bcc5350379fe6d0d49c9741f1c1dd9dc1e1604 Mon Sep 17 00:00:00 2001 From: sproxet <17163658+sproxet@users.noreply.github.com> Date: Mon, 7 Aug 2023 10:18:51 +0545 Subject: [PATCH 159/197] Add a method to request wallet unlock. --- src/spark/sparkwallet.cpp | 15 +++++++++++++-- src/wallet/wallet.cpp | 23 ++++++++++++++++++++++- src/wallet/wallet.h | 5 +++++ 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 942f9d04b4..5b9372308e 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -23,12 +23,20 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { fullViewKey = spark::FullViewKey(params); viewKey = spark::IncomingViewKey(params); + bool fWalletJustUnlocked = false; + // try to get incoming view key from db, if it fails, that means it is first start if (!walletdb.readFullViewKey(fullViewKey)) { if (pwalletMain->IsLocked()) { - LogPrintf("Spark wallet creation FAILED, wallet is locked\n"); - return; + pwalletMain->RequestUnlock(); + if (pwalletMain->WaitForUnlock()) { + fWalletJustUnlocked = true; + } else { + throw std::runtime_error("Spark wallet creation FAILED, wallet could not be unlocked\n"); + return; + } } + // Generating spark key set first time spark::SpendKey spendKey = generateSpendKey(params); fullViewKey = generateFullViewKey(spendKey); @@ -66,6 +74,9 @@ CSparkWallet::CSparkWallet(const std::string& strWalletFile) { } threadPool = new ParallelOpThreadPool(boost::thread::hardware_concurrency()); + + if (fWalletJustUnlocked) + pwalletMain->Lock(); } CSparkWallet::~CSparkWallet() { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 11c2589559..0c37c9364b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -496,13 +496,34 @@ bool CWallet::Unlock(const SecureString &strWalletPassphrase, const bool& fFirst return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) continue; // try another master key - if (CCryptoKeyStore::Unlock(vMasterKey, fFirstUnlock)) + if (CCryptoKeyStore::Unlock(vMasterKey, fFirstUnlock)) { + fUnlockRequested.store(false); return true; + } } } return false; } +void CWallet::RequestUnlock() { + if (!IsLocked()) + return; + + LogPrintf("Requesting wallet unlock\n"); + fUnlockRequested.store(true); +} + +bool CWallet::WaitForUnlock() { + while (IsLocked()) { + MilliSleep(100); + + if (ShutdownRequested()) + return false; + } + + return true; +} + bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase) { bool fWasLocked = IsLocked(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 58ddbe2d8b..d5a4e7b4af 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -758,6 +758,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::unique_ptr sparkWallet; + std::atomic fUnlockRequested; + CWallet() { SetNull(); @@ -897,6 +899,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); + void RequestUnlock(); + bool WaitForUnlock(); + void GetKeyBirthTimes(std::map &mapKeyBirth) const; /** From 66390e9c59ed823ef5a51376953c8453e9532ed7 Mon Sep 17 00:00:00 2001 From: "levoncrypto1994@gmail.com" Date: Thu, 10 Aug 2023 17:04:16 +0400 Subject: [PATCH 160/197] Drop buggy TableViewLastColumnResizingFixer class. bitcoin/bitcoin@fd725c2 --- src/qt/createpcodedialog.cpp | 13 ---- src/qt/createpcodedialog.h | 2 - src/qt/elyassetsdialog.cpp | 11 +-- src/qt/elyassetsdialog.h | 3 - src/qt/guiutil.cpp | 124 ---------------------------------- src/qt/guiutil.h | 40 ----------- src/qt/receivecoinsdialog.cpp | 13 +--- src/qt/receivecoinsdialog.h | 2 - src/qt/tradehistorydialog.cpp | 9 +-- src/qt/tradehistorydialog.h | 2 - src/qt/transactionview.cpp | 23 ++----- src/qt/transactionview.h | 5 -- src/qt/txhistorydialog.cpp | 11 +-- src/qt/txhistorydialog.h | 2 - 14 files changed, 10 insertions(+), 250 deletions(-) diff --git a/src/qt/createpcodedialog.cpp b/src/qt/createpcodedialog.cpp index 06fc438864..852fe864a5 100644 --- a/src/qt/createpcodedialog.cpp +++ b/src/qt/createpcodedialog.cpp @@ -26,7 +26,6 @@ CreatePcodeDialog::CreatePcodeDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::CreatePcodeDialog), - columnResizingFixer(0), model(0), platformStyle(_platformStyle) { @@ -81,12 +80,8 @@ void CreatePcodeDialog::setModel(WalletModel *_model) tableView->setColumnWidth(static_cast(PcodeModel::ColumnIndex::Number), static_cast(ColumnWidths::Number)); tableView->setColumnWidth(static_cast(PcodeModel::ColumnIndex::Pcode), static_cast(ColumnWidths::Pcode)); tableView->setItemDelegateForColumn(int(PcodeModel::ColumnIndex::Pcode), new GUIUtil::TextElideStyledItemDelegate(tableView)); - connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &CreatePcodeDialog::pcodesView_selectionChanged); - // Last 2 columns are set by the columnResizingFixer, when the table geometry is ready. - columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, 70, 70, this, int(PcodeModel::ColumnIndex::Pcode)); - columnResizingFixer->stretchColumnWidth(int(PcodeModel::ColumnIndex::Pcode)); ui->createPcodeButton->setEnabled(false); ui->statusLabel->setText(tr("The label should not be empty.")); @@ -162,14 +157,6 @@ void CreatePcodeDialog::pcodesView_selectionChanged(QItemSelection const & selec ui->showPcodeButton->setEnabled(enable); } -// We override the virtual resizeEvent of the QWidget to adjust tables column -// sizes as the tables width is proportional to the dialogs width. -void CreatePcodeDialog::resizeEvent(QResizeEvent *event) -{ - QWidget::resizeEvent(event); - columnResizingFixer->stretchColumnWidth(int(PcodeModel::ColumnIndex::Pcode)); -} - void CreatePcodeDialog::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Return) diff --git a/src/qt/createpcodedialog.h b/src/qt/createpcodedialog.h index a4ca84cd2f..b41feec761 100644 --- a/src/qt/createpcodedialog.h +++ b/src/qt/createpcodedialog.h @@ -57,7 +57,6 @@ public Q_SLOTS: private: Ui::CreatePcodeDialog *ui; - GUIUtil::TableViewLastColumnResizingFixer *columnResizingFixer; WalletModel *model; QMenu *contextMenu; const PlatformStyle *platformStyle; @@ -65,7 +64,6 @@ public Q_SLOTS: QModelIndex selectedRow(); void copyColumnToClipboard(int column); - virtual void resizeEvent(QResizeEvent *event); private Q_SLOTS: void on_createPcodeButton_clicked(); diff --git a/src/qt/elyassetsdialog.cpp b/src/qt/elyassetsdialog.cpp index 173dfbc92b..6928c106f0 100644 --- a/src/qt/elyassetsdialog.cpp +++ b/src/qt/elyassetsdialog.cpp @@ -50,7 +50,7 @@ ElyAssetsDialog::ElyAssetsDialog(QWidget *parent) : ui->balancesTable->setHorizontalHeaderItem(1, new QTableWidgetItem("Property Name")); ui->balancesTable->setHorizontalHeaderItem(2, new QTableWidgetItem("Reserved")); ui->balancesTable->setHorizontalHeaderItem(3, new QTableWidgetItem("Available")); - borrowedColumnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(ui->balancesTable, 100, 100, this); + // note neither resizetocontents or stretch allow user to adjust - go interactive then manually set widths #if QT_VERSION < 0x050000 ui->balancesTable->horizontalHeader()->setResizeMode(0, QHeaderView::Interactive); @@ -73,7 +73,6 @@ ElyAssetsDialog::ElyAssetsDialog(QWidget *parent) : ui->balancesTable->resizeColumnToContents(0); ui->balancesTable->resizeColumnToContents(2); ui->balancesTable->resizeColumnToContents(3); - borrowedColumnResizingFixer->stretchColumnWidth(1); ui->balancesTable->verticalHeader()->setVisible(false); ui->balancesTable->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui->balancesTable->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -302,11 +301,3 @@ void ElyAssetsDialog::balancesUpdated() UpdatePropSelector(); propSelectorChanged(); // refresh the table with the currently selected property ID } - -// We override the virtual resizeEvent of the QWidget to adjust tables column -// sizes as the tables width is proportional to the dialogs width. -void ElyAssetsDialog::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); - borrowedColumnResizingFixer->stretchColumnWidth(1); -} diff --git a/src/qt/elyassetsdialog.h b/src/qt/elyassetsdialog.h index 90c71a90c6..4e5fffaed9 100644 --- a/src/qt/elyassetsdialog.h +++ b/src/qt/elyassetsdialog.h @@ -45,9 +45,6 @@ class ElyAssetsDialog : public QDialog QMenu *contextMenu; QMenu *contextMenuSummary; - GUIUtil::TableViewLastColumnResizingFixer *borrowedColumnResizingFixer; - virtual void resizeEvent(QResizeEvent *event); - public Q_SLOTS: void propSelectorChanged(); void balancesUpdated(); diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 00179c7aef..fdcbb75884 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -488,130 +488,6 @@ bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt) return QObject::eventFilter(obj, evt); } -void TableViewLastColumnResizingFixer::connectViewHeadersSignals() -{ - connect(tableView->horizontalHeader(), &QHeaderView::sectionResized, this, &TableViewLastColumnResizingFixer::on_sectionResized); - connect(tableView->horizontalHeader(), &QHeaderView::geometriesChanged, this, &TableViewLastColumnResizingFixer::on_geometriesChanged); -} - -// We need to disconnect these while handling the resize events, otherwise we can enter infinite loops. -void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals() -{ - disconnect(tableView->horizontalHeader(), &QHeaderView::sectionResized, this, &TableViewLastColumnResizingFixer::on_sectionResized); - disconnect(tableView->horizontalHeader(), &QHeaderView::geometriesChanged, this, &TableViewLastColumnResizingFixer::on_geometriesChanged); -} - -// Setup the resize mode, handles compatibility for Qt5 and below as the method signatures changed. -// Refactored here for readability. -void TableViewLastColumnResizingFixer::setViewHeaderResizeMode(int logicalIndex, QHeaderView::ResizeMode resizeMode) -{ -#if QT_VERSION < 0x050000 - tableView->horizontalHeader()->setResizeMode(logicalIndex, resizeMode); -#else - tableView->horizontalHeader()->setSectionResizeMode(logicalIndex, resizeMode); -#endif -} - -void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex, int width) -{ - tableView->setColumnWidth(nColumnIndex, width); - tableView->horizontalHeader()->resizeSection(nColumnIndex, width); -} - -int TableViewLastColumnResizingFixer::getColumnsWidth() -{ - int nColumnsWidthSum = 0; - for (int i = 0; i < columnCount; i++) - { - nColumnsWidthSum += tableView->horizontalHeader()->sectionSize(i); - } - return nColumnsWidthSum; -} - -int TableViewLastColumnResizingFixer::getAvailableWidthForColumn(int column) -{ - int nResult = lastColumnMinimumWidth; - int nTableWidth = tableView->horizontalHeader()->width(); - - if (nTableWidth > 0) - { - int nOtherColsWidth = getColumnsWidth() - tableView->horizontalHeader()->sectionSize(column); - nResult = std::max(nResult, nTableWidth - nOtherColsWidth); - } - - return nResult; -} - -// Make sure we don't make the columns wider than the table's viewport width. -void TableViewLastColumnResizingFixer::adjustTableColumnsWidth() -{ - disconnectViewHeadersSignals(); - resizeColumn(lastColumnIndex, getAvailableWidthForColumn(lastColumnIndex)); - connectViewHeadersSignals(); - - int nTableWidth = tableView->horizontalHeader()->width(); - int nColsWidth = getColumnsWidth(); - if (nColsWidth > nTableWidth) - { - resizeColumn(secondToLastColumnIndex,getAvailableWidthForColumn(secondToLastColumnIndex)); - } -} - -// Make column use all the space available, useful during window resizing. -void TableViewLastColumnResizingFixer::stretchColumnWidth(int column) -{ - disconnectViewHeadersSignals(); - resizeColumn(column, getAvailableWidthForColumn(column)); - connectViewHeadersSignals(); -} - -// When a section is resized this is a slot-proxy for ajustAmountColumnWidth(). -void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex, int oldSize, int newSize) -{ - adjustTableColumnsWidth(); - int remainingWidth = getAvailableWidthForColumn(logicalIndex); - if (newSize > remainingWidth) - { - resizeColumn(logicalIndex, remainingWidth); - } -} - -// When the table's geometry is ready, we manually perform the stretch of the "Message" column, -// as the "Stretch" resize mode does not allow for interactive resizing. -void TableViewLastColumnResizingFixer::on_geometriesChanged() -{ - if ((getColumnsWidth() - this->tableView->horizontalHeader()->width()) != 0) - { - disconnectViewHeadersSignals(); - resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex)); - connectViewHeadersSignals(); - } -} - -/** - * Initializes all internal variables and prepares the - * the resize modes of the last 2 columns of the table and - */ -TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer(QTableView* table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent) : - QObject(parent), - tableView(table), - lastColumnMinimumWidth(lastColMinimumWidth), - allColumnsMinimumWidth(allColsMinimumWidth) -{ - columnCount = tableView->horizontalHeader()->count(); - lastColumnIndex = columnCount - 1; - secondToLastColumnIndex = columnCount - 2; - tableView->horizontalHeader()->setMinimumSectionSize(allColumnsMinimumWidth); - setViewHeaderResizeMode(secondToLastColumnIndex, QHeaderView::Interactive); - setViewHeaderResizeMode(lastColumnIndex, QHeaderView::Interactive); -} - -TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer(QTableView* table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent, int resizeColumnIndex): - TableViewLastColumnResizingFixer(table, lastColMinimumWidth, allColsMinimumWidth, parent) -{ - secondToLastColumnIndex = resizeColumnIndex; -} - #ifdef WIN32 boost::filesystem::path static StartupShortcutPath() { diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index f27ca2497f..34ee530a4b 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -136,46 +136,6 @@ namespace GUIUtil int size_threshold; }; - /** - * Makes a QTableView last column feel as if it was being resized from its left border. - * Also makes sure the column widths are never larger than the table's viewport. - * In Qt, all columns are resizable from the right, but it's not intuitive resizing the last column from the right. - * Usually our second to last columns behave as if stretched, and when on strech mode, columns aren't resizable - * interactively or programmatically. - * - * This helper object takes care of this issue. - * - */ - class TableViewLastColumnResizingFixer: public QObject - { - Q_OBJECT - - public: - TableViewLastColumnResizingFixer(QTableView* table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent); - TableViewLastColumnResizingFixer(QTableView* table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent, int resizeColumnIndex); - void stretchColumnWidth(int column); - - private: - QTableView* tableView; - int lastColumnMinimumWidth; - int allColumnsMinimumWidth; - int lastColumnIndex; - int columnCount; - int secondToLastColumnIndex; - - void adjustTableColumnsWidth(); - int getAvailableWidthForColumn(int column); - int getColumnsWidth(); - void connectViewHeadersSignals(); - void disconnectViewHeadersSignals(); - void setViewHeaderResizeMode(int logicalIndex, QHeaderView::ResizeMode resizeMode); - void resizeColumn(int nColumnIndex, int width); - - private Q_SLOTS: - void on_sectionResized(int logicalIndex, int oldSize, int newSize); - void on_geometriesChanged(); - }; - bool GetStartOnSystemStartup(); bool SetStartOnSystemStartup(bool fAutoStart); diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 2ca8ae1342..7fa3179205 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -25,7 +25,6 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) : QDialog(parent), ui(new Ui::ReceiveCoinsDialog), - columnResizingFixer(0), model(0), platformStyle(_platformStyle) { @@ -87,11 +86,11 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model) tableView->setColumnWidth(RecentRequestsTableModel::Date, DATE_COLUMN_WIDTH); tableView->setColumnWidth(RecentRequestsTableModel::Label, LABEL_COLUMN_WIDTH); tableView->setColumnWidth(RecentRequestsTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH); + tableView->horizontalHeader()->setMinimumSectionSize(23); + tableView->horizontalHeader()->setStretchLastSection(true); connect(tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ReceiveCoinsDialog::recentRequestsView_selectionChanged); - // Last 2 columns are set by the columnResizingFixer, when the table geometry is ready. - columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this); } } @@ -207,14 +206,6 @@ void ReceiveCoinsDialog::on_removeRequestButton_clicked() model->getRecentRequestsTableModel()->removeRows(firstIndex.row(), selection.length(), firstIndex.parent()); } -// We override the virtual resizeEvent of the QWidget to adjust tables column -// sizes as the tables width is proportional to the dialogs width. -void ReceiveCoinsDialog::resizeEvent(QResizeEvent *event) -{ - QWidget::resizeEvent(event); - columnResizingFixer->stretchColumnWidth(RecentRequestsTableModel::Message); -} - void ReceiveCoinsDialog::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Return) diff --git a/src/qt/receivecoinsdialog.h b/src/qt/receivecoinsdialog.h index 1d0491c0d5..68c5e0adf5 100644 --- a/src/qt/receivecoinsdialog.h +++ b/src/qt/receivecoinsdialog.h @@ -55,14 +55,12 @@ public Q_SLOTS: private: Ui::ReceiveCoinsDialog *ui; - GUIUtil::TableViewLastColumnResizingFixer *columnResizingFixer; WalletModel *model; QMenu *contextMenu; const PlatformStyle *platformStyle; QModelIndex selectedRow(); void copyColumnToClipboard(int column); - virtual void resizeEvent(QResizeEvent *event); private Q_SLOTS: void on_receiveButton_clicked(); diff --git a/src/qt/tradehistorydialog.cpp b/src/qt/tradehistorydialog.cpp index f8f3dbd9e4..b4b36da867 100755 --- a/src/qt/tradehistorydialog.cpp +++ b/src/qt/tradehistorydialog.cpp @@ -80,7 +80,6 @@ TradeHistoryDialog::TradeHistoryDialog(QWidget *parent) : ui->tradeHistoryTable->setHorizontalHeaderItem(5, new QTableWidgetItem("Trade Details")); ui->tradeHistoryTable->setHorizontalHeaderItem(6, new QTableWidgetItem("Sold")); ui->tradeHistoryTable->setHorizontalHeaderItem(7, new QTableWidgetItem("Received")); - borrowedColumnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(ui->tradeHistoryTable, 100, 100, this); #if QT_VERSION < 0x050000 ui->tradeHistoryTable->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed); ui->tradeHistoryTable->horizontalHeader()->setResizeMode(3, QHeaderView::Interactive); @@ -112,9 +111,9 @@ TradeHistoryDialog::TradeHistoryDialog(QWidget *parent) : ui->tradeHistoryTable->resizeColumnToContents(4); ui->tradeHistoryTable->resizeColumnToContents(6); ui->tradeHistoryTable->resizeColumnToContents(7); - borrowedColumnResizingFixer->stretchColumnWidth(5); ui->tradeHistoryTable->setSortingEnabled(true); ui->tradeHistoryTable->horizontalHeader()->setSortIndicator(3, Qt::DescendingOrder); + QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this); QAction *showDetailsAction = new QAction(tr("Show trade details"), this); contextMenu = new QMenu(); @@ -570,9 +569,3 @@ void TradeHistoryDialog::showDetails() PopulateSimpleDialog(strTXText, "Trade Information", "Trade Information"); } } - -void TradeHistoryDialog::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); - borrowedColumnResizingFixer->stretchColumnWidth(5); -} diff --git a/src/qt/tradehistorydialog.h b/src/qt/tradehistorydialog.h index 4fe6749f7c..9b3871c74d 100755 --- a/src/qt/tradehistorydialog.h +++ b/src/qt/tradehistorydialog.h @@ -53,8 +53,6 @@ class TradeHistoryDialog : public QDialog ~TradeHistoryDialog(); void setWalletModel(WalletModel *model); void setClientModel(ClientModel *model); - GUIUtil::TableViewLastColumnResizingFixer *borrowedColumnResizingFixer; - virtual void resizeEvent(QResizeEvent* event); private: Ui::tradeHistoryDialog *ui; diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 3d86797da2..3ecbbd58b3 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -43,7 +43,7 @@ char const * CopyRapText{"Copy RAP address/label"}; TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *parent) : QWidget(parent), model(0), transactionProxyModel(0), - transactionView(0), abandonAction(0), columnResizingFixer(0) + transactionView(0), abandonAction(0) { // Build filter row setContentsMargins(0,0,0,0); @@ -224,7 +224,6 @@ void TransactionView::setModel(WalletModel *_model) transactionProxyModel->setSortRole(Qt::EditRole); - transactionView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); transactionView->setModel(transactionProxyModel); transactionView->setAlternatingRowColors(true); transactionView->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -238,9 +237,10 @@ void TransactionView::setModel(WalletModel *_model) transactionView->setColumnWidth(TransactionTableModel::InstantSend, INSTANTSEND_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Date, DATE_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH); - transactionView->setColumnWidth(TransactionTableModel::Amount, AMOUNT_MINIMUM_COLUMN_WIDTH); - - columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(transactionView, AMOUNT_MINIMUM_COLUMN_WIDTH, MINIMUM_COLUMN_WIDTH, this); + transactionView->horizontalHeader()->setSectionResizeMode(TransactionTableModel::Amount, QHeaderView::Fixed); + transactionView->horizontalHeader()->setMinimumSectionSize(23); + transactionView->horizontalHeader()->setStretchLastSection(true); + transactionView->horizontalHeader()->setMaximumSectionSize(260); if (_model->getOptionsModel()) { @@ -678,19 +678,6 @@ void TransactionView::focusTransaction(const QModelIndex &idx) transactionView->setFocus(); } -// We override the virtual resizeEvent of the QWidget to adjust tables column -// sizes as the tables width is proportional to the dialogs width. -void TransactionView::resizeEvent(QResizeEvent* event) -{ - if(!transactionView || !transactionView->selectionModel()) - return; - - QWidget::resizeEvent(event); - disconnect(transactionView->horizontalHeader(), &QHeaderView::sectionResized, this, &TransactionView::updateHeaderSizes); - columnResizingFixer->stretchColumnWidth(TransactionTableModel::ToAddress); - connect(transactionView->horizontalHeader(), &QHeaderView::sectionResized, this, &TransactionView::updateHeaderSizes); -} - // Need to override default Ctrl+C action for amount as default behaviour is just to copy DisplayRole text bool TransactionView::eventFilter(QObject *obj, QEvent *event) { diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h index 6a0a806a2d..2497090270 100644 --- a/src/qt/transactionview.h +++ b/src/qt/transactionview.h @@ -87,10 +87,6 @@ class TransactionView : public QWidget QWidget *createDateRangeWidget(); void updateCalendarWidgets(); - GUIUtil::TableViewLastColumnResizingFixer *columnResizingFixer; - - virtual void resizeEvent(QResizeEvent* event); - bool eventFilter(QObject *obj, QEvent *event); private Q_SLOTS: @@ -110,7 +106,6 @@ private Q_SLOTS: void abandonTx(); void rebroadcastTx(); void reconsiderBip47Tx(); - Q_SIGNALS: void doubleClicked(const QModelIndex&); diff --git a/src/qt/txhistorydialog.cpp b/src/qt/txhistorydialog.cpp index 7decc1c85c..b0d945e357 100644 --- a/src/qt/txhistorydialog.cpp +++ b/src/qt/txhistorydialog.cpp @@ -72,9 +72,7 @@ TXHistoryDialog::TXHistoryDialog(QWidget *parent) : ui->txHistoryTable->setHorizontalHeaderItem(4, new QTableWidgetItem("Type")); ui->txHistoryTable->setHorizontalHeaderItem(5, new QTableWidgetItem("Address")); ui->txHistoryTable->setHorizontalHeaderItem(6, new QTableWidgetItem("Amount")); - // borrow ColumnResizingFixer again - borrowedColumnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(ui->txHistoryTable, 100, 100, this); - // allow user to adjust - go interactive then manually set widths + #if QT_VERSION < 0x050000 ui->txHistoryTable->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed); ui->txHistoryTable->horizontalHeader()->setResizeMode(3, QHeaderView::Interactive); @@ -127,7 +125,6 @@ TXHistoryDialog::TXHistoryDialog(QWidget *parent) : ui->txHistoryTable->resizeColumnToContents(6); ui->txHistoryTable->setColumnHidden(0, true); // hidden txid for displaying transaction details ui->txHistoryTable->setColumnHidden(1, true); // hideen sort key - borrowedColumnResizingFixer->stretchColumnWidth(5); ui->txHistoryTable->setSortingEnabled(true); ui->txHistoryTable->horizontalHeader()->setSortIndicator(1, Qt::DescendingOrder); // sort by hidden sort key } @@ -521,12 +518,6 @@ void TXHistoryDialog::showDetails() } } -void TXHistoryDialog::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); - borrowedColumnResizingFixer->stretchColumnWidth(5); -} - std::string TXHistoryDialog::shrinkTxType(int txType, bool *fundsMoved) { string displayType = "Unknown"; diff --git a/src/qt/txhistorydialog.h b/src/qt/txhistorydialog.h index 01e8ca06c8..a0e6b70de1 100644 --- a/src/qt/txhistorydialog.h +++ b/src/qt/txhistorydialog.h @@ -56,14 +56,12 @@ class TXHistoryDialog : public QDialog void setClientModel(ClientModel *model); void setWalletModel(WalletModel *model); - virtual void resizeEvent(QResizeEvent* event); std::string shrinkTxType(int txType, bool *fundsMoved); private: Ui::txHistoryDialog *ui; ClientModel *clientModel; WalletModel *walletModel; - GUIUtil::TableViewLastColumnResizingFixer *borrowedColumnResizingFixer; QMenu *contextMenu; HistoryMap txHistoryMap; From aa843c68a06b4d1d411f1990b107ab4e9b5da21b Mon Sep 17 00:00:00 2001 From: "levoncrypto1994@gmail.com" Date: Thu, 10 Aug 2023 17:11:36 +0400 Subject: [PATCH 161/197] fix Transaction tab resize issue --- src/qt/transactionview.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 3ecbbd58b3..7ad51fc776 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -238,7 +238,7 @@ void TransactionView::setModel(WalletModel *_model) transactionView->setColumnWidth(TransactionTableModel::Date, DATE_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH); transactionView->horizontalHeader()->setSectionResizeMode(TransactionTableModel::Amount, QHeaderView::Fixed); - transactionView->horizontalHeader()->setMinimumSectionSize(23); + transactionView->horizontalHeader()->setMinimumSectionSize(23); transactionView->horizontalHeader()->setStretchLastSection(true); transactionView->horizontalHeader()->setMaximumSectionSize(260); @@ -432,7 +432,7 @@ void TransactionView::updateHeaderSizes(int logicalIndex, int oldSize, int newSi {TransactionTableModel::Amount, amountWidget} }; - if(logicalIndex <= TransactionTableModel::ToAddress) + if(logicalIndex <= TransactionTableModel::Amount) return; for(std::pair const & p : headerWidgets) { From 9ffd44bc0a54d2abdf1d8780015013065221cfdc Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sat, 12 Aug 2023 05:30:40 +0400 Subject: [PATCH 162/197] Fixing tx not mined issue --- src/txmempool.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/txmempool.cpp b/src/txmempool.cpp index cad8e00c11..f1f9eeff3a 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -39,7 +39,7 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFe nSizeWithDescendants = GetTxSize(); nModFeesWithDescendants = nFee; CAmount nValueIn = tx->GetValueOut()+nFee; - if (!tx->IsZerocoinSpend() && !tx->IsSigmaSpend() && !tx->IsZerocoinRemint() && !tx->IsLelantusJoinSplit()) { + if (!tx->IsZerocoinSpend() && !tx->IsSigmaSpend() && !tx->IsZerocoinRemint() && !tx->IsLelantusJoinSplit() && !tx->IsSparkSpend()) { assert(inChainInputValue <= nValueIn); } @@ -417,7 +417,7 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, const CTransaction& tx = newit->GetTx(); std::set setParentTransactions; - if (!entry.GetTx().IsSigmaSpend() && !entry.GetTx().IsLelantusJoinSplit()) { + if (!entry.GetTx().IsSigmaSpend() && !entry.GetTx().IsLelantusJoinSplit() && !entry.GetTx().IsSparkSpend()) { for (unsigned int i = 0; i < tx.vin.size(); i++) { mapNextTx.insert(std::make_pair(&tx.vin[i].prevout, &tx)); setParentTransactions.insert(tx.vin[i].prevout.hash); @@ -518,8 +518,8 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) { NotifyEntryRemoved(it->GetSharedTx(), reason); const uint256 hash = it->GetTx().GetHash(); - if (!it->GetTx().IsSigmaSpend() && !it->GetTx().IsLelantusJoinSplit()) { - LogPrintf("removeUnchecked txHash=%s, IsSpend()=%s\n", hash.ToString(), it->GetTx().IsSigmaSpend() || it->GetTx().IsLelantusJoinSplit()); + if (!it->GetTx().IsSigmaSpend() && !it->GetTx().IsLelantusJoinSplit() && !it->GetTx().IsSparkSpend()) { + LogPrintf("removeUnchecked txHash=%s, IsSpend()=%s\n", hash.ToString(), it->GetTx().IsSigmaSpend() || it->GetTx().IsLelantusJoinSplit() || it->GetTx().IsSparkSpend()); BOOST_FOREACH(const CTxIn& txin, it->GetTx().vin) mapNextTx.erase(txin.prevout); } @@ -658,7 +658,7 @@ void CTxMemPool::addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewC uint256 txhash = tx.GetHash(); for (unsigned int j = 0; j < tx.vin.size(); j++) { const CTxIn input = tx.vin[j]; - if (input.IsSigmaSpend() || input.IsLelantusJoinSplit()) { + if (input.IsSigmaSpend() || input.IsLelantusJoinSplit() || tx.IsSparkSpend()) { continue; } @@ -737,7 +737,7 @@ void CTxMemPool::addSpentIndex(const CTxMemPoolEntry &entry, const CCoinsViewCac uint256 txhash = tx.GetHash(); for (unsigned int j = 0; j < tx.vin.size(); j++) { const CTxIn input = tx.vin[j]; - if (input.IsSigmaSpend() || input.IsLelantusJoinSplit()) { + if (input.IsSigmaSpend() || input.IsLelantusJoinSplit() || tx.IsSparkSpend()) { continue; } From 8582fa477023762a1bdb55f2eecc4fd799b89f13 Mon Sep 17 00:00:00 2001 From: "levoncrypto1994@gmail.com" Date: Sat, 12 Aug 2023 15:58:28 +0400 Subject: [PATCH 163/197] switcher fix --- src/qt/transactionview.cpp | 2 +- src/qt/transactionview.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 7ad51fc776..86ed7cfeae 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -67,7 +67,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa headerLayout->addWidget(watchOnlyWidget); instantsendWidget = new QComboBox(this); - instantsendWidget->setFixedWidth(24); + instantsendWidget->setFixedWidth(150); instantsendWidget->addItem(tr("All"), TransactionFilterProxy::InstantSendFilter_All); instantsendWidget->addItem(tr("Locked by InstantSend"), TransactionFilterProxy::InstantSendFilter_Yes); instantsendWidget->addItem(tr("Not locked by InstantSend"), TransactionFilterProxy::InstantSendFilter_No); diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h index 2497090270..792bbb5277 100644 --- a/src/qt/transactionview.h +++ b/src/qt/transactionview.h @@ -51,7 +51,7 @@ class TransactionView : public QWidget }; enum ColumnWidths { - STATUS_COLUMN_WIDTH = 30, + STATUS_COLUMN_WIDTH = 170, WATCHONLY_COLUMN_WIDTH = 23, INSTANTSEND_COLUMN_WIDTH = 23, DATE_COLUMN_WIDTH = 120, From 1a8a4f0459c943f67bf8a7458b4e5fea26650a9c Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 13 Aug 2023 04:54:09 +0400 Subject: [PATCH 164/197] Making conditions look better --- src/coins.cpp | 4 ++-- src/primitives/transaction.cpp | 4 ++++ src/primitives/transaction.h | 1 + src/txdb.cpp | 4 ++-- src/txmempool.cpp | 20 ++++++++++---------- src/validation.cpp | 25 ++++++++++--------------- src/wallet/rpcwallet.cpp | 2 +- src/wallet/wallet.cpp | 6 +++--- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/coins.cpp b/src/coins.cpp index 130e2bef35..1a0f29afe0 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -216,7 +216,7 @@ unsigned int CCoinsViewCache::GetCacheSize() const { CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const { - if (tx.IsCoinBase() || tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) + if (tx.IsCoinBase() || tx.HasNoRegularInputs()) return 0; CAmount nResult = 0; @@ -228,7 +228,7 @@ CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const { - if (!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { + if (!tx.IsCoinBase() && !tx.HasNoRegularInputs()) { for (unsigned int i = 0; i < tx.vin.size(); i++) { if (!HaveCoin(tx.vin[i].prevout)) { return false; diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index efed5881c9..e36aaa986d 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -259,6 +259,10 @@ bool CTransaction::HasNoRegularInputs() const { return IsZerocoinSpend() || IsSigmaSpend() || IsZerocoinRemint() || IsLelantusJoinSplit() || IsSparkSpend(); } +bool CTransaction::HasPrivateInputs() const { + return IsSigmaSpend() || IsLelantusJoinSplit() || IsSparkSpend(); +} + unsigned int CTransaction::CalculateModifiedSize(unsigned int nTxSize) const { // In order to avoid disincentivizing cleaning up the UTXO set we don't count diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index f653f2dafc..8ade56f1d5 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -459,6 +459,7 @@ class CTransaction bool IsSparkMint() const; bool HasNoRegularInputs() const; + bool HasPrivateInputs() const; /** * Get the total transaction size in bytes, including witness data. diff --git a/src/txdb.cpp b/src/txdb.cpp index 1ff432acec..326b9c9576 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -624,7 +624,7 @@ void handleOutput(const CTxOut &out, size_t outNo, uint256 const & txHash, int h void CDbIndexHelper::ConnectTransaction(CTransaction const & tx, int height, int txNumber, CCoinsViewCache const & view) { size_t no = 0; - if(!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { + if(!tx.IsCoinBase() && !tx.HasNoRegularInputs()) { for (CTxIn const & input : tx.vin) { handleInput(input, no++, tx.GetHash(), height, txNumber, view, addressIndex, addressUnspentIndex, spentIndex); } @@ -679,7 +679,7 @@ void CDbIndexHelper::DisconnectTransactionInputs(CTransaction const & tx, int he size_t no = 0; - if(!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) + if(!tx.IsCoinBase() && !tx.HasNoRegularInputs()) for (CTxIn const & input : tx.vin) { handleInput(input, no++, tx.GetHash(), height, txNumber, view, addressIndex, addressUnspentIndex, spentIndex); } diff --git a/src/txmempool.cpp b/src/txmempool.cpp index f1f9eeff3a..37ee90a610 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -39,7 +39,7 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFe nSizeWithDescendants = GetTxSize(); nModFeesWithDescendants = nFee; CAmount nValueIn = tx->GetValueOut()+nFee; - if (!tx->IsZerocoinSpend() && !tx->IsSigmaSpend() && !tx->IsZerocoinRemint() && !tx->IsLelantusJoinSplit() && !tx->IsSparkSpend()) { + if (!tx->HasNoRegularInputs()) { assert(inChainInputValue <= nValueIn); } @@ -417,7 +417,7 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, const CTransaction& tx = newit->GetTx(); std::set setParentTransactions; - if (!entry.GetTx().IsSigmaSpend() && !entry.GetTx().IsLelantusJoinSplit() && !entry.GetTx().IsSparkSpend()) { + if (!entry.GetTx().HasPrivateInputs()) { for (unsigned int i = 0; i < tx.vin.size(); i++) { mapNextTx.insert(std::make_pair(&tx.vin[i].prevout, &tx)); setParentTransactions.insert(tx.vin[i].prevout.hash); @@ -518,8 +518,8 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) { NotifyEntryRemoved(it->GetSharedTx(), reason); const uint256 hash = it->GetTx().GetHash(); - if (!it->GetTx().IsSigmaSpend() && !it->GetTx().IsLelantusJoinSplit() && !it->GetTx().IsSparkSpend()) { - LogPrintf("removeUnchecked txHash=%s, IsSpend()=%s\n", hash.ToString(), it->GetTx().IsSigmaSpend() || it->GetTx().IsLelantusJoinSplit() || it->GetTx().IsSparkSpend()); + if (!it->GetTx().HasPrivateInputs()) { + LogPrintf("removeUnchecked txHash=%s, IsSpend()=%s\n", hash.ToString(), it->GetTx().HasPrivateInputs()); BOOST_FOREACH(const CTxIn& txin, it->GetTx().vin) mapNextTx.erase(txin.prevout); } @@ -658,7 +658,7 @@ void CTxMemPool::addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewC uint256 txhash = tx.GetHash(); for (unsigned int j = 0; j < tx.vin.size(); j++) { const CTxIn input = tx.vin[j]; - if (input.IsSigmaSpend() || input.IsLelantusJoinSplit() || tx.IsSparkSpend()) { + if (tx.HasPrivateInputs()) { continue; } @@ -737,7 +737,7 @@ void CTxMemPool::addSpentIndex(const CTxMemPoolEntry &entry, const CCoinsViewCac uint256 txhash = tx.GetHash(); for (unsigned int j = 0; j < tx.vin.size(); j++) { const CTxIn input = tx.vin[j]; - if (input.IsSigmaSpend() || input.IsLelantusJoinSplit() || tx.IsSparkSpend()) { + if (tx.HasPrivateInputs()) { continue; } @@ -1140,13 +1140,13 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const assert(linksiter != mapLinks.end()); const TxLinks &links = linksiter->second; innerUsage += memusage::DynamicUsage(links.children); - if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) + if (!tx.HasPrivateInputs()) innerUsage += memusage::DynamicUsage(links.parents); bool fDependsWait = false; setEntries setParentCheck; int64_t parentSizes = 0; int64_t parentSigOpCost = 0; - if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { + if (!tx.HasPrivateInputs()) { BOOST_FOREACH(const CTxIn &txin, tx.vin) { // Check that every mempool transaction's inputs refer to available coins, or other mempool tx's. indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash); @@ -1211,7 +1211,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const waitingOnDependants.push_back(&(*it)); else { CValidationState state; - bool fCheckResult = tx.IsCoinBase() || tx.IsSigmaSpend() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend() || + bool fCheckResult = tx.IsCoinBase() || tx.HasPrivateInputs() || Consensus::CheckTxInputs(tx, state, mempoolDuplicate, nSpendHeight); assert(fCheckResult); @@ -1229,7 +1229,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const assert(stepsSinceLastRemove < waitingOnDependants.size()); } else { const CTransaction &tx = entry->GetTx(); - bool fCheckResult = tx.IsCoinBase() || tx.IsSigmaSpend() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend() || + bool fCheckResult = tx.IsCoinBase() || tx.HasPrivateInputs() || Consensus::CheckTxInputs(entry->GetTx(), state, mempoolDuplicate, nSpendHeight); assert(fCheckResult); UpdateCoins(entry->GetTx(), mempoolDuplicate, 1000000); diff --git a/src/validation.cpp b/src/validation.cpp index 409b0532c0..b8edbf0320 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -519,7 +519,7 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx) unsigned int GetP2SHSigOpCount(const CTransaction &tx, const CCoinsViewCache &inputs) { - if (tx.IsCoinBase() || tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsLelantusJoinSplit()) + if (tx.IsCoinBase() || tx.HasNoRegularInputs()) return 0; unsigned int nSigOps = 0; @@ -538,7 +538,7 @@ int64_t GetTransactionSigOpCost(const CTransaction &tx, const CCoinsViewCache &i { int64_t nSigOps = GetLegacySigOpCount(tx) * WITNESS_SCALE_FACTOR; - if (tx.IsCoinBase() || tx.IsZerocoinSpend() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) + if (tx.IsCoinBase() || tx.HasNoRegularInputs()) return nSigOps; if (flags & SCRIPT_VERIFY_P2SH) { @@ -615,7 +615,7 @@ bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fChe bool const check_di = nHeight != INT_MAX && nHeight > ::Params().GetConsensus().nStartDuplicationCheck; if (fCheckDuplicateInputs || check_di) { std::set vInOutPoints; - if (tx.IsSigmaSpend() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend()) { + if (tx.HasPrivateInputs()) { std::set spendScripts; for (const auto& txin: tx.vin) { @@ -1023,7 +1023,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C std::set setConflicts; { LOCK(pool.cs); // protect pool.mapNextTx - if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { + if (!tx.HasPrivateInputs()) { BOOST_FOREACH(const CTxIn &txin, tx.vin) { auto itConflicting = pool.mapNextTx.find(txin.prevout); @@ -1089,7 +1089,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C } } - if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { + if (!tx.HasPrivateInputs()) { // do all inputs exist? BOOST_FOREACH(const CTxIn txin, tx.vin) { if (!pcoinsTip->HaveCoinInCache(txin.prevout)) { @@ -1168,7 +1168,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C CAmount inChainInputValue = 0; bool fSpendsCoinbase = false; - if (!tx.IsSigmaSpend() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) { + if (!tx.HasPrivateInputs()) { // Keep track of transactions that spend a coinbase, which we re-scan // during reorgs to ensure COINBASE_MATURITY is still met. BOOST_FOREACH(const CTxIn &txin, tx.vin) { @@ -2004,7 +2004,7 @@ void static InvalidBlockFound(CBlockIndex *pindex, const CValidationState &state void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight) { // mark inputs spent - if (!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit()) { + if (!tx.IsCoinBase() && !tx.HasNoRegularInputs()) { txundo.vprevout.reserve(tx.vin.size()); BOOST_FOREACH(const CTxIn &txin, tx.vin) { txundo.vprevout.emplace_back(); @@ -2218,7 +2218,7 @@ bool CheckZerocoinFoundersInputs(const CTransaction &tx, CValidationState &state bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheStore, PrecomputedTransactionData& txdata, std::vector *pvChecks) { - if (!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit() &&!tx.IsSparkSpend()) + if (!tx.IsCoinBase() && !tx.HasNoRegularInputs()) { if (!Consensus::CheckTxInputs(tx, state, inputs, GetSpendHeight(inputs))) return false; @@ -2880,7 +2880,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return state.DoS(100, error("ConnectBlock(): invalid joinsplit tx"), REJECT_INVALID, "bad-txns-input-invalid"); - if (!tx.IsCoinBase() && !tx.IsZerocoinSpend() && !tx.IsSigmaSpend() && !tx.IsZerocoinRemint() && !tx.IsLelantusJoinSplit() && !tx.IsSparkSpend()) + if (!tx.IsCoinBase() && !tx.HasNoRegularInputs()) { if (!view.HaveInputs(tx)) return state.DoS(100, error("ConnectBlock(): inputs missing/spent"), @@ -2954,12 +2954,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin REJECT_INVALID, "bad-blk-sigops"); txdata.emplace_back(tx); - if (!tx.IsCoinBase() - && !tx.IsZerocoinSpend() - && !tx.IsSigmaSpend() - && !tx.IsZerocoinRemint() - && !tx.IsLelantusJoinSplit() - && !tx.IsSparkSpend()) + if (!tx.IsCoinBase() && !tx.HasNoRegularInputs()) { nFees += view.GetValueIn(tx)-tx.GetValueOut(); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 80f1cdaddb..96fd91a14d 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1576,7 +1576,7 @@ void ListTransactions(CWallet * const pwallet, const CWalletTx& wtx, const std:: } entry.push_back(Pair("account", strSentAccount)); MaybePushAddress(entry, s.destination, addr); - if (wtx.tx->IsZerocoinSpend() || wtx.tx->IsSigmaSpend() || wtx.tx->IsZerocoinRemint() || wtx.tx->IsLelantusJoinSplit() || wtx.tx->IsSparkSpend()) { + if (wtx.tx->HasNoRegularInputs()) { entry.push_back(Pair("category", "spend")); } else if (wtx.tx->IsZerocoinMint() || wtx.tx->IsSigmaMint() || wtx.tx->IsLelantusMint() || wtx.tx->IsSparkMint()) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0c37c9364b..eb44bf9905 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -824,7 +824,7 @@ void CWallet::AddToSpends(const uint256& wtxid) return; BOOST_FOREACH(const CTxIn& txin, thisTx.tx->vin) { - if (!txin.IsZerocoinSpend() && !txin.IsSigmaSpend() && !txin.IsLelantusJoinSplit() && !thisTx.tx->IsSparkSpend()) { + if (!thisTx.tx->HasNoRegularInputs()) { AddToSpends(txin.prevout, wtxid); } } @@ -1315,7 +1315,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex AssertLockHeld(cs_wallet); if (posInBlock != -1) { - if(!(tx.IsCoinBase() || tx.IsSigmaSpend() || tx.IsZerocoinRemint() || tx.IsZerocoinSpend() || tx.IsLelantusJoinSplit() || tx.IsSparkSpend())) { + if(!(tx.IsCoinBase() || tx.HasNoRegularInputs())) { BOOST_FOREACH(const CTxIn& txin, tx.vin) { std::pair range = mapTxSpends.equal_range(txin.prevout); while (range.first != range.second) { @@ -2679,7 +2679,7 @@ bool CWalletTx::IsTrusted() const // Trusted if all inputs are from us and are in the mempool: BOOST_FOREACH(const CTxIn& txin, tx->vin) { - if (txin.IsZerocoinSpend() || txin.IsSigmaSpend() || txin.IsZerocoinRemint() || txin.IsLelantusJoinSplit()) { + if (tx->HasNoRegularInputs()) { if (!(pwallet->IsMine(txin, *tx) & ISMINE_SPENDABLE)) { return false; } From b0bea3be5e3acb3cd0721c396fb7f4e603f3acf3 Mon Sep 17 00:00:00 2001 From: levoncrypto <95240473+levoncrypto@users.noreply.github.com> Date: Fri, 18 Aug 2023 15:17:34 +0400 Subject: [PATCH 165/197] Fix receive detail tab size issue (#1306) * Fix receive detail tab size issue * Fix receive detail tab size issue --- src/qt/receiverequestdialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index e95ad831a0..cb181e2f7b 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -130,6 +130,7 @@ void ReceiveRequestDialog::update() { if(!model) return; + resize(width(), 600); QString target = info.label; if(target.isEmpty()) target = info.address; From 39510e31b4d45bdb058440ad110b134e197ebb27 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 20 Aug 2023 18:56:44 +0400 Subject: [PATCH 166/197] Spark IS fixed --- src/llmq/quorums_instantsend.cpp | 69 +++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index a9e9310f38..8496b341d1 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -42,7 +42,7 @@ uint256 CInstantSendLock::GetRequestId() const namespace isutils { static int16_t const INSTANTSEND_ADAPTED_TX = std::numeric_limits::min(); -CTransaction AdaptJsplitTx(CTransaction const & tx); +CTransaction AdaptPrivateTx(CTransaction const & tx); } @@ -491,12 +491,22 @@ bool CInstantSendManager::CheckCanLock(const CTransaction& tx, bool printDebug, } if (tx.nType == isutils::INSTANTSEND_ADAPTED_TX ) { - for (CTxIn const & in : tx.vin) { - Scalar serial; - serial.deserialize(&in.scriptSig.front()); - LOCK(cs_main); - if (lelantus::CLelantusState::GetState()->IsUsedCoinSerial(serial)) - return false; + if (tx.IsLelantusJoinSplit()) { + for (CTxIn const & in : tx.vin) { + Scalar serial; + serial.deserialize(&in.scriptSig.front()); + LOCK(cs_main); + if (lelantus::CLelantusState::GetState()->IsUsedCoinSerial(serial)) + return false; + } + } else if (tx.IsSparkSpend()) { + for (CTxIn const & in : tx.vin) { + GroupElement lTag; + lTag.deserialize(&in.scriptSig.front()); + LOCK(cs_main); + if (spark::CSparkState::GetState()->IsUsedLTag(lTag)) + return false; + } } return true; } @@ -607,8 +617,8 @@ void CInstantSendManager::HandleNewInputLockRecoveredSig(const CRecoveredSig& re return; } - if(tx && tx->IsLelantusJoinSplit()) { - tx = MakeTransactionRef(isutils::AdaptJsplitTx(*tx)); + if (tx && (tx->IsLelantusJoinSplit() || tx->IsSparkSpend())) { + tx = MakeTransactionRef(isutils::AdaptPrivateTx(*tx)); } if (LogAcceptCategory("instantsend")) { @@ -1013,7 +1023,7 @@ void CInstantSendManager::SyncTransaction(const CTransaction& tx_, const CBlockI return; } - CTransaction const & tx{tx_.IsLelantusJoinSplit() ? isutils::AdaptJsplitTx(tx_) : tx_}; + CTransaction const & tx{(tx_.IsLelantusJoinSplit() || tx_.IsSparkSpend()) ? isutils::AdaptPrivateTx(tx_) : tx_}; bool inMempool = mempool.get(tx.GetHash()) != nullptr; bool isDisconnect = pindex && posInBlock == CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK; @@ -1497,7 +1507,7 @@ CInstantSendLockPtr CInstantSendManager::GetConflictingLock(const CTransaction& return nullptr; } - CTransaction const & tx{tx_.IsLelantusJoinSplit() ? isutils::AdaptJsplitTx(tx_) : tx_}; + CTransaction const & tx{(tx_.IsLelantusJoinSplit() || tx_.IsSparkSpend()) ? isutils::AdaptPrivateTx(tx_) : tx_}; LOCK(cs); for (const auto& in : tx.vin) { @@ -1567,6 +1577,43 @@ CTransaction AdaptJsplitTx(CTransaction const & tx) assert(result.GetHash() == tx.GetHash()); return result; } + +CTransaction AdaptSparkTx(CTransaction const & tx) +{ + static size_t const lTagSerialSize = 34; + + CTransaction result{tx}; + std::unique_ptr spend; + try { + spend = std::make_unique(spark::ParseSparkSpend(tx)); + } + catch (...) { + return result; + } + + const_cast*>(&result.vin)->clear(); //This const_cast was done intentionally as the current design allows for this way only + for (GroupElement const & lTag : spend->getUsedLTags()) { + CTxIn newin; + newin.scriptSig.resize(lTagSerialSize); + lTag.serialize(&newin.scriptSig.front()); + newin.prevout.hash = primitives::GetLTagHash(lTag); + newin.prevout.n = 0; + const_cast*>(&result.vin)->push_back(newin); + } + *const_cast(&result.nType) = INSTANTSEND_ADAPTED_TX; + assert(result.GetHash() == tx.GetHash()); + return result; +} + +CTransaction AdaptPrivateTx(CTransaction const & tx) +{ + if (tx.IsLelantusJoinSplit()) { + return AdaptJsplitTx(tx); + } else if (tx.IsSparkSpend()) { + return AdaptSparkTx(tx); + } + return tx; +} } } From 29c9ec43fd416fea24a556ce8764745a9731ad2f Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 20 Aug 2023 19:00:50 +0400 Subject: [PATCH 167/197] ZapSparkMints --- src/wallet/wallet.cpp | 14 +++++++++++++- src/wallet/wallet.h | 2 ++ src/wallet/walletdb.cpp | 14 ++++++++++++++ src/wallet/walletdb.h | 1 + 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index eb44bf9905..769c9e7b49 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -6259,6 +6259,17 @@ DBErrors CWallet::ZapLelantusMints() { return DB_LOAD_OK; } +DBErrors CWallet::ZapSparkMints() { + if (!fFileBacked) + return DB_LOAD_OK; + DBErrors nZapSparkMintRet = CWalletDB(strWalletFile, "cr+").ZapSparkMints(this); + if (nZapSparkMintRet != DB_LOAD_OK){ + LogPrintf("Failed to remove spark mints from CWalletDB"); + return nZapSparkMintRet; + } + + return DB_LOAD_OK; +} bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& strPurpose) { @@ -6926,7 +6937,8 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) CWallet *tempWallet = new CWallet(walletFile); DBErrors nZapMintRet = tempWallet->ZapSigmaMints(); DBErrors nZapLelantusMintRet = tempWallet->ZapLelantusMints(); - if (nZapMintRet != DB_LOAD_OK || nZapLelantusMintRet != DB_LOAD_OK) { + DBErrors nZapSparkMintRet = tempWallet->ZapSparkMints(); + if (nZapMintRet != DB_LOAD_OK || nZapLelantusMintRet != DB_LOAD_OK || nZapSparkMintRet != DB_LOAD_OK) { InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); return NULL; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d5a4e7b4af..1c91fb3909 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1173,6 +1173,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface DBErrors ZapSigmaMints(); // Remove all Lelantus HDMint objects from WalletDB DBErrors ZapLelantusMints(); + // Remove all Spark Mint objects from WalletDB + DBErrors ZapSparkMints(); bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index c9efbdc4a9..f3d33aabae 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1105,6 +1105,20 @@ DBErrors CWalletDB::ZapLelantusMints(CWallet *pwallet) { return DB_LOAD_OK; } +DBErrors CWalletDB::ZapSparkMints(CWallet *pwallet) { + // get list of spark Mints + std::unordered_map sparkMints = ListSparkMints(); + + // erase each Mint + BOOST_FOREACH(auto & mint, sparkMints) + { + if (!EraseSparkMint(mint.first)) + return DB_CORRUPT; + } + + return DB_LOAD_OK; +} + DBErrors CWalletDB::ZapWalletTx(CWallet* pwallet, std::vector& vWtx) { // build list of wallet TXs diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index ac5093ac88..4c83657453 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -265,6 +265,7 @@ class CWalletDB : public CDB DBErrors ZapSelectTx(CWallet* pwallet, std::vector& vHashIn, std::vector& vHashOut); DBErrors ZapSigmaMints(CWallet* pwallet); DBErrors ZapLelantusMints(CWallet *pwallet); + DBErrors ZapSparkMints(CWallet *pwallet); static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys); static bool Recover(CDBEnv& dbenv, const std::string& filename); From a06df90fbb97dd70b7743ea97d263a92ea7935a6 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 20 Aug 2023 20:59:35 +0400 Subject: [PATCH 168/197] Test for Spark IS --- qa/rpc-tests/llmq-is-spark.py | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100755 qa/rpc-tests/llmq-is-spark.py diff --git a/qa/rpc-tests/llmq-is-spark.py b/qa/rpc-tests/llmq-is-spark.py new file mode 100755 index 0000000000..f45502f399 --- /dev/null +++ b/qa/rpc-tests/llmq-is-spark.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# Copyright (c) 2015-2018 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.mininode import * +from test_framework.test_framework import EvoZnodeTestFramework +from test_framework.util import sync_blocks, set_node_times, \ + isolate_node, reconnect_isolated_node, set_mocktime, get_mocktime +from test_framework.util import assert_equal, assert_raises_jsonrpc, \ + bitcoind_processes, start_nodes, start_node, connect_nodes_bi + +from decimal import Decimal + +''' +llmq-is-spark.py + +Testing Instantsend for Spark transactions +''' + +class LLMQ_IS_Lelantus(EvoZnodeTestFramework): + def __init__(self): + super().__init__(6, 5, extra_args=[['-debug=instantsend']] * 6 ) + self.sporkprivkey = "cW2YM2xaeCaebfpKguBahUAgEzLXgSserWRuD29kSyKHq1TTgwRQ" + + def run_test(self): + self.sporkAddress = self.nodes[0].getaccountaddress("") + self.mine_quorum() + self.wait_for_chainlocked_block_all_nodes(self.nodes[0].getbestblockhash()) + + self.nodes[0].generate(1001 - self.nodes[0].getblockcount()) + + sparkaddress = self.nodes[0].getnewsparkaddress()[0] + for i in range(0, 3): + mintTxids = self.nodes[0].mintspark({sparkaddress: {"amount": 1, "memo":"Test memo"}}) + + for mintTxid in mintTxids: + mintTx = self.nodes[0].getrawtransaction(mintTxid, 1) + val = 0 + for vi in mintTx['vin']: + val += vi['valueSat'] + if val > 10000: + break; + val = Decimal((val - 10000) / 1e+8).quantize(Decimal('1e-7')) + + assert(self.wait_for_instantlock(mintTxid, self.nodes[0])) + + mintDspend = self.nodes[0].createrawtransaction(mintTx['vin'], {self.nodes[0].getnewaddress(): str(val)}) + assert_raises_jsonrpc(-26, 'tx-txlock-conflict', self.nodes[0].sendrawtransaction, mintDspend) + + self.nodes[0].generate(3) + assert (self.nodes[0].getrawtransaction(mintTxid, True)['confirmations'] > 0) + + spendTxid = self.nodes[0].spendspark({self.sporkAddress: {"amount": 0.1, "subtractFee": False}}) + assert(self.wait_for_instantlock(spendTxid, self.nodes[0])) + +if __name__ == '__main__': + LLMQ_IS_Lelantus().main() From 3a77e846695d14ca67e9ba84e51a9355c546b7bd Mon Sep 17 00:00:00 2001 From: psolstice Date: Sun, 20 Aug 2023 21:03:52 +0200 Subject: [PATCH 169/197] Fixed broken spark state (#1307) * Fixed broken spark state when a block with more than one spark spend fails lTag check * Stop CSparkState::AddSpend from throwing an exception * Adding cover set existance check * Failing test fixed --------- Co-authored-by: levonpetrosyan93 --- src/spark/state.cpp | 26 ++++++++++++++++---------- src/test/spark_state_test.cpp | 3 --- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/spark/state.cpp b/src/spark/state.cpp index bc6e987746..3440965ad0 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -262,15 +262,17 @@ bool ConnectBlockSpark( )) { return false; } + } - if (!fJustCheck) { + if (!fJustCheck) { + BOOST_FOREACH(auto& lTag, pblock->sparkTxInfo->spentLTags) { pindexNew->spentLTags.insert(lTag); sparkState.AddSpend(lTag.first, lTag.second); } } - - if (fJustCheck) + else { return true; + } const auto& params = ::Params().GetConsensus(); CHash256 hash; @@ -633,9 +635,16 @@ bool CheckSparkSpendTransaction( spend->setCoverSets(cover_set_data); spend->setVout(Vout); + const std::vector& ids = spend->getCoinGroupIds(); + for (const auto& id : ids) { + if (!cover_sets.count(id) || !cover_set_data.count(id)) + return state.DoS(100, + error("CheckSparkSpendTransaction: No cover set found.")); + } + BatchProofContainer* batchProofContainer = BatchProofContainer::get_instance(); bool useBatching = batchProofContainer->fCollectProofs && !isVerifyDB && !isCheckWallet && sparkTxInfo && !sparkTxInfo->fInfoIsComplete; - + // if we are collecting proofs, skip verification and collect proofs // add proofs into container if (useBatching) { @@ -651,7 +660,6 @@ bool CheckSparkSpendTransaction( if (passVerify) { const std::vector& lTags = spend->getUsedLTags(); - const std::vector& ids = spend->getCoinGroupIds(); if (lTags.size() != ids.size()) { return state.DoS(100, @@ -1003,12 +1011,10 @@ void CSparkState::AddMintsToStateAndBlockIndex( } void CSparkState::AddSpend(const GroupElement& lTag, int coinGroupId) { - if (!mintMetaInfo.count(coinGroupId)) { - throw std::invalid_argument("group id doesn't exist"); + if (mintMetaInfo.count(coinGroupId) > 0) { + usedLTags[lTag] = coinGroupId; + spendMetaInfo[coinGroupId] += 1; } - - usedLTags[lTag] = coinGroupId; - spendMetaInfo[coinGroupId] += 1; } void CSparkState::RemoveSpend(const GroupElement& lTag) { diff --git a/src/test/spark_state_test.cpp b/src/test/spark_state_test.cpp index cea2eef88d..e39049c3c6 100644 --- a/src/test/spark_state_test.cpp +++ b/src/test/spark_state_test.cpp @@ -147,9 +147,6 @@ BOOST_AUTO_TEST_CASE(lTag_adding) BOOST_CHECK(!sparkState->IsUsedLTag(lTag2)); BOOST_CHECK(!sparkState->IsUsedLTagHash(receivedLTag, lTagHash2)); - // add lTags to group that doesn't exist, should fail - BOOST_CHECK_THROW(sparkState->AddSpend(GroupElement(), 100), std::invalid_argument); - sparkState->Reset(); mempool.clear(); } From 834bb8e0af4df94edbeed0816061132633c8203b Mon Sep 17 00:00:00 2001 From: Peter Shugalev Date: Fri, 25 Aug 2023 20:06:32 +0200 Subject: [PATCH 170/197] Bumped spark start block numbers for mainnet --- src/firo_params.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/firo_params.h b/src/firo_params.h index 1350bf7cdd..37e0179c5c 100644 --- a/src/firo_params.h +++ b/src/firo_params.h @@ -180,9 +180,9 @@ static const int64_t DUST_HARD_LIMIT = 1000; // 0.00001 FIRO mininput #define DANDELION_FLUFF 10 // Spark -#define SPARK_START_BLOCK 700000 +#define SPARK_START_BLOCK 900000 #define SPARK_TESTNET_START_BLOCK 107000 -#define LELANTUS_GRACEFUL_PERIOD 800000 +#define LELANTUS_GRACEFUL_PERIOD 950000 #define LELANTUS_TESTNET_GRACEFUL_PERIOD 140000 // Versions of zerocoin mint/spend transactions From 3bc641933ac0f258fd5494a3eb1dca9a0dbd79f0 Mon Sep 17 00:00:00 2001 From: Peter Shugalev Date: Mon, 28 Aug 2023 11:28:02 +0200 Subject: [PATCH 171/197] Don't throw an exception in CSparkState::RemoveBlock --- src/spark/state.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/spark/state.cpp b/src/spark/state.cpp index 3440965ad0..9cce5ff48c 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -1059,9 +1059,8 @@ void CSparkState::RemoveBlock(CBlockIndex *index) { // roll back coin group updates for (auto &coins : index->sparkMintedCoins) { - if (coinGroups.count(coins.first) == 0) { - throw std::invalid_argument("Group Id does not exist"); - } + if (coinGroups.count(coins.first) == 0) + continue; SparkCoinGroupInfo& coinGroup = coinGroups[coins.first]; auto nMintsToForget = coins.second.size(); From d563a2229b24c0984828288534a1429a3f7b6dfa Mon Sep 17 00:00:00 2001 From: levoncrypto <95240473+levoncrypto@users.noreply.github.com> Date: Thu, 21 Sep 2023 12:58:05 +0400 Subject: [PATCH 172/197] QT: Transaction tab columns size fix (#1335) * QT: Amount column size fix * QT: Status column size fix --- src/qt/transactionview.cpp | 1 + src/qt/transactionview.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 86ed7cfeae..0af248c458 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -237,6 +237,7 @@ void TransactionView::setModel(WalletModel *_model) transactionView->setColumnWidth(TransactionTableModel::InstantSend, INSTANTSEND_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Date, DATE_COLUMN_WIDTH); transactionView->setColumnWidth(TransactionTableModel::Type, TYPE_COLUMN_WIDTH); + transactionView->setColumnWidth(TransactionTableModel::ToAddress, ADDRESS_COLUMN_WIDTH); transactionView->horizontalHeader()->setSectionResizeMode(TransactionTableModel::Amount, QHeaderView::Fixed); transactionView->horizontalHeader()->setMinimumSectionSize(23); transactionView->horizontalHeader()->setStretchLastSection(true); diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h index 792bbb5277..34775bb779 100644 --- a/src/qt/transactionview.h +++ b/src/qt/transactionview.h @@ -51,11 +51,12 @@ class TransactionView : public QWidget }; enum ColumnWidths { - STATUS_COLUMN_WIDTH = 170, + STATUS_COLUMN_WIDTH = 50, WATCHONLY_COLUMN_WIDTH = 23, INSTANTSEND_COLUMN_WIDTH = 23, DATE_COLUMN_WIDTH = 120, TYPE_COLUMN_WIDTH = 113, + ADDRESS_COLUMN_WIDTH = 300, AMOUNT_MINIMUM_COLUMN_WIDTH = 120, MINIMUM_COLUMN_WIDTH = 23 }; From 5f4487b8d0b2dbb2945a91733b1f177356c580f4 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 25 Sep 2023 12:04:53 +0400 Subject: [PATCH 173/197] Fixed crash when -mobile passed --- src/spark/state.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/spark/state.cpp b/src/spark/state.cpp index 9cce5ff48c..af2d95cf3b 100644 --- a/src/spark/state.cpp +++ b/src/spark/state.cpp @@ -1005,7 +1005,9 @@ void CSparkState::AddMintsToStateAndBlockIndex( LogPrintf("AddMintsToStateAndBlockIndex: Spark mint added id=%d\n", latestCoinId); index->sparkMintedCoins[latestCoinId].push_back(mint); if (GetBoolArg("-mobile", false)) { - index->sparkTxHash[mint.S] = GetTxHashFromCoin(mint); + COutPoint outPoint; + GetOutPointFromBlock(outPoint, mint, *pblock); + index->sparkTxHash[mint.S] = outPoint.hash; } } } From cfbe51a2c4529100a28d18c6329b871cb507a013 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sun, 8 Oct 2023 13:17:09 +0400 Subject: [PATCH 174/197] Unlock screen for spark first run --- src/qt/bitcoin.cpp | 37 ++++++++++++++++++++++++++++++++++++- src/wallet/wallet.cpp | 2 ++ src/wallet/wallet.h | 5 +++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index cbea7f2b5b..7db0654d5f 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -21,7 +21,7 @@ #include "splashscreen.h" #include "utilitydialog.h" #include "winshutdownmonitor.h" - +#include "askpassphrasedialog.h" #ifdef ENABLE_WALLET #include "paymentserver.h" #include "walletmodel.h" @@ -237,6 +237,9 @@ class BitcoinApplication: public QApplication WId getMainWinId() const; public Q_SLOTS: +#ifdef ENABLE_WALLET + void unlockWallet_(void * wallet); +#endif void initializeResult(int retval); void shutdownResult(int retval); /// Handle runaway exceptions. Shows a message box with the problem and quits the program. @@ -322,6 +325,15 @@ void BitcoinCore::shutdown() } } +#ifdef ENABLE_WALLET +static void unlockWallet(BitcoinApplication* application, CWallet* wallet) +{ + Q_UNUSED(wallet); + QMetaObject::invokeMethod(application, "unlockWallet_", Qt::QueuedConnection, + Q_ARG(void *, wallet)); +} +#endif + BitcoinApplication::BitcoinApplication(int &argc, char **argv): QApplication(argc, argv), coreThread(0), @@ -346,6 +358,10 @@ BitcoinApplication::BitcoinApplication(int &argc, char **argv): if (!platformStyle) // Fall back to "other" if specified name not found platformStyle = PlatformStyle::instantiate("other"); assert(platformStyle); + +#ifdef ENABLE_WALLET + UnlockWallet.connect(boost::bind(unlockWallet, this, _1)); +#endif } BitcoinApplication::~BitcoinApplication() @@ -368,6 +384,8 @@ BitcoinApplication::~BitcoinApplication() optionsModel = 0; delete platformStyle; platformStyle = 0; + UnlockWallet.disconnect(boost::bind(unlockWallet, this, _1)); + } #ifdef ENABLE_WALLET @@ -375,6 +393,23 @@ void BitcoinApplication::createPaymentServer() { paymentServer = new PaymentServer(this); } + +void BitcoinApplication::unlockWallet_(void * wallet) +{ + CWallet * wallet_ = reinterpret_cast(wallet); + + QString info = tr("You need to unlock to allow spark wallet be created."); + + walletModel = new WalletModel(platformStyle, wallet_, optionsModel); + + // Unlock wallet when requested by wallet model + if (walletModel->getEncryptionStatus() == WalletModel::Locked) + { + AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this->window, info); + dlg.setModel(walletModel); + dlg.exec(); + } +} #endif void BitcoinApplication::createOptionsModel(bool resetSettings) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 769c9e7b49..7e086b3336 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -70,6 +70,7 @@ bool fSendFreeTransactions = DEFAULT_SEND_FREE_TRANSACTIONS; bool fWalletRbf = DEFAULT_WALLET_RBF; const char * DEFAULT_WALLET_DAT = "wallet.dat"; +boost::signals2::signal UnlockWallet; /** * Fees smaller than this (in satoshi) are considered zero fee (for transaction creation) @@ -510,6 +511,7 @@ void CWallet::RequestUnlock() { return; LogPrintf("Requesting wallet unlock\n"); + UnlockWallet(this); fUnlockRequested.store(true); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 1c91fb3909..6ea15a2ef8 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -642,6 +642,11 @@ class CAccountingEntry class LelantusJoinSplitBuilder; + +/**Open unlock wallet window**/ +//static boost::signals2::signal UnlockWallet; +extern boost::signals2::signal UnlockWallet; + /** * A CWallet is an extension of a keystore, which also maintains a set of transactions and balances, * and provides the ability to create new transactions. From 2139866956f9b11aa4d1e3c6506428f4961f4473 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 9 Oct 2023 05:30:05 +0400 Subject: [PATCH 175/197] Temporarily bypass check for walletpassphrase rpc --- src/qt/bitcoin.cpp | 4 ++-- src/rpc/server.cpp | 2 +- src/wallet/wallet.cpp | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 7db0654d5f..733c2eeaa0 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -379,13 +379,13 @@ BitcoinApplication::~BitcoinApplication() #ifdef ENABLE_WALLET delete paymentServer; paymentServer = 0; + UnlockWallet.disconnect(boost::bind(unlockWallet, this, _1)); + #endif delete optionsModel; optionsModel = 0; delete platformStyle; platformStyle = 0; - UnlockWallet.disconnect(boost::bind(unlockWallet, this, _1)); - } #ifdef ENABLE_WALLET diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 2559a29a00..88c97abee2 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -539,7 +539,7 @@ UniValue CRPCTable::execute(const JSONRPCRequest &request) const // Return immediately if in warmup { LOCK(cs_rpcWarmup); - if (fRPCInWarmup) + if (fRPCInWarmup && request.strMethod != "walletpassphrase") // TODO this is temp check and will be removed after spark transition throw JSONRPCError(RPC_IN_WARMUP, rpcWarmupStatus); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 7e086b3336..6224f1f5ac 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -512,6 +512,8 @@ void CWallet::RequestUnlock() { LogPrintf("Requesting wallet unlock\n"); UnlockWallet(this); + printf("Please unlock the wallet with your passphrase to allow spark wallet be created\nYou need to do this only one time.\n"); + fUnlockRequested.store(true); } From 0414c9725831a4ebfcc3593876a9526ebfa1922f Mon Sep 17 00:00:00 2001 From: levoncrypto <95240473+levoncrypto@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:28:08 +0400 Subject: [PATCH 176/197] Depends NO_WALLET=1 setting fix (#1321) --- configure.ac | 28 ++++++++++++++++++++++++---- src/Makefile.am | 2 ++ src/init.cpp | 2 ++ src/lelantus.cpp | 3 +++ src/llmq/quorums_instantsend.cpp | 2 +- src/miner.cpp | 2 ++ src/qt/bitcoingui.cpp | 16 ++++++++++------ src/qt/masternodelist.cpp | 2 ++ src/qt/pcodemodel.h | 2 +- src/qt/recover.cpp | 4 ++++ src/qt/walletmodel.h | 3 ++- src/rpc/masternode.cpp | 3 ++- src/rpc/misc.cpp | 1 + src/rpc/rpcevo.cpp | 8 +++++--- src/sigma.cpp | 2 ++ src/validation.cpp | 2 ++ src/wallet/wallet.h | 2 ++ 17 files changed, 67 insertions(+), 17 deletions(-) diff --git a/configure.ac b/configure.ac index d4a8fd9732..dfbdddd117 100644 --- a/configure.ac +++ b/configure.ac @@ -92,7 +92,18 @@ AC_PATH_TOOL(OBJCOPY, objcopy) AC_ARG_VAR(PYTHONPATH, Augments the default search path for python module files) # Enable wallet -enable_wallet=yes +AC_ARG_ENABLE([wallet], + [AS_HELP_STRING([--disable-wallet], + [disable wallet (enabled by default)])], + [enable_wallet=$enableval], + [enable_wallet=yes]) + +AC_ARG_WITH([bdb], + [AS_HELP_STRING([--without-bdb], + [disable bdb wallet support (default is enabled if wallet is enabled)])], + [use_bdb=$withval], + [use_bdb=auto]) + AC_ARG_ENABLE([elysium], [AS_HELP_STRING([--enable-elysium],[enable elysium (disabled by default)])], @@ -651,9 +662,11 @@ AC_SUBST(LEVELDB_CPPFLAGS) AC_SUBST(LIBLEVELDB) AC_SUBST(LIBMEMENV) -if test x$enable_wallet != xno; then - dnl Check for libdb_cxx only if wallet enabled - BITCOIN_FIND_BDB48 +if test "$enable_wallet" != "no"; then + dnl Check for libdb_cxx only if wallet enabled + if test "$use_bdb" != "no"; then + BITCOIN_FIND_BDB48 + fi fi dnl Check for libminiupnpc (optional) @@ -1168,6 +1181,7 @@ AM_CONDITIONAL([USE_LCOV],[test x$use_lcov = xyes]) AM_CONDITIONAL([GLIBC_BACK_COMPAT],[test x$use_glibc_compat = xyes]) AM_CONDITIONAL([HARDEN],[test x$use_hardening = xyes]) AM_CONDITIONAL([ENABLE_SSE42],[test x$enable_sse42 = xyes]) +AM_CONDITIONAL([USE_BDB], [test "$use_bdb" = "yes"]) AC_DEFINE(CLIENT_VERSION_MAJOR, _CLIENT_VERSION_MAJOR, [Major version]) AC_DEFINE(CLIENT_VERSION_MINOR, _CLIENT_VERSION_MINOR, [Minor version]) @@ -1222,6 +1236,7 @@ AC_SUBST(ZMQ_LIBS) AC_SUBST(PROTOBUF_LIBS) AC_SUBST(QR_LIBS) AC_SUBST(DSYMUTIL_FLAT) +AC_SUBST(USE_BDB) AC_CONFIG_FILES([Makefile src/Makefile share/setup.nsi share/qt/Info.plist src/test/buildenv.py]) AC_CONFIG_FILES([qa/pull-tester/tests_config.py],[chmod +x qa/pull-tester/tests_config.py]) AC_CONFIG_FILES([contrib/devtools/split-debug.sh],[chmod +x contrib/devtools/split-debug.sh]) @@ -1291,6 +1306,11 @@ if test x$bitcoin_enable_qt != xno; then echo " qt version = $bitcoin_qt_got_major_vers" echo " with qr = $use_qr" fi + +if test "$enable_wallet" != "no"; then + echo " with bdb = $use_bdb" +fi + echo " with zmq = $use_zmq" echo " with test = $use_tests" echo " with bench = $use_bench" diff --git a/src/Makefile.am b/src/Makefile.am index 69e55d3d9f..005a93bc0e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -335,6 +335,7 @@ libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h libbitcoin_server_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libbitcoin_server_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_server_a_SOURCES = \ + activemasternode.cpp \ addrman.cpp \ addrdb.cpp \ batchedlogger.cpp \ @@ -407,6 +408,7 @@ libbitcoin_server_a_SOURCES = \ sigma.cpp \ lelantus.cpp \ spark/state.cpp \ + spark/primitives.cpp \ coin_containers.cpp \ mtpstate.cpp \ $(BITCOIN_CORE_H) diff --git a/src/init.cpp b/src/init.cpp index b8f7234ba7..ef2d083275 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -972,6 +972,7 @@ void InitParameterInteraction() LogPrintf("%s: parameter interaction: -whitelistforcerelay=1 -> setting -whitelistrelay=1\n", __func__); } +#ifdef ENABLE_WALLET // Forcing all mnemonic settings off if -usehd is off. if (!GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET)) { if (SoftSetBoolArg("-usemnemonic", false) && SoftSetArg("-mnemonic", "") && SoftSetArg("-mnemonicpassphrase", "") && SoftSetArg("-hdseed", "not hex")) @@ -983,6 +984,7 @@ void InitParameterInteraction() if (SoftSetArg("-mnemonic", "") && SoftSetArg("-mnemonicpassphrase", "") && SoftSetArg("-hdseed", "not hex")) LogPrintf("%s: Potential parameter interaction: -usemnemonic=0 -> setting -mnemonic=\"\", -mnemonicpassphrase=\"\"\n, -hdseed=\"not hex\"\n", __func__); } +#endif // ENABLE_WALLET } static std::string ResolveErrMsg(const char *const optname, const std::string &strBind) { diff --git a/src/lelantus.cpp b/src/lelantus.cpp index 9c71202e1b..57a0f29687 100644 --- a/src/lelantus.cpp +++ b/src/lelantus.cpp @@ -6,8 +6,11 @@ #include "base58.h" #include "definition.h" #include "txmempool.h" +#ifdef ENABLE_WALLET #include "wallet/wallet.h" #include "wallet/walletdb.h" +#endif // ENABLE_WALLET +#include "sigma.h" #include "crypto/sha256.h" #include "liblelantus/coin.h" #include "liblelantus/schnorr_prover.h" diff --git a/src/llmq/quorums_instantsend.cpp b/src/llmq/quorums_instantsend.cpp index 8496b341d1..9db3cade75 100644 --- a/src/llmq/quorums_instantsend.cpp +++ b/src/llmq/quorums_instantsend.cpp @@ -18,7 +18,7 @@ #ifdef ENABLE_WALLET #include "wallet/wallet.h" #endif - +#include "primitives/mint_spend.h" #include #include #include diff --git a/src/miner.cpp b/src/miner.cpp index 2d9f3d95d5..fee39aaff0 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -25,7 +25,9 @@ #include "util.h" #include "utilmoneystr.h" #include "validationinterface.h" +#ifdef ENABLE_WALLET #include "wallet/wallet.h" +#endif // ENABLE_WALLET #include "definition.h" #include "crypto/scrypt.h" #include "crypto/MerkleTreeProof/mtp.h" diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index fb8a0f5191..bbffd0a4c2 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -284,9 +284,10 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *_platformStyle, const NetworkStyle * // Install event filter to be able to catch status tip events (QEvent::StatusTip) this->installEventFilter(this); - +#ifdef ENABLE_WALLET // Initially wallet actions should be disabled setWalletActionsEnabled(false); +#endif // ENABLE_WALLET // Subscribe to notifications from core subscribeToCoreSignals(); @@ -456,10 +457,11 @@ void BitcoinGUI::createActions() optionsAction->setEnabled(false); toggleHideAction = new QAction(tr("&Show / Hide"), this); toggleHideAction->setStatusTip(tr("Show or hide the main Window")); - +#ifdef ENABLE_WALLET encryptWalletAction = new QAction(tr("&Encrypt Wallet..."), this); encryptWalletAction->setStatusTip(tr("Encrypt the private keys that belong to your wallet")); encryptWalletAction->setCheckable(true); +#endif // ENABLE_WALLET backupWalletAction = new QAction(tr("&Backup Wallet..."), this); backupWalletAction->setStatusTip(tr("Backup wallet to another location")); changePassphraseAction = new QAction(tr("&Change Passphrase..."), this); @@ -655,10 +657,9 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel) setTrayIconVisible(optionsModel->getHideTrayIcon()); } { +#ifdef ENABLE_WALLET auto blocks = clientModel->getNumBlocks(); checkZnodeVisibility(blocks); - -#ifdef ENABLE_WALLET checkLelantusVisibility(blocks); #endif // ENABLE_WALLET } @@ -1085,9 +1086,8 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVer #ifdef ENABLE_WALLET checkLelantusVisibility(count); -#endif // ENABLE_WALLET - checkZnodeVisibility(count); +#endif // ENABLE_WALLET } @@ -1452,7 +1452,9 @@ void BitcoinGUI::updateLelantusPage() void BitcoinGUI::setRapAddressesVisible(bool checked) { +#ifdef ENABLE_WALLET gotoOverviewPage(); +#endif // ENABLE_WALLET createPcodeAction->setVisible(checked); } @@ -1509,7 +1511,9 @@ void BitcoinGUI::checkLelantusVisibility(int numBlocks) if (allowLelantusPage != lelantusAction->isVisible()) { if (!allowLelantusPage && lelantusAction->isChecked()) { +#ifdef ENABLE_WALLET gotoOverviewPage(); +#endif // ENABLE_WALLET } lelantusAction->setVisible(allowLelantusPage); } diff --git a/src/qt/masternodelist.cpp b/src/qt/masternodelist.cpp index 08b9199511..a5b5e399e0 100644 --- a/src/qt/masternodelist.cpp +++ b/src/qt/masternodelist.cpp @@ -10,7 +10,9 @@ #include "netbase.h" #include "sync.h" #include "validation.h" +#ifdef ENABLE_WALLET #include "wallet/wallet.h" +#endif // ENABLE_WALLET #include "walletmodel.h" #include diff --git a/src/qt/pcodemodel.h b/src/qt/pcodemodel.h index 170ce5738b..7d9e960503 100644 --- a/src/qt/pcodemodel.h +++ b/src/qt/pcodemodel.h @@ -6,7 +6,7 @@ #define PCODEMODEL_H #include "walletmodel.h" - +#include "../bip47/paymentcode.h" #include #include #include diff --git a/src/qt/recover.cpp b/src/qt/recover.cpp index 8ac7ba9106..ba0611bebf 100644 --- a/src/qt/recover.cpp +++ b/src/qt/recover.cpp @@ -5,7 +5,9 @@ #include "util.h" +#ifdef ENABLE_WALLET #include "../wallet/wallet.h" +#endif // ENABLE_WALLET #include "../wallet/bip39.h" #include "support/allocators/secure.h" @@ -79,6 +81,7 @@ void Recover::on_usePassphrase_clicked() bool Recover::askRecover(bool& newWallet) { +#ifdef ENABLE_WALLET namespace fs = boost::filesystem; fs::path walletFile = GetDataDir(true) / GetArg("-wallet", DEFAULT_WALLET_DAT); @@ -167,5 +170,6 @@ bool Recover::askRecover(bool& newWallet) } } } +#endif // ENABLE_WALLET return true; } \ No newline at end of file diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 280faeb98e..582e585fce 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -9,9 +9,10 @@ #include "walletmodeltransaction.h" #include "support/allocators/secure.h" - +#ifdef ENABLE_WALLET #include "wallet/walletdb.h" #include "wallet/wallet.h" +#endif // ENABLE_WALLET #include "wallet/coincontrol.h" #include diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index 9aa8615d13..30dbcba27c 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -14,8 +14,9 @@ #include "util.h" #include "utilmoneystr.h" #include "txmempool.h" - +#ifdef ENABLE_WALLET #include "wallet/wallet.h" +#endif // ENABLE_WALLET #include "wallet/rpcwallet.h" #include "wallet/coincontrol.h" diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 6c0c3cac9d..9c52e50a05 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -19,6 +19,7 @@ #include "wallet/wallet.h" #include "wallet/walletdb.h" #endif +#include "sigma.h" #include "txdb.h" #include "masternode-sync.h" diff --git a/src/rpc/rpcevo.cpp b/src/rpc/rpcevo.cpp index a46264548a..14cd7d9dcb 100644 --- a/src/rpc/rpcevo.cpp +++ b/src/rpc/rpcevo.cpp @@ -12,11 +12,11 @@ #include "validation.h" #ifdef ENABLE_WALLET -#include "wallet/coincontrol.h" #include "wallet/wallet.h" #include "wallet/rpcwallet.h" #endif//ENABLE_WALLET +#include "wallet/coincontrol.h" #include "netbase.h" #include "evo/specialtx.h" @@ -1297,6 +1297,7 @@ UniValue spork(const JSONRPCRequest& request) else if (request.params.size() != 3) spork_help(); +#ifdef ENABLE_WALLET // create spork CWallet* const pwallet = GetWalletForJSONRPCRequest(request); CKey secretKey = ParsePrivKey(pwallet, request.params[0].get_str(), true); @@ -1305,12 +1306,12 @@ UniValue spork(const JSONRPCRequest& request) if (!CBitcoinAddress(Params().GetConsensus().evoSporkKeyID).GetKeyID(publicKeyID) || secretKey.GetPubKey().GetID() != publicKeyID) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "incorrect spork secret key"); } +#endif // ENABLE_WALLET CBitcoinAddress feeAddress(request.params[1].get_str()); if (!feeAddress.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("invalid payout address: %s", request.params[1].get_str())); } - CSporkTx sporkTx; UniValue sporkEnableOrDisableObj = request.params[2].get_obj(); std::vector enableOrDisableKeys = sporkEnableOrDisableObj.getKeys(); @@ -1418,12 +1419,13 @@ UniValue spork(const JSONRPCRequest& request) // make sure fee calculation works correctly sporkTx.vchSig.resize(65); - +#ifdef ENABLE_WALLET FundSpecialTx(pwallet, tx, sporkTx, feeAddress.Get()); SignSpecialTxPayloadByHash(tx, sporkTx, secretKey); SetTxPayload(tx, sporkTx); return SignAndSendSpecialTx(tx); +#endif // ENABLE_WALLET } static const CRPCCommand commands[] = diff --git a/src/sigma.cpp b/src/sigma.cpp index 1958c19e02..ae32d83b3a 100644 --- a/src/sigma.cpp +++ b/src/sigma.cpp @@ -6,8 +6,10 @@ #include "base58.h" #include "definition.h" #include "txmempool.h" +#ifdef ENABLE_WALLET #include "wallet/wallet.h" #include "wallet/walletdb.h" +#endif // ENABLE_WALLET #include "crypto/sha256.h" #include "sigma/coinspend.h" #include "sigma/coin.h" diff --git a/src/validation.cpp b/src/validation.cpp index b8edbf0320..aad1ea69f1 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -37,8 +37,10 @@ #include "ui_interface.h" #include "undo.h" #include "util.h" +#ifdef ENABLE_WALLET #include "wallet/wallet.h" #include "wallet/walletdb.h" +#endif // ENABLE_WALLET #include "batchproof_container.h" #include "sigma.h" #include "lelantus.h" diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 6ea15a2ef8..3f147fbfa0 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -17,7 +17,9 @@ #include "script/ismine.h" #include "script/sign.h" #include "wallet/crypter.h" +#ifdef ENABLE_WALLET #include "wallet/walletdb.h" +#endif // ENABLE_WALLET #include "wallet/rpcwallet.h" #include "wallet/mnemoniccontainer.h" #include "../spark/sparkwallet.h" From 4a91cbb46f3382e0cf9dc36091c1b1b0f2430780 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sat, 28 Oct 2023 05:35:20 +0400 Subject: [PATCH 177/197] Spend remaining sigma coins in lelantustospark rpc --- src/wallet/wallet.cpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6224f1f5ac..a94a56bbb2 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5759,6 +5759,8 @@ CWalletTx CWallet::SpendAndStoreSpark( bool CWallet::LelantusToSpark(std::string& strFailReason) { std::list coins = GetAvailableLelantusCoins(); + std::list sigmaCoins = GetAvailableCoins(); + CScript scriptChange; { // Reserve a new key pair from key pool @@ -5774,21 +5776,29 @@ bool CWallet::LelantusToSpark(std::string& strFailReason) { scriptChange = GetScriptForDestination(vchPubKey.GetID()); } - while (coins.size() > 0) { + while (coins.size() > 0 || sigmaCoins.size() > 0) { bool addMoreCoins = true; std::size_t selectedNum = 0; CCoinControl coinControl; CAmount spendValue = 0; while (true) { - auto coin = coins.begin(); COutPoint outPoint; - lelantus::GetOutPoint(outPoint, coin->value); - coinControl.Select(outPoint); - spendValue += coin->amount; - selectedNum ++; - coins.erase(coin); - if (!coins.size()) - break; + if (sigmaCoins.size() > 0) { + auto coin = sigmaCoins.begin(); + sigma::GetOutPoint(outPoint, coin->value); + coinControl.Select(outPoint); + spendValue += coin->get_denomination_value(); + selectedNum++; + sigmaCoins.erase(coin); + } else if (coins.size() > 0) { + auto coin = coins.begin(); + lelantus::GetOutPoint(outPoint, coin->value); + coinControl.Select(outPoint); + spendValue += coin->amount; + selectedNum++; + coins.erase(coin); + } else + break; if ((spendValue + coins.begin()->amount) > Params().GetConsensus().nMaxValueLelantusSpendPerTransaction) break; From 21586cb42186d4b1ce0ba75c2b9d0467693855f4 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 30 Oct 2023 06:29:27 +0400 Subject: [PATCH 178/197] Fix removetxmempool rpc --- src/wallet/rpcwallet.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 96fd91a14d..884abae511 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4802,9 +4802,9 @@ UniValue removetxmempool(const JSONRPCRequest& request) { LOCK(mempool.cs); if (mempool.exists(hash)) { LogPrintf("[Ooops], Uncomplete function\n"); -// CTransaction tx; -// tx = mempool.lookup(hash); -// mempool.remove(tx); + CTransactionRef tx; + tx = txpools.get(hash); + txpools.removeRecursive(*tx); return NullUniValue; } } From 881ff260d3b459d8b2e6eaa5c089eec35c385d47 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 30 Oct 2023 07:25:32 +0400 Subject: [PATCH 179/197] Fix help strings for mint/spendspark rpc calls --- src/wallet/rpcwallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 884abae511..fe6467969e 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3535,7 +3535,7 @@ UniValue mintspark(const JSONRPCRequest& request) "\nSend two amounts to two different spark addresses:\n" + HelpExampleCli("mintspark", "\"{\\\"sr1xtw3yd6v4ghgz873exv2r5nzfwryufxjzzz4xr48gl4jmh7fxml4568xr0nsdd7s4l5as2h50gakzjqrqpm7yrecne8ut8ylxzygj8klttsgm37tna4jk06acl2azph0dq4yxdqqgwa60\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\"},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"\\\"}}\"") + "\nSend two amounts to two different spark addresses setting memo:\n" - + HelpExampleRpc("mintspark", "\"{\\\"sr1xtw3yd6v4ghgz873exv2r5nzfwryufxjzzz4xr48gl4jmh7fxml4568xr0nsdd7s4l5as2h50gakzjqrqpm7yrecne8ut8ylxzygj8klttsgm37tna4jk06acl2azph0dq4yxdqqgwa60\\\":{\\\"amount\\\":1},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo2\\\"}}\"") + + HelpExampleRpc("mintspark", "\"{\"sr1xtw3yd6v4ghgz873exv2r5nzfwryufxjzzz4xr48gl4jmh7fxml4568xr0nsdd7s4l5as2h50gakzjqrqpm7yrecne8ut8ylxzygj8klttsgm37tna4jk06acl2azph0dq4yxdqqgwa60\":{\"amount\":1},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\":{\"amount\":0.01, \"memo\":\"test_memo2\"}}\"") ); EnsureWalletIsUnlocked(pwallet); EnsureSparkWalletIsAvailable(); @@ -3663,7 +3663,7 @@ UniValue spendspark(const JSONRPCRequest& request) "\nSend an amount to a transparent address and two different private addresses:\n" + HelpExampleCli("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}, \\\"sr1hk87wuh660mss6vnxjf0syt4p6r6ptew97de3dvz698tl7p5p3w7h4m4hcw74mxnqhtz70r7gyydcx6pmkfmnew9q4z0c0muga3sd83h786znjx74ccsjwm284aswppqf2jd0sssendlj\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false}}\"") + "\nSend two amounts to two different transparent addresses and two different private addresses:\n" - + HelpExampleRpc("spendspark", "\"{\\\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": false},\\\"TuzUyNtTznSNnT2rPXG6Mk7hHG8Svuuoci\\\":{\\\"amount\\\":0.01, \\\"subtractFee\\\": true}, \\\"sr1hk87wuh660mss6vnxjf0syt4p6r6ptew97de3dvz698tl7p5p3w7h4m4hcw74mxnqhtz70r7gyydcx6pmkfmnew9q4z0c0muga3sd83h786znjx74ccsjwm284aswppqf2jd0sssendlj\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"\\\", \\\"subtractFee\\\": false},\\\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\\\":{\\\"amount\\\":0.01, \\\"memo\\\":\\\"test_memo\\\", \\\"subtractFee\\\": false}}\"") + + HelpExampleRpc("spendspark", "\"{\"TR1FW48J6ozpRu25U8giSDdTrdXXUYau7U\":{\"amount\":0.01, \"subtractFee\": false},\"TuzUyNtTznSNnT2rPXG6Mk7hHG8Svuuoci\":{\"amount\":0.01, \"subtractFee\": true}, \"sr1hk87wuh660mss6vnxjf0syt4p6r6ptew97de3dvz698tl7p5p3w7h4m4hcw74mxnqhtz70r7gyydcx6pmkfmnew9q4z0c0muga3sd83h786znjx74ccsjwm284aswppqf2jd0sssendlj\":{\"amount\":0.01, \"memo\":\"\", \"subtractFee\": false},\"sr1x7gcqdy670l2v4p9h2m4n5zgzde9y6ht86egffa0qrq40c6z329yfgvu8vyf99tgvnq4hwshvfxxhfzuyvz8dr3lt32j70x8l34japg73ca4w6z9x7c7ryd2gnafg9eg3gpr90gtunraw\":{\"amount\":0.01, \"memo\":\"test_memo\", \"subtractFee\": false}}\"") ); EnsureWalletIsUnlocked(pwallet); From d7843325854073c46fe741856a7ef36a2cbbace4 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 6 Nov 2023 05:39:45 +0400 Subject: [PATCH 180/197] Fix for #1328 --- src/libspark/mint_transaction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libspark/mint_transaction.cpp b/src/libspark/mint_transaction.cpp index 7915ff6050..7acac3cb73 100644 --- a/src/libspark/mint_transaction.cpp +++ b/src/libspark/mint_transaction.cpp @@ -44,7 +44,7 @@ MintTransaction::MintTransaction( } else { Coin coin; coin.type = 0; - coin.r_.ciphertext.resize(32); + coin.r_.ciphertext.resize(82); // max possible size coin.r_.key_commitment.resize(64); coin.r_.tag.resize(16); coin.v = 0; From a9adf577afcb39f5f03ca9d58feaeb069bff973c Mon Sep 17 00:00:00 2001 From: giangnamnabka <82715242+giangnamnabka@users.noreply.github.com> Date: Fri, 10 Nov 2023 07:03:31 +0700 Subject: [PATCH 181/197] Integrate Spark into QT (#1263) * integrate spark * update default of Requested payments history, combine mintspark trans, update nTxFee * update spark address book * fix bug for spark address book * add default spark address into spark address book * update migrateLelantusToSpark * fix spendspark crash * update typo * integrate Anonymize All * merge remote branch firo * fix migrateLelantusToSpark * remove close X of the migrate dialog * remove close X of the private dialog * fix the migration issues * fix the migration issues * update for verifying migration * update for verifying migration * update calculating estimated fee of spendspark * fix mintspark/spendspark from an another wallet * fix crash if using the existing wallet * fix bug 1282 & 1288 * Add output spark address & amount * Merge remote-tracking branch 'firo/spark' into spark * remove i * remove addresstype * fix edit spark label, size of address col in transaction history * Version bump * Fix Spark address view on recieverequestdialog * Merge conflict fixed * Review commit resolved * Remove sensitive data from Coin --------- Co-authored-by: levonpetrosyan93 Co-authored-by: levonpetrosyan93 <45027856+levonpetrosyan93@users.noreply.github.com> --- configure.ac | 2 +- src/Makefile.qt.include | 9 +- src/clientversion.h | 2 +- src/libspark/coin.cpp | 4 +- src/qt/addressbookpage.cpp | 222 +++++++------ src/qt/addressbookpage.h | 37 ++- src/qt/addresstablemodel.cpp | 308 ++++++++++++++---- src/qt/addresstablemodel.h | 13 +- src/qt/automintdialog.cpp | 143 +++++++++ src/qt/automintdialog.h | 40 +++ src/qt/automintmodel.cpp | 183 +++++++++++ src/qt/automintmodel.h | 64 ++++ src/qt/automintnotification.cpp | 53 ++++ src/qt/automintnotification.h | 27 ++ src/qt/bitcoin.qrc | 4 + src/qt/bitcoinaddressvalidator.cpp | 20 +- src/qt/bitcoinaddressvalidator.h | 4 + src/qt/coincontroldialog.cpp | 29 +- src/qt/editaddressdialog.cpp | 49 ++- src/qt/editaddressdialog.h | 6 +- src/qt/forms/addressbookpage.ui | 318 ++++++++----------- src/qt/forms/overviewpage.ui | 73 ++++- src/qt/forms/receivecoinsdialog.ui | 86 ++++- src/qt/forms/sendcoinsentry.ui | 56 +++- src/qt/guiutil.cpp | 2 +- src/qt/overviewpage.cpp | 142 ++++++++- src/qt/overviewpage.h | 27 ++ src/qt/receivecoinsdialog.cpp | 92 +++++- src/qt/receivecoinsdialog.h | 36 +++ src/qt/receiverequestdialog.cpp | 14 +- src/qt/receiverequestdialog.h | 1 + src/qt/recentrequeststablemodel.cpp | 14 +- src/qt/recentrequeststablemodel.h | 8 +- src/qt/res/icons/ic_info.png | Bin 0 -> 1165 bytes src/qt/res/icons/ic_warning.png | Bin 0 -> 709 bytes src/qt/res/icons/refresh.png | Bin 0 -> 20147 bytes src/qt/res/icons/spark.png | Bin 0 -> 595 bytes src/qt/sendcoinsdialog.cpp | 290 +++++++++++++++-- src/qt/sendcoinsdialog.h | 16 + src/qt/sendcoinsentry.cpp | 17 +- src/qt/sendcoinsentry.h | 3 +- src/qt/sparkmodel.cpp | 135 ++++++++ src/qt/sparkmodel.h | 56 ++++ src/qt/transactiondesc.cpp | 61 +++- src/qt/transactionrecord.cpp | 69 +++- src/qt/transactionrecord.h | 5 + src/qt/transactiontablemodel.cpp | 21 ++ src/qt/transactionview.cpp | 15 +- src/qt/transactionview.h | 4 +- src/qt/walletmodel.cpp | 475 +++++++++++++++++++++++++++- src/qt/walletmodel.h | 31 +- src/qt/walletmodeltransaction.cpp | 40 ++- src/qt/walletmodeltransaction.h | 1 + src/qt/walletview.cpp | 88 +++++- src/qt/walletview.h | 12 + src/script/script.cpp | 2 +- src/spark/primitives.h | 25 ++ src/spark/sparkwallet.cpp | 43 ++- src/spark/sparkwallet.h | 2 + src/wallet/rpcwallet.cpp | 3 + src/wallet/wallet.cpp | 272 +++++++++++++++- src/wallet/wallet.h | 36 ++- src/wallet/walletdb.cpp | 30 +- src/wallet/walletdb.h | 2 + 64 files changed, 3343 insertions(+), 499 deletions(-) create mode 100644 src/qt/res/icons/ic_info.png create mode 100644 src/qt/res/icons/ic_warning.png create mode 100644 src/qt/res/icons/refresh.png create mode 100644 src/qt/res/icons/spark.png create mode 100644 src/qt/sparkmodel.cpp create mode 100644 src/qt/sparkmodel.h diff --git a/configure.ac b/configure.ac index dfbdddd117..67e868bcc4 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 14) define(_CLIENT_VERSION_REVISION, 12) -define(_CLIENT_VERSION_BUILD, 4) +define(_CLIENT_VERSION_BUILD, 5) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2023) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 9e0690792e..93b1b4501a 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -200,7 +200,8 @@ QT_MOC_CPP = \ qt/moc_lelantuscoincontroldialog.cpp \ qt/moc_automintmodel.cpp \ qt/moc_automintnotification.cpp \ - qt/moc_pcodemodel.cpp + qt/moc_pcodemodel.cpp \ + qt/moc_sparkmodel.cpp BITCOIN_MM = \ qt/macdockiconhandler.mm \ @@ -298,7 +299,8 @@ BITCOIN_QT_H = \ qt/lelantusmodel.h \ qt/lelantusdialog.h \ qt/lelantuscoincontroldialog.h \ - qt/automintmodel.h + qt/automintmodel.h \ + qt/sparkmodel.h RES_ICONS = \ qt/res/icons/add.png \ @@ -491,7 +493,8 @@ BITCOIN_QT_WALLET_CPP = \ qt/lelantusmodel.cpp \ qt/lelantusdialog.cpp \ qt/lelantuscoincontroldialog.cpp \ - qt/automintmodel.cpp + qt/automintmodel.cpp \ + qt/sparkmodel.cpp FIRO_QT_ELYSIUM_CPP = \ qt/elyassetsdialog.cpp \ diff --git a/src/clientversion.h b/src/clientversion.h index 5169291121..7b7cab614c 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -17,7 +17,7 @@ #define CLIENT_VERSION_MAJOR 0 #define CLIENT_VERSION_MINOR 14 #define CLIENT_VERSION_REVISION 12 -#define CLIENT_VERSION_BUILD 4 +#define CLIENT_VERSION_BUILD 5 //! Set to true for release, false for prerelease or test build #define CLIENT_VERSION_IS_RELEASE true diff --git a/src/libspark/coin.cpp b/src/libspark/coin.cpp index 05f271bfb8..785cc1de90 100644 --- a/src/libspark/coin.cpp +++ b/src/libspark/coin.cpp @@ -56,9 +56,9 @@ Coin::Coin( // Type-specific elements // - if (this->type == COIN_TYPE_MINT) { - this->v = v; + if (this->type == COIN_TYPE_MINT) { + this->v = v; // Encrypt recipient data MintCoinRecipientData r; r.d = address.get_d(); diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 34c2d80330..7ba41d25e6 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -23,7 +23,7 @@ #include #include -AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, Tabs _tab, QWidget *parent) : +AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, Tabs _tab, QWidget *parent, bool isReused) : QDialog(parent), ui(new Ui::AddressBookPage), model(0), @@ -32,6 +32,19 @@ AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, { ui->setupUi(this); + if (tab == SendingTab) { + ui->addressType->addItem(tr("Spark"), Spark); + ui->addressType->addItem(tr("Transparent"), Transparent); + ui->addressType->addItem(tr("RAP"), RAP); + } else if(tab == ReceivingTab && !isReused) { + ui->addressType->addItem(tr("Spark"), Spark); + ui->addressType->addItem(tr("Transparent"), Transparent); + } else { + ui->addressType->addItem(tr(""), Transparent); + ui->addressType->addItem(tr("Transparent"), Transparent); + ui->addressType->hide(); + } + if (!platformStyle->getImagesOnButtons()) { ui->newAddress->setIcon(QIcon()); ui->copyAddress->setIcon(QIcon()); @@ -53,7 +66,6 @@ AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, case ReceivingTab: setWindowTitle(tr("Choose the address to receive coins with")); break; } connect(ui->tableView, &QTableView::doubleClicked, this, &QDialog::accept); - connect(ui->tableViewPcodes, &QTableView::doubleClicked, this, &QDialog::accept); ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->tableView->setFocus(); ui->closeButton->setText(tr("C&hoose")); @@ -72,14 +84,10 @@ AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, case SendingTab: ui->labelExplanation->setText(tr("These are your Firo addresses for sending payments. Always check the amount and the receiving address before sending coins.")); ui->deleteAddress->setVisible(true); - connect(ui->tabWidget, &QTabWidget::currentChanged, this, &AddressBookPage::selectionChanged); - connect(ui->tableViewPcodes, &QWidget::customContextMenuRequested, this, &AddressBookPage::contextualMenu); break; case ReceivingTab: ui->labelExplanation->setText(tr("These are your Firo addresses for receiving payments. It is recommended to use a new receiving address for each transaction.")); ui->deleteAddress->setVisible(false); - ui->tabWidget->removeTab(1); //RAP Pcodes tab - ui->tabWidget->tabBar()->setVisible(false); break; } @@ -121,11 +129,8 @@ void AddressBookPage::setModel(AddressTableModel *_model) return; proxyModel = new QSortFilterProxyModel(this); - proxyModel->setSourceModel(_model); - proxyModel->setDynamicSortFilter(true); - proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); - proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); - + fproxyModel = new AddressBookFilterProxy(this); + proxyModel->setSourceModel(model); switch(tab) { case ReceivingTab: @@ -137,59 +142,46 @@ void AddressBookPage::setModel(AddressTableModel *_model) // Send filter proxyModel->setFilterRole(AddressTableModel::TypeRole); proxyModel->setFilterFixedString(AddressTableModel::Send); - - proxyModelPcode = new QSortFilterProxyModel(this); - proxyModelPcode->setSourceModel(_model->getPcodeAddressTableModel()); - proxyModelPcode->setDynamicSortFilter(true); - proxyModelPcode->setSortCaseSensitivity(Qt::CaseInsensitive); - proxyModelPcode->setFilterCaseSensitivity(Qt::CaseInsensitive); - ui->tableViewPcodes->setModel(proxyModelPcode); - ui->tableViewPcodes->sortByColumn(0, Qt::AscendingOrder); - connect(ui->tableViewPcodes->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AddressBookPage::selectionChanged); - -#if QT_VERSION < 0x050000 - ui->tableViewPcodes->horizontalHeader()->setResizeMode(AddressTableModel::Label, QHeaderView::Stretch); - ui->tableViewPcodes->horizontalHeader()->setResizeMode(AddressTableModel::Address, QHeaderView::ResizeToContents); -#else - ui->tableViewPcodes->horizontalHeader()->setSectionResizeMode(AddressTableModel::Label, QHeaderView::Stretch); - ui->tableViewPcodes->horizontalHeader()->setSectionResizeMode(AddressTableModel::Address, QHeaderView::ResizeToContents); -#endif break; } - ui->tableView->setModel(proxyModel); - ui->tableView->sortByColumn(0, Qt::AscendingOrder); - + proxyModel->setDynamicSortFilter(true); + proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + + fproxyModel->setSourceModel(proxyModel); + fproxyModel->setDynamicSortFilter(true); + fproxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + fproxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + ui->tableView->setModel(fproxyModel); // Set column widths -#if QT_VERSION < 0x050000 - ui->tableView->horizontalHeader()->setResizeMode(AddressTableModel::Label, QHeaderView::Stretch); - ui->tableView->horizontalHeader()->setResizeMode(AddressTableModel::Address, QHeaderView::ResizeToContents); -#else - ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Label, QHeaderView::Stretch); - ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Address, QHeaderView::ResizeToContents); -#endif - + #if QT_VERSION < 0x050000 + ui->tableView->horizontalHeader()->setResizeMode(AddressTableModel::Label, QHeaderView::Stretch); + ui->tableView->horizontalHeader()->setResizeMode(AddressTableModel::Address, QHeaderView::Stretch); + ui->tableView->horizontalHeader()->setResizeMode(AddressTableModel::AddressType, QHeaderView::Stretch); + #else + ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Label, QHeaderView::Stretch); + ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Address, QHeaderView::Stretch); + ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::AddressType, QHeaderView::Stretch); + #endif + ui->tableView->setTextElideMode(Qt::ElideMiddle); connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AddressBookPage::selectionChanged); // Select row for newly created address - connect(_model, &AddressTableModel::rowsInserted, this, &AddressBookPage::selectNewAddress); + connect(model, &AddressTableModel::rowsInserted, this, &AddressBookPage::selectNewAddress); selectionChanged(); + chooseAddressType(0); + connect(ui->addressType, qOverload(&QComboBox::activated), this, &AddressBookPage::chooseAddressType); } void AddressBookPage::on_copyAddress_clicked() { - if(ui->tabWidget->currentWidget() == ui->tabAddresses) - GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Address); - else - GUIUtil::copyEntryData(ui->tableViewPcodes, AddressTableModel::Address); + GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Address); } void AddressBookPage::onCopyLabelAction() { - if(ui->tabWidget->currentWidget() == ui->tabAddresses) - GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Label); - else - GUIUtil::copyEntryData(ui->tableViewPcodes, AddressTableModel::Label); + GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Label); } void AddressBookPage::onEditAction() @@ -198,32 +190,27 @@ void AddressBookPage::onEditAction() EditAddressDialog::Mode mode; AddressTableModel * pmodel; - QSortFilterProxyModel *pproxyModel; - if(ui->tabWidget->currentWidget() == ui->tabAddresses) - { - mode = tab == SendingTab ? EditAddressDialog::EditSendingAddress : EditAddressDialog::EditReceivingAddress; - pmodel = model; - pproxyModel = proxyModel; - if(!ui->tableView->selectionModel()) - return; - indexes = ui->tableView->selectionModel()->selectedRows(); - } - else - { + pmodel = model; + if (ui->addressType->currentText() == AddressTableModel::RAP) { mode = EditAddressDialog::EditPcode; - pmodel = model->getPcodeAddressTableModel(); - pproxyModel = proxyModelPcode; - if(!ui->tableViewPcodes->selectionModel()) - return; - indexes = ui->tableViewPcodes->selectionModel()->selectedRows(); + } else if (ui->addressType->currentText() == AddressTableModel::Transparent) { + mode = tab == SendingTab ? EditAddressDialog::EditSendingAddress : EditAddressDialog::EditReceivingAddress; + } else { + mode = tab == SendingTab ? EditAddressDialog::EditSparkSendingAddress : EditAddressDialog::EditSparkReceivingAddress; } - if(!pmodel || indexes.isEmpty()) + + if (!ui->tableView->selectionModel()) + return; + indexes = ui->tableView->selectionModel()->selectedRows(); + if (!pmodel || indexes.isEmpty()) return; EditAddressDialog dlg(mode, this); dlg.setModel(pmodel); - QModelIndex origIndex = pproxyModel->mapToSource(indexes.at(0)); - dlg.loadRow(origIndex.row()); + QModelIndex origIndex1, origIndex2; + origIndex1 = fproxyModel->mapToSource(indexes.at(0)); + origIndex2 = proxyModel->mapToSource(origIndex1); + dlg.loadRow(origIndex2.row()); dlg.exec(); } @@ -234,15 +221,13 @@ void AddressBookPage::on_newAddress_clicked() AddressTableModel *pmodel; EditAddressDialog::Mode mode; - if(ui->tabWidget->currentWidget() == ui->tabAddresses) - { - pmodel = model; - mode = tab == SendingTab ? EditAddressDialog::NewSendingAddress : EditAddressDialog::NewReceivingAddress; - } - else - { - pmodel = model->getPcodeAddressTableModel(); + pmodel = model; + if (ui->addressType->currentText() == AddressTableModel::Spark) { + mode = tab == SendingTab ? EditAddressDialog::NewSparkSendingAddress : EditAddressDialog::NewSparkReceivingAddress; + } else if (ui->addressType->currentText() == AddressTableModel::RAP) { mode = EditAddressDialog::NewPcode; + } else { + mode = tab == SendingTab ? EditAddressDialog::NewSendingAddress : EditAddressDialog::NewReceivingAddress; } EditAddressDialog dlg(mode, this); @@ -256,15 +241,13 @@ void AddressBookPage::on_newAddress_clicked() void AddressBookPage::on_deleteAddress_clicked() { QTableView *table; - if(ui->tabWidget->currentWidget() == ui->tabAddresses) - table = ui->tableView; - else - table = ui->tableViewPcodes; + table = ui->tableView; if(!table->selectionModel()) return; QModelIndexList indexes = table->selectionModel()->selectedRows(); + if(!indexes.isEmpty()) { table->model()->removeRow(indexes.at(0).row()); @@ -275,10 +258,7 @@ void AddressBookPage::selectionChanged() { // Set button states based on selected tab and selection QTableView *table; - if(ui->tabWidget->currentWidget() == ui->tabAddresses) - table = ui->tableView; - else - table = ui->tableViewPcodes; + table = ui->tableView; if(!table->selectionModel()) return; @@ -300,6 +280,7 @@ void AddressBookPage::selectionChanged() deleteAction->setEnabled(false); break; } + ui->copyAddress->setEnabled(true); } else @@ -312,10 +293,7 @@ void AddressBookPage::selectionChanged() void AddressBookPage::done(int retval) { QTableView *table; - if(ui->tabWidget->currentWidget() == ui->tabAddresses) - table = ui->tableView; - else - table = ui->tableViewPcodes; + table = ui->tableView; if(!table->selectionModel() || !table->model()) return; @@ -350,17 +328,19 @@ void AddressBookPage::on_exportButton_clicked() CSVModelWriter writer(filename); QTableView *table; - if(ui->tabWidget->currentWidget() == ui->tabAddresses) - { - writer.setModel(proxyModel); + writer.setModel(proxyModel); + if (ui->addressType->currentText() == AddressTableModel::Transparent) { writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole); - writer.addColumn("Address", AddressTableModel::Address, Qt::EditRole); - } - else - { - writer.setModel(proxyModelPcode); + writer.addColumn("Transparent Address", AddressTableModel::Address, Qt::EditRole); + writer.addColumn("Address Type", AddressTableModel::AddressType, Qt::EditRole); + } else if (ui->addressType->currentText() == AddressTableModel::RAP) { writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole); writer.addColumn("PaymentCode", AddressTableModel::Address, Qt::EditRole); + writer.addColumn("Address Type", AddressTableModel::AddressType, Qt::EditRole); + } else { + writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole); + writer.addColumn("Spark Address", AddressTableModel::Address, Qt::EditRole); + writer.addColumn("Address Type", AddressTableModel::AddressType, Qt::EditRole); } if(!writer.write()) { @@ -372,15 +352,14 @@ void AddressBookPage::on_exportButton_clicked() void AddressBookPage::contextualMenu(const QPoint &point) { QModelIndex index; - if(ui->tabWidget->currentWidget() == ui->tabAddresses) - { - index = ui->tableView->indexAt(point); - copyAddressAction->setText(tr("&Copy Address")); - } - else - { - index = ui->tableViewPcodes->indexAt(point); + index = ui->tableView->indexAt(point); + + if (ui->addressType->currentText() == "Spark") { + copyAddressAction->setText(tr("&Copy Spark Address")); + } else if (ui->addressType->currentText() == "RAP") { copyAddressAction->setText(tr("&Copy RAP address")); + } else { + copyAddressAction->setText(tr("&Copy Transparent Address")); } if(index.isValid()) { @@ -399,3 +378,38 @@ void AddressBookPage::selectNewAddress(const QModelIndex &parent, int begin, int newAddressToSelect.clear(); } } + +void AddressBookPage::chooseAddressType(int idx) +{ + if(!proxyModel) + return; + fproxyModel->setTypeFilter( + ui->addressType->itemData(idx).toInt()); +} + +AddressBookFilterProxy::AddressBookFilterProxy(QObject *parent) : + QSortFilterProxyModel(parent) +{ +} + +bool AddressBookFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex index = sourceModel()->index(sourceRow, 2, sourceParent); + bool res0 = sourceModel()->data(index).toString().contains("spark"); + bool res1 = sourceModel()->data(index).toString().contains("transparent"); + bool res2 = sourceModel()->data(index).toString().contains("RAP"); + + if(res0 && typeFilter == 0) + return true; + if(res1 && typeFilter == 1) + return true; + if(res2 && typeFilter == 2) + return true; + return false; +} + +void AddressBookFilterProxy::setTypeFilter(quint32 modes) +{ + this->typeFilter = modes; + invalidateFilter(); +} diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index 0a209cf47c..e89d1402f8 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -6,10 +6,12 @@ #define BITCOIN_QT_ADDRESSBOOKPAGE_H #include +#include class AddressTableModel; class OptionsModel; class PlatformStyle; +class AddressBookFilterProxy; namespace Ui { class AddressBookPage; @@ -40,7 +42,14 @@ class AddressBookPage : public QDialog ForEditing /**< Open address book for editing */ }; - explicit AddressBookPage(const PlatformStyle *platformStyle, Mode mode, Tabs tab, QWidget *parent); + enum AddressTypeEnum + { + Spark, + Transparent, + RAP + }; + + explicit AddressBookPage(const PlatformStyle *platformStyle, Mode mode, Tabs tab, QWidget *parent, bool isReused = true); ~AddressBookPage(); void setModel(AddressTableModel *model); @@ -55,7 +64,8 @@ public Q_SLOTS: Mode mode; Tabs tab; QString returnValue; - QSortFilterProxyModel *proxyModel, *proxyModelPcode; + QSortFilterProxyModel *proxyModel; + AddressBookFilterProxy *fproxyModel; QMenu *contextMenu; QAction *copyAddressAction; QAction *deleteAction; // to be able to explicitly disable it @@ -82,8 +92,31 @@ private Q_SLOTS: /** New entry/entries were added to address table */ void selectNewAddress(const QModelIndex &parent, int begin, int /*end*/); + void chooseAddressType(int idx); + Q_SIGNALS: void sendCoins(QString addr); }; +class AddressBookFilterProxy : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + explicit AddressBookFilterProxy(QObject *parent = 0); + + // static const quint32 RECEIVE_TYPE = 0xFFFFFFFF; + static const quint32 RECEIVE_TYPE = 8; + + static quint32 TYPE(int type) { return 1<