Skip to content

Commit

Permalink
Lelantus JoinSplit ownership Proof creation/Verification (#1347)
Browse files Browse the repository at this point in the history
* Lelantus tx ownership proof

* Better error messages added

* Typo fix

* Mac build fix
  • Loading branch information
levonpetrosyan93 authored Nov 21, 2023
1 parent 0579884 commit 7154f7f
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 0 deletions.
38 changes: 38 additions & 0 deletions src/rpc/misc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,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 @@ -1630,6 +1666,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 @@ -141,6 +141,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 @@ -333,6 +334,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 @@ -680,6 +680,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 @@ -5519,6 +5565,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 @@ -3580,6 +3580,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 @@ -1006,6 +1006,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

0 comments on commit 7154f7f

Please sign in to comment.