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 d14b4a9d3e..589d2f199f 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 8c522e8c8a..851c0ff019 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/consensus/params.h b/src/consensus/params.h index fd19b1c63f..1db0a6ac3c 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -333,6 +333,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 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; 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 7a06a92603..5f7a5951ad 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 @@ -251,6 +253,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 c67f0068a6..3462581956 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); @@ -660,6 +663,7 @@ class CScript : public CScriptBase bool IsNormalPaymentScript() 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/spark/sparkwallet.cpp b/src/spark/sparkwallet.cpp index 36c1c7cdbe..a6199008b7 100644 --- a/src/spark/sparkwallet.cpp +++ b/src/spark/sparkwallet.cpp @@ -1230,6 +1230,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 28ce36216b..f7b5db1653 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -704,6 +704,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 && !isCheckWallet && !isVerifyDB && nTxHeight < ::Params().GetConsensus().nExchangeAddressStartBlock) + return state.DoS(100, false, REJECT_INVALID, "bad-exchange-address"); + if (tx.IsCoinBase()) { size_t minCbSize = 2; @@ -713,6 +735,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 { @@ -724,16 +748,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/wallet/lelantusjoinsplitbuilder.cpp b/src/wallet/lelantusjoinsplitbuilder.cpp index ebb0666ec5..b294bd3900 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 1e29a7cd30..61564146fe 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -209,6 +209,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=false) { @@ -1302,6 +1355,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)) { @@ -5535,6 +5593,7 @@ static const CRPCCommand commands[] = { "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"} }, diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 3af16f9b13..cd599b771c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -149,6 +149,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))