Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lelantus JoinSplit ownership Proof creation/Verification" #1362

Merged
merged 4 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/rpc/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
"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"
"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<unsigned char> 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)
Expand Down Expand Up @@ -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 },
Expand Down
69 changes: 69 additions & 0 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -331,6 +332,74 @@ bool CheckFinalTx(const CTransaction &tx, int flags)
return IsFinalTx(tx, nBlockHeight, nBlockTime);
}

bool VerifyPrivateTxOwn(const uint256& txid, const std::vector<unsigned char>& 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<lelantus::JoinSplit> 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;
}

uint32_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++;
}
} 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;
}

/**
* 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.
Expand Down
4 changes: 4 additions & 0 deletions src/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<unsigned char>& vchSig, const std::string& message);

/**
* Test whether the LockPoints height and time are still valid on the current chain
*/
Expand Down
47 changes: 47 additions & 0 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<unsigned char> 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);
Expand Down Expand Up @@ -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"} },
Expand Down
61 changes: 61 additions & 0 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3344,6 +3344,67 @@ bool CWallet::GetCoinsToJoinSplit(
return true;
}

std::vector<unsigned char> CWallet::ProvePrivateTxOwn(const uint256& txid, const std::string& message) const {
std::vector<unsigned char> 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<lelantus::JoinSplit> joinsplit;
try {
joinsplit = lelantus::ParseLelantusJoinSplit(*wtx.tx);
} catch (const std::exception&) {
return result;
}
const auto& serials = joinsplit->getCoinSerialNumbers();
uint32_t count = 0;
result.resize(serials.size() * 64);

for (const auto& serial : serials) {
CLelantusEntry mint;
uint256 hashSerial = primitives::GetSerialHash(serial);
std::vector<unsigned char> ecdsaSecretKey;
if (!GetMint(hashSerial, mint, false)) {
CSigmaEntry sigmaMint;
if (!GetMint(hashSerial, mint, false)) {
return std::vector<unsigned char>();
}
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<unsigned char>();
}
if (1 != secp256k1_ecdsa_signature_serialize_compact(
OpenSSLContext::get_context(), &result[count * 64], &sig)) {
return std::vector<unsigned char>();
}

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;
}

CAmount CWallet::GetUnconfirmedBalance() const {
CAmount nTotal = 0;
{
Expand Down
2 changes: 2 additions & 0 deletions src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
const CAmount amountToSpendLimit = MAX_MONEY,
const CCoinControl *coinControl = NULL) const;

std::vector<unsigned char> ProvePrivateTxOwn(const uint256& txid, const std::string& message) const;

/**
* Insert additional inputs into the transaction by
* calling CreateTransaction();
Expand Down
Loading