diff --git a/Jenkinsfile b/Jenkinsfile index 1027545126..5c548d03ff 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -32,11 +32,19 @@ pipeline { } stage('Test') { steps { - dir('dist') { - sh 'make check' + catchError(buildResult: 'FAILURE', stageResult: 'FAILURE'){ + dir('dist') { + sh 'make check' + } } } } + stage('Archive unit tests logs') { + steps { + archiveArtifacts artifacts: 'dist/src/test-suite.log', + allowEmptyArchive: true + } + } stage('RPC Tests') { steps { dir('dist') { diff --git a/configure.ac b/configure.ac index 53d504a5da..c532149f8d 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, 12) -define(_CLIENT_VERSION_BUILD, 6) +define(_CLIENT_VERSION_REVISION, 13) +define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2023) define(_COPYRIGHT_HOLDERS,[The %s developers]) diff --git a/qa/rpc-tests/test_framework/script.py b/qa/rpc-tests/test_framework/script.py index 83bbf20479..5905a7dee9 100644 --- a/qa/rpc-tests/test_framework/script.py +++ b/qa/rpc-tests/test_framework/script.py @@ -246,6 +246,7 @@ def __new__(cls, n): OP_SMALLINTEGER = CScriptOp(0xfa) OP_PUBKEYS = CScriptOp(0xfb) OP_PUBKEYHASH = CScriptOp(0xfd) +OP_SUPERSTRANSPARENTPUBKEYHASH = CScriptOp(0xe0) OP_PUBKEY = CScriptOp(0xfe) OP_INVALIDOPCODE = CScriptOp(0xff) diff --git a/src/base58.cpp b/src/base58.cpp index 605576dcfe..005eed6d35 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -222,6 +222,7 @@ class CBitcoinAddressVisitor : public boost::static_visitor CBitcoinAddressVisitor(CBitcoinAddress* addrIn) : addr(addrIn) {} bool operator()(const CKeyID& id) const { return addr->Set(id); } + bool operator()(const CExchangeKeyID& id) const { return addr->Set(id); } bool operator()(const CScriptID& id) const { return addr->Set(id); } bool operator()(const CNoDestination& no) const { return false; } }; @@ -234,6 +235,18 @@ bool CBitcoinAddress::Set(const CKeyID& id) return true; } +bool CBitcoinAddress::Set(const CExchangeKeyID& id) +{ + SetData(Params().Base58Prefix(CChainParams::EXCHANGE_PUBKEY_ADDRESS), &id, 20); + return true; +} + +bool CBitcoinAddress::SetExchange(const CKeyID& id) +{ + SetData(Params().Base58Prefix(CChainParams::EXCHANGE_PUBKEY_ADDRESS), &id, 20); + return true; +} + bool CBitcoinAddress::Set(const CScriptID& id) { SetData(Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS), &id, 20); @@ -254,7 +267,8 @@ bool CBitcoinAddress::IsValid(const CChainParams& params) const { bool fCorrectSize = vchData.size() == 20; bool fKnownVersion = vchVersion == params.Base58Prefix(CChainParams::PUBKEY_ADDRESS) || - vchVersion == params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); + vchVersion == params.Base58Prefix(CChainParams::SCRIPT_ADDRESS) || + vchVersion == params.Base58Prefix(CChainParams::EXCHANGE_PUBKEY_ADDRESS); return fCorrectSize && fKnownVersion; } @@ -268,6 +282,8 @@ CTxDestination CBitcoinAddress::Get() const return CKeyID(id); else if (vchVersion == Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS)) return CScriptID(id); + else if (vchVersion == Params().Base58Prefix(CChainParams::EXCHANGE_PUBKEY_ADDRESS)) + return CExchangeKeyID(id); else return CNoDestination(); } diff --git a/src/base58.h b/src/base58.h index 631dabbfed..1ea609ae40 100644 --- a/src/base58.h +++ b/src/base58.h @@ -106,14 +106,22 @@ class CBase58Data class CBitcoinAddress : public CBase58Data { public: bool Set(const CKeyID &id); + bool Set(const CExchangeKeyID &id); bool Set(const CScriptID &id); bool Set(const CTxDestination &dest); + bool SetExchange(const CKeyID &id); bool IsValid() const; bool IsValid(const CChainParams ¶ms) const; CBitcoinAddress() {} CBitcoinAddress(const CTxDestination &dest) { Set(dest); } - CBitcoinAddress(const std::string& strAddress) { SetString(strAddress); } + CBitcoinAddress(const std::string& strAddress) { + SetString(strAddress); + if (vchData.size() != 20) { + // give the address second chance and try exchange address format with 3 byte prefix + SetString(strAddress.c_str(), 3); + } + } CBitcoinAddress(const char* pszAddress) { SetString(pszAddress); } CTxDestination Get() const; diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 6cc42204d2..7c31a4c84a 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -354,6 +354,7 @@ class CMainParams : public CChainParams { // Note that of those with the service bits flag, most only support a subset of possible options base58Prefixes[PUBKEY_ADDRESS] = std::vector < unsigned char > (1, 82); base58Prefixes[SCRIPT_ADDRESS] = std::vector < unsigned char > (1, 7); + base58Prefixes[EXCHANGE_PUBKEY_ADDRESS] = {0x01, 0xb9, 0xbb}; // EXX prefix for the address base58Prefixes[SECRET_KEY] = std::vector < unsigned char > (1, 210); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x88)(0xB2)(0x1E).convert_to_container < std::vector < unsigned char > > (); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x88)(0xAD)(0xE4).convert_to_container < std::vector < unsigned char > > (); @@ -472,6 +473,9 @@ class CMainParams : public CChainParams { consensus.nPPSwitchTime = 1635228000; // Tue Oct 26 2021 06:00:00 GMT+0000 consensus.nPPBlockNumber = 419264; consensus.nInitialPPDifficulty = 0x1b1774cd; // 40GH/s + + // exchange address + consensus.nExchangeAddressStartBlock = consensus.nSparkStartBlock; } virtual bool SkipUndoForBlock(int nHeight) const { @@ -660,6 +664,7 @@ class CTestNetParams : public CChainParams { base58Prefixes[PUBKEY_ADDRESS] = std::vector < unsigned char > (1, 65); base58Prefixes[SCRIPT_ADDRESS] = std::vector < unsigned char > (1, 178); + base58Prefixes[EXCHANGE_PUBKEY_ADDRESS] = {0x01, 0xb9, 0xbb}; // EXT prefix for the address base58Prefixes[SECRET_KEY] = std::vector < unsigned char > (1, 185); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xCF).convert_to_container < std::vector < unsigned char > > (); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x94).convert_to_container < std::vector < unsigned char > > (); @@ -763,6 +768,9 @@ class CTestNetParams : public CChainParams { consensus.nPPSwitchTime = 1630069200; // August 27 2021, 13:00 UTC consensus.nPPBlockNumber = 37305; consensus.nInitialPPDifficulty = 0x1d016e81; // 10MH/s + + // exchange address + consensus.nExchangeAddressStartBlock = 147000; } }; @@ -918,6 +926,7 @@ class CDevNetParams : public CChainParams { base58Prefixes[PUBKEY_ADDRESS] = std::vector < unsigned char > (1, 66); base58Prefixes[SCRIPT_ADDRESS] = std::vector < unsigned char > (1, 179); + base58Prefixes[EXCHANGE_PUBKEY_ADDRESS] = {0x01, 0xb9, 0x8e}; // EXD prefix for the address base58Prefixes[SECRET_KEY] = std::vector < unsigned char > (1, 186); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xD0).convert_to_container < std::vector < unsigned char > > (); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x95).convert_to_container < std::vector < unsigned char > > (); @@ -996,6 +1005,9 @@ class CDevNetParams : public CChainParams { consensus.nPPSwitchTime = 1631261566; // immediately after network start consensus.nPPBlockNumber = 1; consensus.nInitialPPDifficulty = 0x2000ffff; + + // exchange address + consensus.nExchangeAddressStartBlock = 2500; } }; @@ -1158,6 +1170,7 @@ class CRegTestParams : public CChainParams { }; base58Prefixes[PUBKEY_ADDRESS] = std::vector < unsigned char > (1, 65); base58Prefixes[SCRIPT_ADDRESS] = std::vector < unsigned char > (1, 178); + base58Prefixes[EXCHANGE_PUBKEY_ADDRESS] = {0x01, 0xb9, 0xac}; // EXR prefix for the address base58Prefixes[SECRET_KEY] = std::vector < unsigned char > (1, 239); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xCF).convert_to_container < std::vector < unsigned char > > (); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x94).convert_to_container < std::vector < unsigned char > > (); @@ -1182,6 +1195,7 @@ class CRegTestParams : public CChainParams { consensus.nLelantusStartBlock = 400; consensus.nLelantusFixesStartBlock = 400; consensus.nSparkStartBlock = 1000; + consensus.nExchangeAddressStartBlock = 1000; consensus.nLelantusGracefulPeriod = 1500; consensus.nZerocoinV2MintMempoolGracefulPeriod = 1; consensus.nZerocoinV2MintGracefulPeriod = 1; diff --git a/src/chainparams.h b/src/chainparams.h index 3e83350079..a236876f39 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -52,6 +52,7 @@ class CChainParams SECRET_KEY, EXT_PUBLIC_KEY, EXT_SECRET_KEY, + EXCHANGE_PUBKEY_ADDRESS, MAX_BASE58_TYPES }; diff --git a/src/client-api/send.cpp b/src/client-api/send.cpp index f28f3762ea..14afaa45f8 100644 --- a/src/client-api/send.cpp +++ b/src/client-api/send.cpp @@ -55,7 +55,7 @@ bool getPaymentRequest(UniValue &paymentRequestUni, UniValue &paymentRequestData if(!paymentRequestUni["data"].isNull()){ paymentRequestData = paymentRequestUni["data"]; } - + return true; } @@ -114,15 +114,9 @@ UniValue paymentrequestaddress(Type type, const UniValue& data, const UniValue& CWalletDB walletdb(pwalletMain->strWalletFile); if(addressType == "Spark") { - if(!walletdb.ReadPaymentRequestSparkAddress(address)){ - address = getNewSparkAddress().get_str(); - walletdb.WritePaymentRequestSparkAddress(address); - } + address = getNewSparkAddress().get_str(); } else if (addressType == "Transparent") { - if(!walletdb.ReadPaymentRequestAddress(address)){ - address = getNewAddress().get_str(); - walletdb.WritePaymentRequestAddress(address); - } + address = getNewAddress().get_str(); } else { throw JSONAPIError(API_INVALID_PARAMETER, "Invalid addressType"); } @@ -131,7 +125,7 @@ UniValue paymentrequestaddress(Type type, const UniValue& data, const UniValue& } UniValue sendzcoin(Type type, const UniValue& data, const UniValue& auth, bool fHelp) -{ +{ LOCK2(cs_main, pwalletMain->cs_wallet); CCoinControl cc; @@ -156,7 +150,7 @@ UniValue sendzcoin(Type type, const UniValue& data, const UniValue& auth, bool f std::vector keys = sendTo.getKeys(); BOOST_FOREACH(const std::string& name_, keys) { - + UniValue entry(UniValue::VOBJ); try{ entry = find_value(sendTo, name_).get_obj(); @@ -184,7 +178,7 @@ UniValue sendzcoin(Type type, const UniValue& data, const UniValue& auth, bool f CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; vecSend.push_back(recipient); } - + // Send CReserveKey keyChange(pwalletMain); CAmount nFeeRequired = 0; @@ -211,7 +205,7 @@ UniValue sendzcoin(Type type, const UniValue& data, const UniValue& auth, bool f } UniValue txfee(Type type, const UniValue& data, const UniValue& auth, bool fHelp){ - // first set the tx fee per kb, then return the total fee with addresses. + // first set the tx fee per kb, then return the total fee with addresses. if (!EnsureWalletIsAvailable(pwalletMain, fHelp)) return NullUniValue; @@ -274,154 +268,16 @@ UniValue txfee(Type type, const UniValue& data, const UniValue& auth, bool fHelp std::string strFailReason; bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason, hasCoinControl ? &coinControl : NULL, false); if (!fCreated) - throw JSONAPIError(API_WALLET_INSUFFICIENT_FUNDS, strFailReason); - + throw JSONAPIError(API_WALLET_INSUFFICIENT_FUNDS, strFailReason); + ret.push_back(Pair("fee", nFeeRequired)); return ret; } -UniValue paymentrequest(Type type, const UniValue& data, const UniValue& auth, bool fHelp) -{ - if (!EnsureWalletIsAvailable(pwalletMain, false)) - return NullUniValue; - - UniValue paymentRequestUni(UniValue::VOBJ); - UniValue paymentRequestData(UniValue::VOBJ); - - getPaymentRequest(paymentRequestUni, paymentRequestData); - - bool returnEntry = false; - UniValue entry(UniValue::VOBJ); - - switch(type){ - case Initial: { - return paymentRequestData; - break; - } - case Create: { - - milliseconds secs = duration_cast< milliseconds >( - system_clock::now().time_since_epoch() - ); - UniValue createdAt = secs.count(); - - std::string paymentRequestAddress; - entry.push_back(Pair("createdAt", createdAt.get_int64())); - entry.push_back(Pair("state", "active")); - - try{ - paymentRequestAddress = find_value(data, "address").get_str(); - entry.push_back(Pair("amount", find_value(data, "amount"))); - entry.push_back(Pair("address", paymentRequestAddress)); - entry.push_back(Pair("message", find_value(data, "message").get_str())); - entry.push_back(Pair("label", find_value(data, "label").get_str())); - }catch (const std::exception& e){ - throw JSONAPIError(API_WRONG_TYPE_CALLED, "wrong key passed/value type for method"); - } - - CWalletDB walletdb(pwalletMain->strWalletFile); - std::string nextPaymentRequestAddress; - if(!walletdb.ReadPaymentRequestAddress(nextPaymentRequestAddress)) - throw std::runtime_error("Could not retrieve wallet payment address."); - - if(nextPaymentRequestAddress != paymentRequestAddress) - throw std::runtime_error("Payment request address passed does not match wallet."); - - if(!paymentRequestUni.replace("data", paymentRequestData)){ - throw std::runtime_error("Could not replace key/value pair."); - } - returnEntry = true; - - // remove payment request address - if(!walletdb.ErasePaymentRequestAddress()) - throw std::runtime_error("Could not reset payment request address."); - - break; - } - case Delete: { - std::string id = find_value(data, "id").get_str(); - - const UniValue addressObj = find_value(paymentRequestData, id); - if(addressObj.isNull()){ - throw JSONAPIError(API_INVALID_PARAMETER, "Invalid data, id does not exist"); - } - - const UniValue addressStr = find_value(addressObj, "address"); - if(addressStr.isNull()){ - throw JSONAPIError(API_INVALID_PARAMETER, "Invalid data, address not found"); - } - - paymentRequestData.erase(addressStr); - - if(!paymentRequestUni.replace("data", paymentRequestData)){ - throw std::runtime_error("Could not replace key/value pair."); - } - return true; - break; - } - /* - "Update" can be used to either: - - Update an existing address and metadata associated with a payment request - - Create a new entry for address and metadata that was NOT created through a payment request (eg. created with the Qt application). - */ - case Update: { - std::string id; - std::vector dataKeys; - try{ - id = find_value(data, "id").get_str(); - dataKeys = data.getKeys(); - }catch (const std::exception& e){ - throw JSONAPIError(API_WRONG_TYPE_CALLED, "wrong key passed/value type for method"); - } - - entry = find_value(paymentRequestData, id); - - // If null, declare the object again. - if(entry.isNull()){ - entry.setObject(); - entry.push_back(Pair("address", id)); - } - - for (std::vector::iterator it = dataKeys.begin(); it != dataKeys.end(); it++){ - std::string key = (*it); - UniValue value = find_value(data, key); - if(!(key=="id")){ - if(key=="state"){ - // Only update state should it be a valid value - if(!(value.getType()==UniValue::VSTR) && !nStates.count(value.get_str())) - throw JSONAPIError(API_WRONG_TYPE_CALLED, "wrong key passed/value type for method"); - } - entry.replace(key, value); //todo might have to specify type - } - } - - paymentRequestData.replace(id, entry); - - if(!paymentRequestUni.replace("data", paymentRequestData)){ - throw std::runtime_error("Could not replace key/value pair."); - } - returnEntry = true; - break; - } - default: { - - } - } - - setPaymentRequest(paymentRequestUni); - - if(returnEntry){ - return entry; - } - - return true; -} - static const CAPICommand commands[] = { // category collection actor (function) authPort authPassphrase warmupOk // --------------------- ------------ ---------------- -------- -------------- -------- - { "send", "paymentRequest", &paymentrequest, true, false, false }, { "send", "paymentRequestAddress", &paymentrequestaddress, true, false, false }, { "send", "txFee", &txfee, true, false, false }, { "send", "sendZcoin", &sendzcoin, true, true, false } diff --git a/src/clientversion.h b/src/clientversion.h index a2d9aa8cbb..07684206fd 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 12 -#define CLIENT_VERSION_BUILD 6 +#define CLIENT_VERSION_REVISION 13 +#define CLIENT_VERSION_BUILD 0 //! Set to true for release, false for prerelease or test build #define CLIENT_VERSION_IS_RELEASE true diff --git a/src/consensus/params.h b/src/consensus/params.h index 061d7ff754..105bcd4dd1 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -334,6 +334,9 @@ struct Params { // Number of blocks with allowed zerocoin to sigma remint transaction (after nSigmaStartBlock) int nZerocoinToSigmaRemintWindowSize; + // Number of block that introduces ability to specify super-transparent addresses + int nExchangeAddressStartBlock; + /** switch to MTP time */ uint32_t nMTPSwitchTime; /** number of block when MTP switch occurs or 0 if not clear yet */ diff --git a/src/elysium/script.cpp b/src/elysium/script.cpp index 854807154d..fc87b066dd 100644 --- a/src/elysium/script.cpp +++ b/src/elysium/script.cpp @@ -73,6 +73,9 @@ bool SafeSolver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& ids = joinsplit->getCoinGroupIds(); + for (const auto& id: ids) { + if (!anonymity_sets.count(id)) + return state.DoS(100, + error("CheckLelantusJoinSplitTransaction: No anonymity set found.")); + } + BatchProofContainer* batchProofContainer = BatchProofContainer::get_instance(); bool useBatching = batchProofContainer->fCollectProofs && !isVerifyDB && !isCheckWallet && lelantusTxInfo && !lelantusTxInfo->fInfoIsComplete; @@ -926,15 +933,17 @@ bool ConnectBlockLelantus( )) { return false; } + } - if (!fJustCheck) { + if (!fJustCheck) { + BOOST_FOREACH(auto& serial, pblock->lelantusTxInfo->spentSerials) { pindexNew->lelantusSpentSerials.insert(serial); lelantusState.AddSpend(serial.first, serial.second); } } - - if (fJustCheck) + else { return true; + } const auto& params = ::Params().GetConsensus(); CHash256 hash; @@ -1346,9 +1355,8 @@ void CLelantusState::RemoveBlock(CBlockIndex *index) { // roll back coin group updates for (auto &coins : index->lelantusMintedPubCoins) { - if (coinGroups.count(coins.first) == 0) { - throw std::invalid_argument("Group Id does not exist"); - } + if (coinGroups.count(coins.first) == 0) + continue; LelantusCoinGroupInfo& coinGroup = coinGroups[coins.first]; auto nMintsToForget = coins.second.size(); diff --git a/src/pubkey.h b/src/pubkey.h index 702a953570..0da1cae33a 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -33,6 +33,15 @@ class CKeyID : public uint160 CKeyID(const uint160& in) : uint160(in) {} }; +/** A reference to a CKey: the Hash160 of its serialized public key, special case for exchange key */ + +class CExchangeKeyID : public uint160 +{ +public: + CExchangeKeyID() : uint160() {} + CExchangeKeyID(const uint160& in) : uint160(in) {} +}; + typedef uint256 ChainCode; /** An encapsulated public key. */ diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 7ba41d25e6..bd08df359d 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -31,19 +31,7 @@ AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, tab(_tab) { 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(); - } + this->isReused = isReused; if (!platformStyle->getImagesOnButtons()) { ui->newAddress->setIcon(QIcon()); @@ -127,6 +115,23 @@ void AddressBookPage::setModel(AddressTableModel *_model) this->model = _model; if(!_model) return; + bool spark = this->model->IsSparkAllowed(); + + if (tab == SendingTab) { + if (spark) { + ui->addressType->addItem(tr("Spark"), Spark); + } + ui->addressType->addItem(tr("Transparent"), Transparent); + } else if(tab == ReceivingTab && !this->isReused) { + if (spark) { + 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(); + } proxyModel = new QSortFilterProxyModel(this); fproxyModel = new AddressBookFilterProxy(this); @@ -174,6 +179,23 @@ void AddressBookPage::setModel(AddressTableModel *_model) connect(ui->addressType, qOverload(&QComboBox::activated), this, &AddressBookPage::chooseAddressType); } +void AddressBookPage::updateSpark() { + ui->addressType->clear(); + if (tab == SendingTab) { + ui->addressType->addItem(tr("Spark"), Spark); + ui->addressType->addItem(tr("Transparent"), Transparent); + } else if(tab == ReceivingTab && !this->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(); + } + + chooseAddressType(0); +} + void AddressBookPage::on_copyAddress_clicked() { GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Address); @@ -191,9 +213,7 @@ void AddressBookPage::onEditAction() EditAddressDialog::Mode mode; AddressTableModel * pmodel; pmodel = model; - if (ui->addressType->currentText() == AddressTableModel::RAP) { - mode = EditAddressDialog::EditPcode; - } else if (ui->addressType->currentText() == AddressTableModel::Transparent) { + if (ui->addressType->currentText() == AddressTableModel::Transparent) { mode = tab == SendingTab ? EditAddressDialog::EditSendingAddress : EditAddressDialog::EditReceivingAddress; } else { mode = tab == SendingTab ? EditAddressDialog::EditSparkSendingAddress : EditAddressDialog::EditSparkReceivingAddress; @@ -224,8 +244,6 @@ void AddressBookPage::on_newAddress_clicked() 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; } @@ -333,10 +351,6 @@ void AddressBookPage::on_exportButton_clicked() writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole); 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); @@ -356,8 +370,6 @@ void AddressBookPage::contextualMenu(const QPoint &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")); } @@ -397,14 +409,12 @@ bool AddressBookFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex & 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; } diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index e89d1402f8..7ddbb65b5e 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -45,8 +45,7 @@ class AddressBookPage : public QDialog enum AddressTypeEnum { Spark, - Transparent, - RAP + Transparent }; explicit AddressBookPage(const PlatformStyle *platformStyle, Mode mode, Tabs tab, QWidget *parent, bool isReused = true); @@ -55,6 +54,8 @@ class AddressBookPage : public QDialog void setModel(AddressTableModel *model); const QString &getReturnValue() const { return returnValue; } + void updateSpark(); + public Q_SLOTS: void done(int retval); @@ -70,6 +71,7 @@ public Q_SLOTS: QAction *copyAddressAction; QAction *deleteAction; // to be able to explicitly disable it QString newAddressToSelect; + bool isReused; private Q_SLOTS: /** Delete currently selected address entry */ diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 0352d19f03..16359b21a4 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -684,6 +684,11 @@ PcodeAddressTableModel * AddressTableModel::getPcodeAddressTableModel() return walletModel->getPcodeAddressTableModel(); } +bool AddressTableModel::IsSparkAllowed(){ + return spark::IsSparkAllowed(); +} + + // RAP pcodes static void NotifyPcodeLabeled(PcodeAddressTableModel *walletmodel, std::string pcode, std::string label, bool removed) diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 3ff1cfa630..2fa4e51537 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -88,6 +88,8 @@ class AddressTableModel : public QAbstractTableModel EditStatus getEditStatus() const { return editStatus; } PcodeAddressTableModel * getPcodeAddressTableModel(); + + bool IsSparkAllowed(); protected: WalletModel *walletModel; CWallet *wallet; diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index c31ee1d8bb..0497bfce5e 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -399,7 +399,7 @@ void BitcoinApplication::unlockWallet_(void * wallet) { CWallet * wallet_ = reinterpret_cast(wallet); - QString info = tr("You need to unlock to allow spark wallet be created."); + QString info = tr("You need to unlock to allow Spark wallet be created."); walletModel = new WalletModel(platformStyle, wallet_, optionsModel); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 5b9dce0c66..bb1dfebe85 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1496,6 +1496,9 @@ void BitcoinGUI::checkLelantusVisibility(int numBlocks) } lelantusAction->setVisible(allowLelantusPage); } + + if (numBlocks == ::Params().GetConsensus().nSparkStartBlock) + walletFrame->updateAddressbook(); } void BitcoinGUI::toggleNetworkActive() diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 46b1f33f62..a7affffa67 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -173,16 +173,6 @@ - - - - Whether to show RAP addresses or not. - - - Display RAP addresses - - - diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index e0307a5395..6b94d1cc7a 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -185,7 +185,6 @@ void OptionsDialog::setMapper() mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange); mapper->addMapping(ui->reindexLelantus, OptionsModel::ReindexLelantus); mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures); - mapper->addMapping(ui->enableRapAddresses, OptionsModel::enableRapAddresses); /* Lelantus */ mapper->addMapping(ui->autoAnonymize, OptionsModel::AutoAnonymize); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 047a4bfa12..98b5955051 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -81,10 +81,6 @@ void OptionsModel::Init(bool resetSettings) settings.setValue("fCoinControlFeatures", false); fCoinControlFeatures = settings.value("fCoinControlFeatures", false).toBool(); - if (!settings.contains("fenableRapAddresses")) - settings.setValue("fenableRapAddresses", false); - fenableRapAddresses = settings.value("fenableRapAddresses", false).toBool(); - if (!settings.contains("fAutoAnonymize")) settings.setValue("fAutoAnonymize", false); fAutoAnonymize = settings.value("fAutoAnonymize", false).toBool(); @@ -281,8 +277,6 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const return settings.value("language"); case CoinControlFeatures: return fCoinControlFeatures; - case enableRapAddresses: - return fenableRapAddresses; case AutoAnonymize: return fAutoAnonymize; case LelantusPage: @@ -430,11 +424,6 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in settings.setValue("fCoinControlFeatures", fCoinControlFeatures); Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); break; - case enableRapAddresses: - fenableRapAddresses = value.toBool(); - settings.setValue("fenableRapAddresses", fenableRapAddresses); - Q_EMIT enableRapAddressesChanged(fenableRapAddresses); - break; case AutoAnonymize: fAutoAnonymize = value.toBool(); settings.setValue("fAutoAnonymize", fAutoAnonymize); diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 6b7d5de4c3..26a4c9612e 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -425,10 +425,10 @@ MigrateLelantusToSparkDialog::MigrateLelantusToSparkDialog(WalletModel *_model): QPushButton *ignore = new QPushButton(this); ignore->setText("Ignore"); - ignore->setStyleSheet("color:#9b1c2e;background-color:none;margin-top:30px;margin-bottom:60px;margin-left:50px;margin-right:20px;border:1px solid #9b1c2e;"); + ignore->setStyleSheet("margin-top:30px;margin-bottom:60px;margin-left:20px;margin-right:50px;"); QPushButton *migrate = new QPushButton(this); migrate->setText("Migrate"); - migrate->setStyleSheet("margin-top:30px;margin-bottom:60px;margin-left:20px;margin-right:50px;"); + migrate->setStyleSheet("color:#9b1c2e;background-color:none;margin-top:30px;margin-bottom:60px;margin-left:50px;margin-right:20px;border:1px solid #9b1c2e;"); QHBoxLayout *groupButton = new QHBoxLayout(this); groupButton->addWidget(ignore); groupButton->addWidget(migrate); @@ -462,13 +462,13 @@ MigrateLelantusToSparkDialog::MigrateLelantusToSparkDialog(WalletModel *_model): void MigrateLelantusToSparkDialog::onIgnoreClicked() { setVisible(false); - clickedButton = false; + clickedButton = true; } void MigrateLelantusToSparkDialog::onMigrateClicked() { setVisible(false); - clickedButton = true; + clickedButton = false; model->migrateLelantusToSpark(); } diff --git a/src/qt/res/images/splash.png b/src/qt/res/images/splash.png index aee3979157..7a543d8f74 100644 Binary files a/src/qt/res/images/splash.png and b/src/qt/res/images/splash.png differ diff --git a/src/qt/res/images/splash_testnet.png b/src/qt/res/images/splash_testnet.png index aee3979157..7a543d8f74 100644 Binary files a/src/qt/res/images/splash_testnet.png and b/src/qt/res/images/splash_testnet.png differ diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 5480410cbd..10e1e91db9 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -320,10 +320,10 @@ void SendCoinsDialog::on_sendButton_clicked() CAmount mintSparkAmount = 0; CAmount txFee = 0; CAmount totalAmount = 0; - if (model->getLelantusModel()->getPrivateBalance().first > 0 && chainActive.Height() < ::Params().GetConsensus().nLelantusGracefulPeriod) { + if (model->getLelantusModel()->getPrivateBalance().first > 0 && spark::IsSparkAllowed() && chainActive.Height() < ::Params().GetConsensus().nLelantusGracefulPeriod) { MigrateLelantusToSparkDialog migrateLelantusToSpark(model); bool clickedButton = migrateLelantusToSpark.getClickedButton(); - if(clickedButton) { + if(!clickedButton) { fNewRecipientAllowed = true; return; } @@ -332,16 +332,23 @@ void SendCoinsDialog::on_sendButton_clicked() prepareStatus = model->prepareJoinSplitTransaction(currentTransaction, &ctrl); } else if ((fAnonymousMode == true) && spark::IsSparkAllowed()) { prepareStatus = model->prepareSpendSparkTransaction(currentTransaction, &ctrl); - } else if ((fAnonymousMode == false) && (recipients.size() == sparkAddressCount) && spark::IsSparkAllowed()) { - prepareStatus = model->prepareMintSparkTransaction(transactions, recipients, wtxAndFees, reservekeys, &ctrl); - } else if ((fAnonymousMode == false) && (sparkAddressCount == 0)){ - SendGoPrivateDialog goPrivateDialog; - bool clickedButton = goPrivateDialog.getClickedButton(); - if(clickedButton) { - setAnonymizeMode(true); - fNewRecipientAllowed = true; + } else if ((fAnonymousMode == false) && (recipients.size() == sparkAddressCount)) { + if (spark::IsSparkAllowed()) + prepareStatus = model->prepareMintSparkTransaction(transactions, recipients, wtxAndFees, reservekeys, &ctrl); + else { + processSendCoinsReturn(WalletModel::InvalidAddress); return; } + } else if ((fAnonymousMode == false) && (sparkAddressCount == 0)) { + if (spark::IsSparkAllowed()) { + SendGoPrivateDialog goPrivateDialog; + bool clickedButton = goPrivateDialog.getClickedButton(); + if (!clickedButton) { + setAnonymizeMode(true); + fNewRecipientAllowed = true; + return; + } + } prepareStatus = model->prepareTransaction(currentTransaction, &ctrl); } else { fNewRecipientAllowed = true; diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 3fb7f204f5..3b4d428003 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -250,3 +250,10 @@ void WalletFrame::outOfSyncWarningClicked() { Q_EMIT requestedSyncWarningInfo(); } + +void WalletFrame::updateAddressbook() { + WalletView *walletView = currentWalletView(); + + if (walletView) + walletView->updateAddressbook(); +} diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 25360b18b1..00725cfb38 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -108,6 +108,8 @@ public Q_SLOTS: void usedReceivingAddresses(); /** Pass on signal over requested out-of-sync-warning information */ void outOfSyncWarningClicked(); + + void updateAddressbook(); }; #endif // BITCOIN_QT_WALLETFRAME_H diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 306c5ba32f..7670ddc9ab 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -602,6 +602,12 @@ void WalletView::usedSendingAddresses() usedSendingAddressesPage->activateWindow(); } +void WalletView::updateAddressbook() +{ + usedReceivingAddressesPage->updateSpark(); + usedSendingAddressesPage->updateSpark(); +} + void WalletView::usedReceivingAddresses() { if(!walletModel) diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 271dde87e7..d07ccdc90e 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -187,6 +187,9 @@ public Q_SLOTS: /** Show used sending addresses */ void usedSendingAddresses(); + + void updateAddressbook(); + /** Show used receiving addresses */ void usedReceivingAddresses(); diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 6954bc0a24..1edb205d40 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -139,6 +139,17 @@ class DescribeAddressVisitor : public boost::static_visitor return obj; } + UniValue operator()(const CExchangeKeyID &keyID) const { + UniValue obj(UniValue::VOBJ); + CPubKey vchPubKey; + obj.push_back(Pair("isscript", false)); + if (pwallet && pwallet->GetPubKey(keyID, vchPubKey)) { + obj.push_back(Pair("exchangepubkey", HexStr(vchPubKey))); + obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed())); + } + return obj; + } + UniValue operator()(const CScriptID &scriptID) const { UniValue obj(UniValue::VOBJ); CScript subscript; @@ -490,6 +501,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 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) @@ -1632,6 +1679,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/script/interpreter.cpp b/src/script/interpreter.cpp index 7a85147e10..941ce813bb 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -268,6 +268,7 @@ bool EvalScript(std::vector >& stack, const CScript& try { + bool fFirstOpCode = true; while (pc < pend) { bool fExec = !count(vfExec.begin(), vfExec.end(), false); @@ -345,6 +346,13 @@ bool EvalScript(std::vector >& stack, const CScript& case OP_NOP: break; + case OP_EXCHANGEADDR: + // allow OP_EXCHANGEADDR only at the beginning of the script + if (!fFirstOpCode) + return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + // otherwise NOOP + break; + case OP_CHECKLOCKTIMEVERIFY: { if (!(flags & SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY)) { @@ -1030,6 +1038,8 @@ bool EvalScript(std::vector >& stack, const CScript& // Size limits if (stack.size() + altstack.size() > 1000) return set_error(serror, SCRIPT_ERR_STACK_SIZE); + + fFirstOpCode = false; } } catch (...) diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index fb1761b30d..670bf8a1d3 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -88,6 +88,7 @@ isminetype IsMine(const CKeyStore &keystore, const CScript& scriptPubKey, bool& break; } case TX_PUBKEYHASH: + case TX_EXCHANGEADDRESS: keyID = CKeyID(uint160(vSolutions[0])); if (sigversion != SIGVERSION_BASE) { CPubKey pubkey; diff --git a/src/script/script.cpp b/src/script/script.cpp index 697887ebe0..e7dcd8cc6b 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -156,6 +156,8 @@ const char* GetOpName(opcodetype opcode) case OP_SPARKMINT : return "OP_SPARKMINT"; case OP_SPARKSMINT : return "OP_SPARKSMINT"; case OP_SPARKSPEND : return "OP_SPARKSPEND"; + // Super transparent txout script prefix + case OP_EXCHANGEADDR : return "OP_EXCHANGEADDR"; // Note: // The template matching params OP_SMALLINTEGER/etc are defined in opcodetype enum @@ -261,6 +263,18 @@ bool CScript::IsPayToPublicKeyHash() const (*this)[24] == OP_CHECKSIG); } +bool CScript::IsPayToExchangeAddress() const +{ + // Extra-fast test for pay-to-pubkey-hash CScripts: + return (this->size() == 26 && + (*this)[0] == OP_EXCHANGEADDR && + (*this)[1] == OP_DUP && + (*this)[2] == OP_HASH160 && + (*this)[3] == 0x14 && + (*this)[24] == OP_EQUALVERIFY && + (*this)[25] == OP_CHECKSIG); +} + bool CScript::IsPayToScriptHash() const { // Extra-fast test for pay-to-script-hash CScripts: diff --git a/src/script/script.h b/src/script/script.h index 75b03d6660..e5e0bce3f6 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -207,6 +207,9 @@ enum opcodetype OP_SPARKMINT = 0xd1, OP_SPARKSMINT = 0xd2, OP_SPARKSPEND = 0xd3, + + // basically NOP but identifies that sunsequent txout script contains super transparent address + OP_EXCHANGEADDR = 0xe0 }; const char* GetOpName(opcodetype opcode); @@ -661,6 +664,7 @@ class CScript : public CScriptBase bool IsPayToPublicKey() const; bool IsPayToPublicKeyHash() const; + bool IsPayToExchangeAddress() const; bool IsPayToScriptHash() const; bool IsPayToWitnessScriptHash() const; diff --git a/src/script/sign.cpp b/src/script/sign.cpp index c646752f56..9e1577fe39 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -89,6 +89,7 @@ static bool SignStep(const BaseSignatureCreator& creator, const CScript& scriptP keyID = CPubKey(vSolutions[0]).GetID(); return Sign1(keyID, creator, scriptPubKey, ret, sigversion); case TX_PUBKEYHASH: + case TX_EXCHANGEADDRESS: keyID = CKeyID(uint160(vSolutions[0])); if (!Sign1(keyID, creator, scriptPubKey, ret, sigversion)) return false; @@ -325,6 +326,7 @@ static Stacks CombineSignatures(const CScript& scriptPubKey, const BaseSignature return sigs2; case TX_PUBKEY: case TX_PUBKEYHASH: + case TX_EXCHANGEADDRESS: // Signatures are bigger than placeholders or empty scripts: if (sigs1.script.empty() || sigs1.script[0].empty()) return sigs2; diff --git a/src/script/standard.cpp b/src/script/standard.cpp index c44fca6a13..8af564f37a 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -37,6 +37,7 @@ const char* GetTxnOutputType(txnouttype t) case TX_LELANTUSJMINT: return "lelantusmint"; case TX_SPARKMINT: return "sparkmint"; case TX_SPARKSMINT: return "sparksmint"; + case TX_EXCHANGEADDRESS: return "exchangeaddress"; } return NULL; } @@ -56,6 +57,9 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector hashBytes(scriptPubKey.begin()+4, scriptPubKey.begin()+24); + vSolutionsRet.push_back(hashBytes); + return true; + } + // Zerocoin if (scriptPubKey.IsZerocoinMint()) { @@ -266,6 +278,11 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) addressRet = CKeyID(uint160(vSolutions[0])); return true; } + else if (whichType == TX_EXCHANGEADDRESS) + { + addressRet = CExchangeKeyID(uint160(vSolutions[0])); + return true; + } else if (whichType == TX_SCRIPTHASH) { addressRet = CScriptID(uint160(vSolutions[0])); @@ -335,6 +352,12 @@ class CScriptVisitor : public boost::static_visitor return true; } + bool operator()(const CExchangeKeyID &keyID) const { + script->clear(); + *script << OP_EXCHANGEADDR << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG; + return true; + } + bool operator()(const CScriptID &scriptID) const { script->clear(); *script << OP_HASH160 << ToByteVector(scriptID) << OP_EQUAL; diff --git a/src/script/standard.h b/src/script/standard.h index 7840a0170b..4c49266d83 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -58,7 +58,8 @@ enum txnouttype TX_LELANTUSMINT, TX_LELANTUSJMINT, TX_SPARKMINT, - TX_SPARKSMINT + TX_SPARKSMINT, + TX_EXCHANGEADDRESS }; class CNoDestination { @@ -72,9 +73,10 @@ class CNoDestination { * * CNoDestination: no destination set * * CKeyID: TX_PUBKEYHASH destination * * CScriptID: TX_SCRIPTHASH destination + * * CExchangeKeyID: CKeyID for exchange key * A CTxDestination is the internal data type encoded in a CBitcoinAddress */ -typedef boost::variant CTxDestination; +typedef boost::variant CTxDestination; const char* GetTxnOutputType(txnouttype t); diff --git a/src/sigma.cpp b/src/sigma.cpp index ae32d83b3a..5b9d27b111 100644 --- a/src/sigma.cpp +++ b/src/sigma.cpp @@ -633,15 +633,17 @@ bool ConnectBlockSigma( )) { return false; } + } - if (!fJustCheck) { + if (!fJustCheck) { + BOOST_FOREACH(auto& serial, pblock->sigmaTxInfo->spentSerials) { pindexNew->sigmaSpentSerials.insert(serial); sigmaState.AddSpend(serial.first, serial.second.denomination, serial.second.coinGroupId); } } - - if (fJustCheck) + else { return true; + } sigmaState.AddMintsToStateAndBlockIndex(pindexNew, pblock); } diff --git a/src/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 70f28b2747..98ebae8412 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -1233,6 +1233,10 @@ CWalletTx CSparkWallet::CreateSparkSpendTransaction( for (size_t i = 0; i < recipients.size(); i++) { auto& recipient = recipients[i]; + if (recipient.scriptPubKey.IsPayToExchangeAddress()) { + throw std::runtime_error("Cannot create private transaction with exchange address as a destination"); + } + if (!MoneyRange(recipient.nAmount)) { throw std::runtime_error(boost::str(boost::format(_("Recipient has invalid amount")) % i)); } diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index 4539a0c567..5d9ddbf81a 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -114,6 +114,10 @@ class TestAddrTypeVisitor : public boost::static_visitor { return (exp_addrType == "pubkey"); } + bool operator()(const CExchangeKeyID &id) const + { + return (exp_addrType == "exchangepubkey"); + } bool operator()(const CScriptID &id) const { return (exp_addrType == "script"); diff --git a/src/test/data/script_tests.json b/src/test/data/script_tests.json index 3c6cb903a7..787f69bbb9 100644 --- a/src/test/data/script_tests.json +++ b/src/test/data/script_tests.json @@ -270,7 +270,7 @@ ["0", "IF 0xdd ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xde ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xdf ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], -["0", "IF 0xe0 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF 0xe1 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xe2 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xe3 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], @@ -908,7 +908,6 @@ ["1", "IF 0xdd ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xde ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xdf ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], -["1", "IF 0xe0 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xe1 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xe2 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xe3 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], diff --git a/src/validation.cpp b/src/validation.cpp index 368bacba37..65552a72c2 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -145,6 +145,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 { @@ -337,6 +338,74 @@ 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; + } + + 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. @@ -639,6 +708,28 @@ bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fChe } } + // input scripts cannot have OP_EXCHANGEADDR at all + for (const auto &vin: tx.vin) { + if (vin.scriptSig.size() >= 1 && vin.scriptSig[0] == OP_EXCHANGEADDR) { + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); + } + } + + bool hasExchangeUTXOs = false; + for (const auto &vout : tx.vout) { + if (vout.scriptPubKey.size() >= 1 && vout.scriptPubKey[0] == OP_EXCHANGEADDR) { + hasExchangeUTXOs = true; + break; + } + } + int nTxHeight = nHeight; + if (nTxHeight == INT_MAX) { + LOCK(cs_main); + nTxHeight = chainActive.Height(); + } + if (hasExchangeUTXOs && !isVerifyDB && nTxHeight < ::Params().GetConsensus().nExchangeAddressStartBlock) + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); + if (tx.IsCoinBase()) { size_t minCbSize = 2; @@ -648,6 +739,8 @@ bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fChe } if (tx.vin[0].scriptSig.size() < minCbSize || tx.vin[0].scriptSig.size() > 100) return state.DoS(100, false, REJECT_INVALID, "bad-cb-length"); + if (hasExchangeUTXOs) + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); } else { @@ -659,16 +752,22 @@ bool CheckTransaction(const CTransaction &tx, CValidationState &state, bool fChe return state.DoS(10, false, REJECT_INVALID, "bad-txns-prevout-null"); if (tx.IsZerocoinV3SigmaTransaction()) { + if (hasExchangeUTXOs) + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); if (!CheckSigmaTransaction(tx, state, hashTx, isVerifyDB, nHeight, isCheckWallet, fStatefulZerocoinCheck, sigmaTxInfo)) return false; } if (tx.IsLelantusTransaction()) { + if (hasExchangeUTXOs) + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); if (!CheckLelantusTransaction(tx, state, hashTx, isVerifyDB, nHeight, isCheckWallet, fStatefulZerocoinCheck, sigmaTxInfo, lelantusTxInfo)) return false; } if (tx.IsSparkTransaction()) { + if (hasExchangeUTXOs) + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); if (!CheckSparkTransaction(tx, state, hashTx, isVerifyDB, nHeight, isCheckWallet, fStatefulZerocoinCheck, sparkTxInfo)) return false; } diff --git a/src/validation.h b/src/validation.h index 33f0eefb95..36051d4a93 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/lelantusjoinsplitbuilder.cpp b/src/wallet/lelantusjoinsplitbuilder.cpp index b49dd733af..21ab5bf082 100644 --- a/src/wallet/lelantusjoinsplitbuilder.cpp +++ b/src/wallet/lelantusjoinsplitbuilder.cpp @@ -59,6 +59,10 @@ CWalletTx LelantusJoinSplitBuilder::Build( for (size_t i = 0; i < recipients.size(); i++) { auto& recipient = recipients[i]; + if (recipient.scriptPubKey.IsPayToExchangeAddress()) { + throw std::runtime_error("Cannot create private transaction with exchange address as a destination"); + } + if (!MoneyRange(recipient.nAmount)) { throw std::runtime_error(boost::str(boost::format(_("Recipient has invalid amount")) % i)); } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 66a7d92214..c1645320e0 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -208,6 +208,59 @@ UniValue getnewaddress(const JSONRPCRequest& request) return CBitcoinAddress(keyID).ToString(); } +UniValue getnewexchangeaddress(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(""); + + LOCK2(cs_main, pwallet->cs_wallet); + + if (!pwallet->IsLocked()) { + pwallet->TopUpKeyPool(); + } + + // Generate a new key or use existing one and convert it to exchange address format + CKeyID keyID; + if (request.params.size() == 0) { + CPubKey newKey; + if (!pwallet->GetKeyFromPool(newKey)) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + } + keyID = newKey.GetID(); + pwallet->SetAddressBook(keyID, "", "receive"); + } + else { + // out of four tx destinations types only CKeyID (P2PKH) is supported here + class CTxDestinationVisitor : public boost::static_visitor { + public: + CTxDestinationVisitor() {} + CKeyID operator() (const CNoDestination&) const {return CKeyID();} + CKeyID operator() (const CKeyID& keyID) const {return keyID;} + CKeyID operator() (const CExchangeKeyID&) const {return CKeyID();} + CKeyID operator() (const CScriptID&) const {return CKeyID();} + }; + + CBitcoinAddress existingKey(request.params[0].get_str()); + if (!existingKey.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Firo address"); + } + + keyID = boost::apply_visitor(CTxDestinationVisitor(), existingKey.Get()); + if (keyID.IsNull()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Must be P2PKH address"); + } + } + + CBitcoinAddress newAddress; + newAddress.SetExchange(keyID); + + return newAddress.ToString(); +} CBitcoinAddress GetAccountAddress(CWallet * const pwallet, std::string strAccount, bool bForceNew) { @@ -693,6 +746,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); @@ -1269,6 +1368,11 @@ class Witnessifier : public boost::static_visitor return false; } + bool operator()(const CExchangeKeyID &/*keyID*/) { + // can't witnessify this + return false; + } + bool operator()(const CScriptID &scriptID) { CScript subscript; if (pwallet && pwallet->GetCScript(scriptID, subscript)) { @@ -5502,6 +5606,7 @@ static const CRPCCommand rpcCommands[] = { "wallet", "getprivatebalance", &getprivatebalance, false, {} }, { "wallet", "gettotalbalance", &gettotalbalance, false, {} }, { "wallet", "getnewaddress", &getnewaddress, true, {"account"} }, + { "hidden", "getnewexchangeaddress", &getnewexchangeaddress, true, {} }, { "wallet", "getrawchangeaddress", &getrawchangeaddress, true, {} }, { "wallet", "getreceivedbyaccount", &getreceivedbyaccount, false, {"account","minconf","addlocked"} }, { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, false, {"address","minconf","addlocked"} }, @@ -5532,6 +5637,7 @@ static const CRPCCommand rpcCommands[] = { "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 5b92568f32..ed4b3fbfac 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -162,6 +162,11 @@ class CAffectedKeysVisitor : public boost::static_visitor { vKeys.push_back(keyId); } + void operator()(const CExchangeKeyID &keyId) { + if (keystore.HaveKey(keyId)) + vKeys.push_back(keyId); + } + void operator()(const CScriptID &scriptId) { CScript script; if (keystore.GetCScript(scriptId, script)) @@ -3669,6 +3674,67 @@ 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(); + uint32_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++; + } + } 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; { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index bd253f7837..af623b402d 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1017,6 +1017,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();