From 32c7c5847b344451a3fb5ee3738cc73ccd57543d Mon Sep 17 00:00:00 2001 From: levonpetrosyan93 <45027856+levonpetrosyan93@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:30:20 +0400 Subject: [PATCH] Preventing double mint in Lelantus (#996) --- src/wallet/lelantusjoinsplitbuilder.cpp | 74 +++++++++++---------- src/wallet/wallet.cpp | 85 ++++++++++++++----------- 2 files changed, 89 insertions(+), 70 deletions(-) diff --git a/src/wallet/lelantusjoinsplitbuilder.cpp b/src/wallet/lelantusjoinsplitbuilder.cpp index 1894822ff1..17e7da2e1f 100644 --- a/src/wallet/lelantusjoinsplitbuilder.cpp +++ b/src/wallet/lelantusjoinsplitbuilder.cpp @@ -351,42 +351,52 @@ void LelantusJoinSplitBuilder::GenerateMints(const std::vector& newMint std::vector newMintsAndChange(newMints); newMintsAndChange.push_back(changeToMint); for (CAmount mintVal : newMintsAndChange) { - hdMint.SetNull(); + while (true) { + hdMint.SetNull(); + lelantus::PrivateCoin newCoin(params, mintVal); + newCoin.setVersion(LELANTUS_TX_VERSION_4); + CWalletDB walletdb(pwalletMain->strWalletFile); - lelantus::PrivateCoin newCoin(params, mintVal); - newCoin.setVersion(LELANTUS_TX_VERSION_4); - CWalletDB walletdb(pwalletMain->strWalletFile); + uint160 seedID; + mintWallet.GenerateLelantusMint(walletdb, newCoin, hdMint, seedID, boost::none, true); - uint160 seedID; - mintWallet.GenerateLelantusMint(walletdb, newCoin, hdMint, seedID, boost::none, true); - Cout.emplace_back(newCoin); - auto& pubCoin = newCoin.getPublicCoin(); + auto &pubCoin = newCoin.getPublicCoin(); - if (!pubCoin.validate()) { - throw std::runtime_error("Unable to mint a lelantus coin."); - } + if (!pubCoin.validate()) { + throw std::runtime_error("Unable to mint a lelantus coin."); + } - // Create script for coin - CScript scriptSerializedCoin; - scriptSerializedCoin << OP_LELANTUSJMINT; - std::vector vch = pubCoin.getValue().getvch(); - scriptSerializedCoin.insert(scriptSerializedCoin.end(), vch.begin(), vch.end()); - - std::vector encryptedValue = pwalletMain->EncryptMintAmount(mintVal, pubCoin.getValue()); - scriptSerializedCoin.insert(scriptSerializedCoin.end(), encryptedValue.begin(), encryptedValue.end()); - - auto pubcoin = hdMint.GetPubcoinValue() + lelantus::Params::get_default()->get_h1() * Scalar(hdMint.GetAmount()).negate(); - uint256 hashPub = primitives::GetPubCoinValueHash(pubcoin); - CDataStream ss(SER_GETHASH, 0); - ss << hashPub; - ss << seedID; - uint256 hashForRecover = Hash(ss.begin(), ss.end()); - CDataStream serializedHash(SER_NETWORK, 0); - serializedHash << hashForRecover; - scriptSerializedCoin.insert(scriptSerializedCoin.end(), serializedHash.begin(), serializedHash.end()); - - outputs.push_back(CTxOut(0, scriptSerializedCoin)); - mintCoins.push_back(hdMint); + // Create script for coin + CScript scriptSerializedCoin; + scriptSerializedCoin << OP_LELANTUSJMINT; + std::vector vch = pubCoin.getValue().getvch(); + scriptSerializedCoin.insert(scriptSerializedCoin.end(), vch.begin(), vch.end()); + + std::vector encryptedValue = pwalletMain->EncryptMintAmount(mintVal, pubCoin.getValue()); + scriptSerializedCoin.insert(scriptSerializedCoin.end(), encryptedValue.begin(), encryptedValue.end()); + + auto pubcoin = hdMint.GetPubcoinValue() + + lelantus::Params::get_default()->get_h1() * Scalar(hdMint.GetAmount()).negate(); + uint256 hashPub = primitives::GetPubCoinValueHash(pubcoin); + CDataStream ss(SER_GETHASH, 0); + ss << hashPub; + ss << seedID; + uint256 hashForRecover = Hash(ss.begin(), ss.end()); + // Check if there is a mint with same private data in chain, most likely Hd mint state corruption, + // If yes, try with new counter + GroupElement dummyValue; + if (lelantus::CLelantusState::GetState()->HasCoinTag(dummyValue, hashForRecover)) + continue; + + CDataStream serializedHash(SER_NETWORK, 0); + serializedHash << hashForRecover; + scriptSerializedCoin.insert(scriptSerializedCoin.end(), serializedHash.begin(), serializedHash.end()); + + Cout.emplace_back(newCoin); + outputs.push_back(CTxOut(0, scriptSerializedCoin)); + mintCoins.push_back(hdMint); + break; + } } } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d319cf7651..1d474af1b8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2650,48 +2650,57 @@ CRecipient CWallet::CreateLelantusMintRecipient( { EnsureMintWalletAvailable(); - CWalletDB walletdb(pwalletMain->strWalletFile); - uint160 seedID; - if (generate) { - // Generate and store secrets deterministically in the following function. - pwalletMain->zwallet->GenerateLelantusMint(walletdb, coin, vDMint, seedID); - } + while (true) { + CWalletDB walletdb(pwalletMain->strWalletFile); + uint160 seedID; + if (generate) { + // Generate and store secrets deterministically in the following function. + pwalletMain->zwallet->GenerateLelantusMint(walletdb, coin, vDMint, seedID); + } - // Get a copy of the 'public' portion of the coin. You should - // embed this into a Lelantus 'MINT' transaction along with a series of currency inputs - auto& pubCoin = coin.getPublicCoin(); + // Get a copy of the 'public' portion of the coin. You should + // embed this into a Lelantus 'MINT' transaction along with a series of currency inputs + auto &pubCoin = coin.getPublicCoin(); - if (!pubCoin.validate()) { - throw std::runtime_error("Unable to mint a lelantus coin."); - } + if (!pubCoin.validate()) { + throw std::runtime_error("Unable to mint a lelantus coin."); + } - // Create script for coin - CScript script; - // opcode is inserted as 1 byte according to file script/script.h - script << OP_LELANTUSMINT; - - // and this one will write the size in different byte lengths depending on the length of vector. If vector size is <0.4c, which is 76, will write the size of vector in just 1 byte. In our case the size is always 34, so must write that 34 in 1 byte. - std::vector vch = pubCoin.getValue().getvch(); - script.insert(script.end(), vch.begin(), vch.end()); //this uses 34 byte - - // generating schnorr proof - CDataStream serializedSchnorrProof(SER_NETWORK, PROTOCOL_VERSION); - lelantus::GenerateMintSchnorrProof(coin, serializedSchnorrProof); - script.insert(script.end(), serializedSchnorrProof.begin(), serializedSchnorrProof.end()); //this uses 98 byte - - auto pubcoin = vDMint.GetPubcoinValue() + lelantus::Params::get_default()->get_h1() * Scalar(vDMint.GetAmount()).negate(); - uint256 hashPub = primitives::GetPubCoinValueHash(pubcoin); - CDataStream ss(SER_GETHASH, 0); - ss << hashPub; - ss << seedID; - uint256 hashForRecover = Hash(ss.begin(), ss.end()); - CDataStream serializedHash(SER_NETWORK, 0); - serializedHash << hashForRecover; - script.insert(script.end(), serializedHash.begin(), serializedHash.end()); - - // overall Lelantus mint script size is 1 + 34 + 98 + 32 = 165 byte - return {script, CAmount(coin.getV()), false}; + // Create script for coin + CScript script; + // opcode is inserted as 1 byte according to file script/script.h + script << OP_LELANTUSMINT; + + // and this one will write the size in different byte lengths depending on the length of vector. If vector size is <0.4c, which is 76, will write the size of vector in just 1 byte. In our case the size is always 34, so must write that 34 in 1 byte. + std::vector vch = pubCoin.getValue().getvch(); + script.insert(script.end(), vch.begin(), vch.end()); //this uses 34 byte + + // generating schnorr proof + CDataStream serializedSchnorrProof(SER_NETWORK, PROTOCOL_VERSION); + lelantus::GenerateMintSchnorrProof(coin, serializedSchnorrProof); + script.insert(script.end(), serializedSchnorrProof.begin(), serializedSchnorrProof.end()); //this uses 98 byte + + auto pubcoin = vDMint.GetPubcoinValue() + + lelantus::Params::get_default()->get_h1() * Scalar(vDMint.GetAmount()).negate(); + uint256 hashPub = primitives::GetPubCoinValueHash(pubcoin); + CDataStream ss(SER_GETHASH, 0); + ss << hashPub; + ss << seedID; + uint256 hashForRecover = Hash(ss.begin(), ss.end()); + + // Check if there is a mint with same private data in chain, most likely Hd mint state corruption, + // If yes, try with new counter + GroupElement dummyValue; + if (lelantus::CLelantusState::GetState()->HasCoinTag(dummyValue, hashForRecover)) + continue; + + CDataStream serializedHash(SER_NETWORK, 0); + serializedHash << hashForRecover; + script.insert(script.end(), serializedHash.begin(), serializedHash.end()); + // overall Lelantus mint script size is 1 + 34 + 98 + 32 = 165 byte + return {script, CAmount(coin.getV()), false}; + } } // coinsIn has to be sorted in descending order.