From bb4b3fe66fe2f654bd841cb862993760b8d43928 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 2 Oct 2023 13:30:44 +0400 Subject: [PATCH 1/4] Lelantus tx ownership proof --- src/rpc/misc.cpp | 38 ++++++++++++++++++++++++ src/validation.cpp | 64 ++++++++++++++++++++++++++++++++++++++++ src/validation.h | 4 +++ src/wallet/rpcwallet.cpp | 47 +++++++++++++++++++++++++++++ src/wallet/wallet.cpp | 57 +++++++++++++++++++++++++++++++++++ src/wallet/wallet.h | 2 ++ 6 files changed, 212 insertions(+) diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 555733bd35..b23c3a5908 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -487,6 +487,42 @@ UniValue signmessagewithprivkey(const JSONRPCRequest& request) return EncodeBase64(&vchSig[0], vchSig.size()); } +UniValue verifyprivatetxown(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 3) + throw std::runtime_error( + "verifyrivatetxown \"txid\" \"signature\" \"message\"\n" + "\nVerify a lelantus tx ownership\n" + "\nArguments:\n" + "1. \"txid\" (string, required) Txid, in which we spend lelantus coins.\n" + "2. \"proof\" (string, required) The signatures of the message encoded in base 64\n" + "3. \"message\" (string, required) The message that was signed.\n" + "\nResult:\n" + "true|false (boolean) If the signature is verified or not.\n" + "\nExamples:\n" + "\nVerify the signature\n" + + HelpExampleCli("verifyprivatetxown", "\"34df0ec7bcc8a2bda2c0df41ac560172d974c56ffc9adc0e2377d0fc54b4e8f9\" \"signature\" \"my message\"") + + "\nAs json rpc\n" + + HelpExampleRpc("verifyprivatetxown", "\"34df0ec7bcc8a2bda2c0df41ac560172d974c56ffc9adc0e2377d0fc54b4e8f9\", \"signature\", \"my message\"") + ); + + LOCK(cs_main); + + std::string strTxId = request.params[0].get_str(); + std::string strProof = request.params[1].get_str(); + std::string strMessage = request.params[2].get_str(); + + uint256 txid = uint256S(strTxId); + bool fInvalid = false; + std::vector vchSig = DecodeBase64(strProof.c_str(), &fInvalid); + + if (fInvalid) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding"); + + return VerifyPrivateTxOwn(txid, vchSig, strMessage); +} + + UniValue setmocktime(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 1) @@ -1380,6 +1416,8 @@ static const CRPCCommand commands[] = { "firo", "znsync", &mnsync, true, {} }, { "firo", "evoznsync", &mnsync, true, {} }, + { "firo", "verifyprivatetxown", &verifyprivatetxown, true, {} }, + /* Not shown in help */ { "hidden", "getinfoex", &getinfoex, false }, { "addressindex", "gettotalsupply", &gettotalsupply, false }, diff --git a/src/validation.cpp b/src/validation.cpp index a84c548d30..31fec4075f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -139,6 +139,7 @@ static void CheckBlockIndex(const Consensus::Params& consensusParams); CScript COINBASE_FLAGS; const std::string strMessageMagic = "Zcoin Signed Message:\n"; +const std::string strLelantusMessageMagic = "Lelantus signed Message:\n"; // Internal stuff namespace { @@ -331,6 +332,69 @@ bool CheckFinalTx(const CTransaction &tx, int flags) return IsFinalTx(tx, nBlockHeight, nBlockTime); } +bool VerifyPrivateTxOwn(const uint256& txid, const std::vector& vchSig, const std::string& message) +{ + CTransactionRef tx; + uint256 hashBlock; + if(!GetTransaction(txid, tx, Params().GetConsensus(), hashBlock, true)) + return false; + + if (tx->IsLelantusJoinSplit()) { + CHashWriter ss(SER_GETHASH, 0); + ss << strLelantusMessageMagic; + ss << message; + + std::unique_ptr joinsplit; + try { + joinsplit = lelantus::ParseLelantusJoinSplit(*tx); + } catch (const std::exception&) { + return false; + } + const auto& pubKeys = joinsplit->GetEcdsaPubkeys(); + + if((pubKeys.size() *64) != vchSig.size()) { + LogPrintf("Verification to serialNumbers and ecdsaSignatures/ecdsaPubkeys number mismatch."); + return false; + } + + size_t count = 0; + + for (const auto& pub : pubKeys) { + ss << count; + uint256 metahash = ss.GetHash(); + + // Check sizes + if (pub.size() != 33 ) { + LogPrintf("Verification failed due to incorrect size of ecdsaSignature."); + return false; + } + + // Verify signature + secp256k1_pubkey pubkey; + secp256k1_ecdsa_signature signature; + + if (!secp256k1_ec_pubkey_parse(OpenSSLContext::get_context(), &pubkey, pub.data(), 33)) { + LogPrintf("Verification failed due to unable to parse ecdsaPubkey."); + return false; + } + + if (1 != secp256k1_ecdsa_signature_parse_compact(OpenSSLContext::get_context(), &signature, &vchSig[count * 64]) ) { + LogPrintf("Verification failed due to signature cannot be parsed."); + return false; + } + + if (!secp256k1_ecdsa_verify( + OpenSSLContext::get_context(), &signature, metahash.begin(), &pubkey)) { + LogPrintf("Verification failed due to signature cannot be verified."); + return false; + } + + count++; + } + } + return true; +} + /** * Calculates the block height and previous block's median time past at * which the transaction will be considered final in the context of BIP 68. diff --git a/src/validation.h b/src/validation.h index e8e8be17fa..e7cbd85bfe 100644 --- a/src/validation.h +++ b/src/validation.h @@ -189,6 +189,7 @@ extern uint64_t nLastBlockTx; extern uint64_t nLastBlockSize; extern uint64_t nLastBlockWeight; extern const std::string strMessageMagic; +extern const std::string strLelantusMessageMagic; extern CWaitableCriticalSection csBestBlock; extern CConditionVariable cvBlockChange; extern std::atomic_bool fImporting; @@ -456,6 +457,9 @@ bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime); */ bool CheckFinalTx(const CTransaction &tx, int flags = -1); + +bool VerifyPrivateTxOwn(const uint256& txid, const std::vector& vchSig, const std::string& message); + /** * Test whether the LockPoints height and time are still valid on the current chain */ diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 6904f0e71f..6b0523c6e8 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -673,6 +673,52 @@ UniValue signmessage(const JSONRPCRequest& request) return EncodeBase64(&vchSig[0], vchSig.size()); } +UniValue proveprivatetxown(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( + "proveprivatetxown \"txid\" \"message\"\n" + "\nCreated a proof by signing the message with private key of each spent coin." + + HelpRequiringPassphrase(pwallet) + "\n" + "\nArguments:\n" + "1. \"strTxId\" (string, required) Txid, in which we spend lelantus coins.\n" + "2. \"message\" (string, required) The message to create a signature of.\n" + "\nResult:\n" + "\"proof\" (string) The signatures of the message encoded in base 64\n" + "\nExamples:\n" + "\nUnlock the wallet for 30 seconds\n" + + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + + "\nCreate the signature\n" + + HelpExampleCli("proveprivatetxown", "\"34df0ec7bcc8a2bda2c0df41ac560172d974c56ffc9adc0e2377d0fc54b4e8f9 \" \"my message\"") + + "\nVerify the signature\n" + + HelpExampleCli("verifyprivatetxown", "\"34df0ec7bcc8a2bda2c0df41ac560172d974c56ffc9adc0e2377d0fc54b4e8f9 \" \"proof\" \"my message\"") + + "\nAs json rpc\n" + + HelpExampleRpc("proveprivatetxown", "\"34df0ec7bcc8a2bda2c0df41ac560172d974c56ffc9adc0e2377d0fc54b4e8f9 \", \"my message\"") + ); + + EnsureLelantusWalletIsAvailable(); + + LOCK2(cs_main, pwallet->cs_wallet); + EnsureWalletIsUnlocked(pwallet); + + std::string strTxId = request.params[0].get_str(); + std::string strMessage = request.params[1].get_str(); + + uint256 txid = uint256S(strTxId); + std::vector vchSig = pwallet->ProvePrivateTxOwn(txid, strMessage); + + if (vchSig.empty()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Something went wrong, may be you are not the owner of provided tx"); + + return EncodeBase64(&vchSig[0], vchSig.size()); +} + + UniValue getreceivedbyaddress(const JSONRPCRequest& request) { CWallet * const pwallet = GetWalletForJSONRPCRequest(request); @@ -4871,6 +4917,7 @@ static const CRPCCommand commands[] = { "wallet", "setaccount", &setaccount, true, {"address","account"} }, { "wallet", "settxfee", &settxfee, true, {"amount"} }, { "wallet", "signmessage", &signmessage, true, {"address","message"} }, + { "wallet", "proveprivatetxown", &proveprivatetxown, true, {"txid","message"} }, { "wallet", "walletlock", &walletlock, true, {} }, { "wallet", "walletpassphrasechange", &walletpassphrasechange, true, {"oldpassphrase","newpassphrase"} }, { "wallet", "walletpassphrase", &walletpassphrase, true, {"passphrase","timeout"} }, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 99cfff5029..89be3669d5 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3344,6 +3344,63 @@ bool CWallet::GetCoinsToJoinSplit( return true; } +std::vector CWallet::ProvePrivateTxOwn(const uint256& txid, const std::string& message) const { + std::vector result; + if (!mapWallet.count(txid)) + return result; + + const CWalletTx& wtx = mapWallet.at(txid); + + if (wtx.tx->IsLelantusJoinSplit()) { + CHashWriter ss(SER_GETHASH, 0); + ss << strLelantusMessageMagic; + ss << message; + + std::unique_ptr joinsplit; + try { + joinsplit = lelantus::ParseLelantusJoinSplit(*wtx.tx); + } catch (const std::exception&) { + return result; + } + const auto& serials = joinsplit->getCoinSerialNumbers(); + size_t count = 0; + result.resize(serials.size() * 64); + + for (const auto& serial : serials) { + CLelantusEntry mint; + uint256 hashSerial = primitives::GetSerialHash(serial); + std::vector ecdsaSecretKey; + if (!GetMint(hashSerial, mint, false)) { + CSigmaEntry sigmaMint; + if (!GetMint(hashSerial, mint, false)) { + return std::vector(); + } + ecdsaSecretKey = sigmaMint.ecdsaSecretKey; + } else { + ecdsaSecretKey = mint.ecdsaSecretKey; + + } + + ss << count; + uint256 metahash = ss.GetHash(); + secp256k1_ecdsa_signature sig; + if (1 != secp256k1_ecdsa_sign( + OpenSSLContext::get_context(), &sig, + metahash.begin(), &ecdsaSecretKey[0], NULL, NULL)) { + return std::vector(); + } + if (1 != secp256k1_ecdsa_signature_serialize_compact( + OpenSSLContext::get_context(), &result[count * 64], &sig)) { + return std::vector(); + } + + count++; + } + } + + return result; +} + CAmount CWallet::GetUnconfirmedBalance() const { CAmount nTotal = 0; { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 8d1a6284ea..200e7611f8 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -980,6 +980,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface const CAmount amountToSpendLimit = MAX_MONEY, const CCoinControl *coinControl = NULL) const; + std::vector ProvePrivateTxOwn(const uint256& txid, const std::string& message) const; + /** * Insert additional inputs into the transaction by * calling CreateTransaction(); From 99dcc973c09f1a812850c85c671d8f5592071664 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 9 Oct 2023 07:17:57 +0400 Subject: [PATCH 2/4] Better error messages added --- src/validation.cpp | 5 +++++ src/wallet/wallet.cpp | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/validation.cpp b/src/validation.cpp index 31fec4075f..692c9b3340 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -391,7 +391,12 @@ bool VerifyPrivateTxOwn(const uint256& txid, const std::vector& v count++; } + } else if (tx->IsCoinBase()) { + throw std::runtime_error("This is a coinbase transaction and not a private transaction"); + } else { + throw std::runtime_error("Currently this is allowed only for Lelantus transactions"); } + return true; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 89be3669d5..697c7a20bf 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3396,6 +3396,10 @@ std::vector CWallet::ProvePrivateTxOwn(const uint256& txid, const count++; } + } else if (wtx.tx->IsCoinBase()) { + throw std::runtime_error("This is a coinbase transaction and not a private transaction"); + } else { + throw std::runtime_error("Currently this operation is allowed only for Lelantus transactions"); } return result; From 4cb091b0d16dbb3679dc9e54661787388bd68b6a Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Sat, 28 Oct 2023 05:41:48 +0400 Subject: [PATCH 3/4] Typo fix --- 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 b23c3a5908..872d1d7c21 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -491,7 +491,7 @@ UniValue verifyprivatetxown(const JSONRPCRequest& request) { if (request.fHelp || request.params.size() != 3) throw std::runtime_error( - "verifyrivatetxown \"txid\" \"signature\" \"message\"\n" + "verifyprivatetxown \"txid\" \"signature\" \"message\"\n" "\nVerify a lelantus tx ownership\n" "\nArguments:\n" "1. \"txid\" (string, required) Txid, in which we spend lelantus coins.\n" From 6524f8207e975225bab58f4ebef504a1ef677393 Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 Date: Mon, 30 Oct 2023 02:17:23 +0400 Subject: [PATCH 4/4] Mac build fix --- src/validation.cpp | 2 +- src/wallet/wallet.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index 692c9b3340..e6ea80156a 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -357,7 +357,7 @@ bool VerifyPrivateTxOwn(const uint256& txid, const std::vector& v return false; } - size_t count = 0; + uint32_t count = 0; for (const auto& pub : pubKeys) { ss << count; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 697c7a20bf..0462d22ffb 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3363,7 +3363,7 @@ std::vector CWallet::ProvePrivateTxOwn(const uint256& txid, const return result; } const auto& serials = joinsplit->getCoinSerialNumbers(); - size_t count = 0; + uint32_t count = 0; result.resize(serials.size() * 64); for (const auto& serial : serials) {