void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry) { entry.pushKV("txid", tx.GetHash().GetHex()); entry.pushKV("version", tx.nVersion); entry.pushKV("locktime", (int64_t)tx.nLockTime); UniValue vin(UniValue::VARR); BOOST_FOREACH(const CTxIn& txin, tx.vin) { UniValue in(UniValue::VOBJ); if (tx.IsCoinBase()) in.pushKV("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())); else { in.pushKV("txid", txin.prevout.hash.GetHex()); in.pushKV("vout", (int64_t)txin.prevout.n); UniValue o(UniValue::VOBJ); o.pushKV("asm", txin.scriptSig.ToString()); o.pushKV("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())); in.pushKV("scriptSig", o); } in.pushKV("sequence", (int64_t)txin.nSequence); vin.push_back(in); } entry.pushKV("vin", vin); UniValue vout(UniValue::VARR); for (unsigned int i = 0; i < tx.vout.size(); i++) { const CTxOut& txout = tx.vout[i]; UniValue out(UniValue::VOBJ); UniValue outValue(UniValue::VNUM, FormatMoney(txout.nValue)); out.pushKV("value", outValue); out.pushKV("n", (int64_t)i); UniValue o(UniValue::VOBJ); ScriptPubKeyToUniv(txout.scriptPubKey, o, true); out.pushKV("scriptPubKey", o); vout.push_back(out); } entry.pushKV("vout", vout); if (!hashBlock.IsNull()) entry.pushKV("blockhash", hashBlock.GetHex()); entry.pushKV("hex", EncodeHexTx(tx)); // the hex-encoded transaction. used the name "hex" to be consistent with the verbose output of "getrawtransaction". }
bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { CDBBatch batch(db); size_t count = 0; size_t changed = 0; for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) { if (it->second.flags & CCoinsCacheEntry::DIRTY) { if (it->second.coins.IsPruned()) batch.Erase(std::make_pair(DB_COINS, it->first)); else batch.Write(std::make_pair(DB_COINS, it->first), it->second.coins); changed++; } count++; CCoinsMap::iterator itOld = it++; mapCoins.erase(itOld); } if (!hashBlock.IsNull()) batch.Write(DB_BEST_BLOCK, hashBlock); LogPrint("coindb", "Committing %u changed transactions (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count); return db.WriteBatch(batch); }
void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) { // Call into TxToUniv() in bitcoin-common to decode the transaction hex. // // Blockchain contextual information (confirmations and blocktime) is not // available to code in bitcoin-common, so we query them here and push the // data into the returned UniValue. TxToUniv(tx, uint256(), entry, true, RPCSerializationFlags()); if (!hashBlock.IsNull()) { entry.push_back(Pair("blockhash", hashBlock.GetHex())); BlockMap::iterator mi = mapBlockIndex.find(hashBlock); if (mi != mapBlockIndex.end() && (*mi).second) { CBlockIndex* pindex = (*mi).second; if (chainActive.Contains(pindex)) { entry.push_back(Pair("confirmations", 1 + chainActive.Height() - pindex->nHeight)); entry.push_back(Pair("time", pindex->GetBlockTime())); entry.push_back(Pair("blocktime", pindex->GetBlockTime())); } else entry.push_back(Pair("confirmations", 0)); } } }
void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) { entry.push_back(Pair("txid", tx.GetHash().GetHex())); entry.push_back(Pair("hash", tx.GetWitnessHash().GetHex())); entry.push_back(Pair("size", (int)::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION))); entry.push_back(Pair("vsize", (int)::GetVirtualTransactionSize(tx))); entry.push_back(Pair("version", tx.nVersion)); entry.push_back(Pair("locktime", (int64_t)tx.nLockTime)); UniValue vin(UniValue::VARR); for (unsigned int i = 0; i < tx.vin.size(); i++) { const CTxIn& txin = tx.vin[i]; UniValue in(UniValue::VOBJ); if (tx.IsCoinBase()) in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); else { in.push_back(Pair("txid", txin.prevout.hash.GetHex())); in.push_back(Pair("vout", (int64_t)txin.prevout.n)); UniValue o(UniValue::VOBJ); o.push_back(Pair("asm", ScriptToAsmStr(txin.scriptSig, true))); o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); in.push_back(Pair("scriptSig", o)); } if (!tx.wit.IsNull()) { if (!tx.wit.vtxinwit[i].IsNull()) { UniValue txinwitness(UniValue::VARR); for (unsigned int j = 0; j < tx.wit.vtxinwit[i].scriptWitness.stack.size(); j++) { std::vector<unsigned char> item = tx.wit.vtxinwit[i].scriptWitness.stack[j]; txinwitness.push_back(HexStr(item.begin(), item.end())); } in.push_back(Pair("txinwitness", txinwitness)); } } in.push_back(Pair("sequence", (int64_t)txin.nSequence)); vin.push_back(in); } entry.push_back(Pair("vin", vin)); UniValue vout(UniValue::VARR); for (unsigned int i = 0; i < tx.vout.size(); i++) { const CTxOut& txout = tx.vout[i]; UniValue out(UniValue::VOBJ); out.push_back(Pair("value", ValueFromAmount(txout.nValue))); out.push_back(Pair("n", (int64_t)i)); UniValue o(UniValue::VOBJ); ScriptPubKeyToJSON(txout.scriptPubKey, o, true); out.push_back(Pair("scriptPubKey", o)); vout.push_back(out); } entry.push_back(Pair("vout", vout)); if (!hashBlock.IsNull()) { entry.push_back(Pair("blockhash", hashBlock.GetHex())); BlockMap::iterator mi = mapBlockIndex.find(hashBlock); if (mi != mapBlockIndex.end() && (*mi).second) { CBlockIndex* pindex = (*mi).second; if (chainActive.Contains(pindex)) { entry.push_back(Pair("confirmations", 1 + chainActive.Height() - pindex->nHeight)); entry.push_back(Pair("time", pindex->GetBlockTime())); entry.push_back(Pair("blocktime", pindex->GetBlockTime())); } else entry.push_back(Pair("confirmations", 0)); } } }
UniValue name_pending (const UniValue& params, bool fHelp) { if (fHelp || params.size () > 1) throw std::runtime_error ( "name_pending (\"name\")\n" "\nList unconfirmed name operations in the mempool.\n" "\nIf a name is given, only check for operations on this name.\n" "\nArguments:\n" "1. \"name\" (string, optional) only look for this name\n" "\nResult:\n" "[\n" " {\n" " \"op\": xxxx (string) the operation being performed\n" " \"name\": xxxx (string) the name operated on\n" " \"value\": xxxx (string) the name's new value\n" " \"txid\": xxxx (string) the txid corresponding to the operation\n" " \"ismine\": xxxx (boolean) whether the name is owned by the wallet\n" " },\n" " ...\n" "]\n" + HelpExampleCli ("name_pending", "") + HelpExampleCli ("name_pending", "\"d/domob\"") + HelpExampleRpc ("name_pending", "") ); #ifdef ENABLE_WALLET LOCK2 (pwalletMain ? &pwalletMain->cs_wallet : NULL, mempool.cs); #else LOCK (mempool.cs); #endif std::vector<uint256> txHashes; if (params.size () == 0) mempool.queryHashes (txHashes); else { const std::string name = params[0].get_str (); const valtype vchName = ValtypeFromString (name); const uint256 txid = mempool.getTxForName (vchName); if (!txid.IsNull ()) txHashes.push_back (txid); } UniValue arr(UniValue::VARR); for (std::vector<uint256>::const_iterator i = txHashes.begin (); i != txHashes.end (); ++i) { std::shared_ptr<const CTransaction> tx = mempool.get (*i); if (!tx || !tx->IsNamecoin ()) continue; for (const auto& txOut : tx->vout) { const CNameScript op(txOut.scriptPubKey); if (!op.isNameOp () || !op.isAnyUpdate ()) continue; const valtype vchName = op.getOpName (); const valtype vchValue = op.getOpValue (); const std::string name = ValtypeToString (vchName); const std::string value = ValtypeToString (vchValue); std::string strOp; switch (op.getNameOp ()) { case OP_NAME_FIRSTUPDATE: strOp = "name_firstupdate"; break; case OP_NAME_UPDATE: strOp = "name_update"; break; default: assert (false); } UniValue obj(UniValue::VOBJ); obj.push_back (Pair ("op", strOp)); obj.push_back (Pair ("name", name)); obj.push_back (Pair ("value", value)); obj.push_back (Pair ("txid", tx->GetHash ().GetHex ())); #ifdef ENABLE_WALLET isminetype mine = ISMINE_NO; if (pwalletMain) mine = IsMine (*pwalletMain, op.getAddress ()); const bool isMine = (mine & ISMINE_SPENDABLE); obj.push_back (Pair ("ismine", isMine)); #endif arr.push_back (obj); } } return arr; }
bool CCoinsViewDB::ValidateNameDB(CGameDB& gameDb) const { /* Skip for genesis block, since there is no game state available yet (test would fail below). There's not really anything to verify for the genesis block anyway. */ const uint256 blockHash = GetBestBlock(); if (blockHash.IsNull()) return true; /* It seems that there are no "const iterators" for LevelDB. Since we only need read operations on it, use a const-cast to get around that restriction. */ boost::scoped_ptr<CDBIterator> pcursor(const_cast<CDBWrapper*>(&db)->NewIterator()); pcursor->SeekToFirst(); /* Loop over the total database and read interesting things to memory. We later use that to check everything against each other. */ std::set<valtype> namesTotal; std::set<valtype> namesInDB; std::set<valtype> namesWithHistory; std::map<valtype, CAmount> namesInUTXO; for (; pcursor->Valid(); pcursor->Next()) { boost::this_thread::interruption_point(); char chType; if (!pcursor->GetKey(chType)) continue; switch (chType) { case DB_COINS: { CCoins coins; if (!pcursor->GetValue(coins)) return error("%s : failed to read coins", __func__); BOOST_FOREACH(const CTxOut& txout, coins.vout) if (!txout.IsNull()) { const CNameScript nameOp(txout.scriptPubKey); if (nameOp.isNameOp() && nameOp.isAnyUpdate()) { const valtype& name = nameOp.getOpName(); if (namesInUTXO.count(name) > 0) return error("%s : name %s duplicated in UTXO set", __func__, ValtypeToString(name).c_str()); namesInUTXO.insert(std::make_pair(nameOp.getOpName(), txout.nValue)); } } break; } case DB_NAME: { std::pair<char, valtype> key; if (!pcursor->GetKey(key) || key.first != DB_NAME) return error("%s : failed to read DB_NAME key", __func__); const valtype& name = key.second; CNameData data; if (!pcursor->GetValue(data)) return error("%s : failed to read name value", __func__); if (namesTotal.count(name) > 0) return error("%s : name %s duplicated in name index", __func__, ValtypeToString(name).c_str()); namesTotal.insert(name); assert(namesInDB.count(name) == 0); if (!data.isDead ()) namesInDB.insert(name); break; } case DB_NAME_HISTORY: { std::pair<char, valtype> key; if (!pcursor->GetKey(key) || key.first != DB_NAME_HISTORY) return error("%s : failed to read DB_NAME_HISTORY key", __func__); const valtype& name = key.second; if (namesWithHistory.count(name) > 0) return error("%s : name %s has duplicate history", __func__, ValtypeToString(name).c_str()); namesWithHistory.insert(name); break; } default: break; } } std::map<valtype, CAmount> namesInGame; GameState state(Params().GetConsensus()); if (!gameDb.get(blockHash, state)) return error("%s : failed to read game state", __func__); for (PlayerStateMap::const_iterator mi = state.players.begin(); mi != state.players.end(); ++mi) { const valtype cur = ValtypeFromString(mi->first); if (namesInGame.count(cur) > 0) return error("%s : name %s is duplicate in the game state", __func__, mi->first.c_str()); namesInGame.insert(std::make_pair(cur, mi->second.lockedCoins)); } /* Now verify the collected data. */ assert (namesTotal.size() >= namesInDB.size()); if (namesInGame != namesInUTXO) return error("%s : game state and name DB mismatch", __func__); BOOST_FOREACH(const valtype& name, namesInDB) if (namesInUTXO.count(name) == 0) return error("%s : name '%s' in DB but not UTXO set", __func__, ValtypeToString(name).c_str()); BOOST_FOREACH(const PAIRTYPE(valtype, CAmount)& pair, namesInUTXO) if (namesInDB.count(pair.first) == 0) return error("%s : name '%s' in UTXO set but not DB", __func__, ValtypeToString(pair.first).c_str()); if (fNameHistory) { BOOST_FOREACH(const valtype& name, namesWithHistory) if (namesTotal.count(name) == 0) return error("%s : history entry for name '%s' not in main DB", __func__, ValtypeToString(name).c_str()); } else if (!namesWithHistory.empty ())
int populateRPCTransactionObject(const CTransaction& tx, const uint256& blockHash, UniValue& txobj, std::string filterAddress, bool extendedDetails, std::string extendedDetailsFilter, int blockHeight) { int confirmations = 0; int64_t blockTime = 0; int positionInBlock = 0; if (blockHeight == 0) { blockHeight = GetHeight(); } if (!blockHash.IsNull()) { CBlockIndex* pBlockIndex = GetBlockIndex(blockHash); if (NULL != pBlockIndex) { confirmations = 1 + blockHeight - pBlockIndex->nHeight; blockTime = pBlockIndex->nTime; blockHeight = pBlockIndex->nHeight; } } // attempt to parse the transaction CMPTransaction mp_obj; int parseRC = ParseTransaction(tx, blockHeight, 0, mp_obj, blockTime); if (parseRC < 0) return MP_TX_IS_NOT_MASTER_PROTOCOL; const uint256& txid = tx.GetHash(); // DEx BTC payment needs special handling since it's not actually an Omni message - handle and return if (parseRC > 0) { if (confirmations <= 0) { // only confirmed DEx payments are currently supported return MP_TX_UNCONFIRMED; } std::string tmpBuyer, tmpSeller; uint64_t tmpVout, tmpNValue, tmpPropertyId; { LOCK(cs_tally); p_txlistdb->getPurchaseDetails(txid, 1, &tmpBuyer, &tmpSeller, &tmpVout, &tmpPropertyId, &tmpNValue); } UniValue purchases(UniValue::VARR); if (populateRPCDExPurchases(tx, purchases, filterAddress) <= 0) return -1; txobj.push_back(Pair("txid", txid.GetHex())); txobj.push_back(Pair("type", "DEx Purchase")); txobj.push_back(Pair("sendingaddress", tmpBuyer)); txobj.push_back(Pair("purchases", purchases)); txobj.push_back(Pair("blockhash", blockHash.GetHex())); txobj.push_back(Pair("blocktime", blockTime)); txobj.push_back(Pair("block", blockHeight)); txobj.push_back(Pair("confirmations", confirmations)); return 0; } // check if we're filtering from listtransactions_MP, and if so whether we have a non-match we want to skip if (!filterAddress.empty() && mp_obj.getSender() != filterAddress && mp_obj.getReceiver() != filterAddress) return -1; // parse packet and populate mp_obj if (!mp_obj.interpret_Transaction()) return MP_TX_IS_NOT_MASTER_PROTOCOL; // obtain validity - only confirmed transactions can be valid bool valid = false; if (confirmations > 0) { LOCK(cs_tally); valid = getValidMPTX(txid); positionInBlock = p_OmniTXDB->FetchTransactionPosition(txid); } // populate some initial info for the transaction bool fMine = false; if (IsMyAddress(mp_obj.getSender()) || IsMyAddress(mp_obj.getReceiver())) fMine = true; txobj.push_back(Pair("txid", txid.GetHex())); txobj.push_back(Pair("fee", FormatDivisibleMP(mp_obj.getFeePaid()))); txobj.push_back(Pair("sendingaddress", mp_obj.getSender())); if (showRefForTx(mp_obj.getType())) txobj.push_back(Pair("referenceaddress", mp_obj.getReceiver())); txobj.push_back(Pair("ismine", fMine)); txobj.push_back(Pair("version", (uint64_t)mp_obj.getVersion())); txobj.push_back(Pair("type_int", (uint64_t)mp_obj.getType())); if (mp_obj.getType() != MSC_TYPE_SIMPLE_SEND) { // Type 0 will add "Type" attribute during populateRPCTypeSimpleSend txobj.push_back(Pair("type", mp_obj.getTypeString())); } // populate type specific info and extended details if requested // extended details are not available for unconfirmed transactions if (confirmations <= 0) extendedDetails = false; populateRPCTypeInfo(mp_obj, txobj, mp_obj.getType(), extendedDetails, extendedDetailsFilter, confirmations); // state and chain related information if (confirmations != 0 && !blockHash.IsNull()) { txobj.push_back(Pair("valid", valid)); if (!valid) { txobj.push_back(Pair("invalidreason", p_OmniTXDB->FetchInvalidReason(txid))); } txobj.push_back(Pair("blockhash", blockHash.GetHex())); txobj.push_back(Pair("blocktime", blockTime)); txobj.push_back(Pair("positioninblock", positionInBlock)); } if (confirmations != 0) { txobj.push_back(Pair("block", blockHeight)); } txobj.push_back(Pair("confirmations", confirmations)); // finished return 0; }
bool CCoinsViewDB::ValidateNameDB() const { const uint256 blockHash = GetBestBlock(); int nHeight; if (blockHash.IsNull()) nHeight = 0; else nHeight = mapBlockIndex.find(blockHash)->second->nHeight; /* It seems that there are no "const iterators" for LevelDB. Since we only need read operations on it, use a const-cast to get around that restriction. */ boost::scoped_ptr<CDBIterator> pcursor(const_cast<CDBWrapper*>(&db)->NewIterator()); pcursor->SeekToFirst(); /* Loop over the total database and read interesting things to memory. We later use that to check everything against each other. */ std::map<valtype, unsigned> nameHeightsIndex; std::map<valtype, unsigned> nameHeightsData; std::set<valtype> namesInDB; std::set<valtype> namesInUTXO; std::set<valtype> namesWithHistory; for (; pcursor->Valid(); pcursor->Next()) { boost::this_thread::interruption_point(); char chType; if (!pcursor->GetKey(chType)) continue; switch (chType) { case DB_COINS: { CCoins coins; if (!pcursor->GetValue(coins)) return error("%s : failed to read coins", __func__); BOOST_FOREACH(const CTxOut& txout, coins.vout) if (!txout.IsNull()) { const CNameScript nameOp(txout.scriptPubKey); if (nameOp.isNameOp() && nameOp.isAnyUpdate()) { const valtype& name = nameOp.getOpName(); if (namesInUTXO.count(name) > 0) return error("%s : name %s duplicated in UTXO set", __func__, ValtypeToString(name).c_str()); namesInUTXO.insert(nameOp.getOpName()); } } break; } case DB_NAME: { std::pair<char, valtype> key; if (!pcursor->GetKey(key) || key.first != DB_NAME) return error("%s : failed to read DB_NAME key", __func__); const valtype& name = key.second; CNameData data; if (!pcursor->GetValue(data)) return error("%s : failed to read name value", __func__); if (nameHeightsData.count(name) > 0) return error("%s : name %s duplicated in name index", __func__, ValtypeToString(name).c_str()); nameHeightsData.insert(std::make_pair(name, data.getHeight())); /* Expiration is checked at height+1, because that matches how the UTXO set is cleared in ExpireNames. */ assert(namesInDB.count(name) == 0); if (!data.isExpired(nHeight + 1)) namesInDB.insert(name); break; } case DB_NAME_HISTORY: { std::pair<char, valtype> key; if (!pcursor->GetKey(key) || key.first != DB_NAME_HISTORY) return error("%s : failed to read DB_NAME_HISTORY key", __func__); const valtype& name = key.second; if (namesWithHistory.count(name) > 0) return error("%s : name %s has duplicate history", __func__, ValtypeToString(name).c_str()); namesWithHistory.insert(name); break; } case DB_NAME_EXPIRY: { std::pair<char, CNameCache::ExpireEntry> key; if (!pcursor->GetKey(key) || key.first != DB_NAME_EXPIRY) return error("%s : failed to read DB_NAME_EXPIRY key", __func__); const CNameCache::ExpireEntry& entry = key.second; const valtype& name = entry.name; if (nameHeightsIndex.count(name) > 0) return error("%s : name %s duplicated in expire idnex", __func__, ValtypeToString(name).c_str()); nameHeightsIndex.insert(std::make_pair(name, entry.nHeight)); break; } default: break; } } /* Now verify the collected data. */ assert (nameHeightsData.size() >= namesInDB.size()); if (nameHeightsIndex != nameHeightsData) return error("%s : name height data mismatch", __func__); BOOST_FOREACH(const valtype& name, namesInDB) if (namesInUTXO.count(name) == 0) return error("%s : name '%s' in DB but not UTXO set", __func__, ValtypeToString(name).c_str()); BOOST_FOREACH(const valtype& name, namesInUTXO) if (namesInDB.count(name) == 0) return error("%s : name '%s' in UTXO set but not DB", __func__, ValtypeToString(name).c_str()); if (fNameHistory) { BOOST_FOREACH(const valtype& name, namesWithHistory) if (nameHeightsData.count(name) == 0) return error("%s : history entry for name '%s' not in main DB", __func__, ValtypeToString(name).c_str()); } else if (!namesWithHistory.empty ())
UniValue AsyncRPCOperation_sendmany::perform_joinsplit( AsyncJoinSplitInfo & info, std::vector<boost::optional < ZCIncrementalWitness>> witnesses, uint256 anchor) { if (anchor.IsNull()) { throw std::runtime_error("anchor is null"); } if (!(witnesses.size() == info.notes.size())) { throw runtime_error("number of notes and witnesses do not match"); } for (size_t i = 0; i < witnesses.size(); i++) { if (!witnesses[i]) { throw runtime_error("joinsplit input could not be found in tree"); } info.vjsin.push_back(JSInput(*witnesses[i], info.notes[i], boost::get<libzcash::SproutSpendingKey>(spendingkey_))); } // Make sure there are two inputs and two outputs while (info.vjsin.size() < ZC_NUM_JS_INPUTS) { info.vjsin.push_back(JSInput()); } while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) { info.vjsout.push_back(JSOutput()); } if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) { throw runtime_error("unsupported joinsplit input/output counts"); } CMutableTransaction mtx(tx_); LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n", getId(), tx_.vjoinsplit.size(), FormatMoney(info.vpub_old), FormatMoney(info.vpub_new), FormatMoney(info.vjsin[0].note.value()), FormatMoney(info.vjsin[1].note.value()), FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value) ); // Generate the proof, this can take over a minute. std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs {info.vjsin[0], info.vjsin[1]}; std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs {info.vjsout[0], info.vjsout[1]}; std::array<size_t, ZC_NUM_JS_INPUTS> inputMap; std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap; uint256 esk; // payment disclosure - secret JSDescription jsdesc = JSDescription::Randomized( mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION), *pzcashParams, joinSplitPubKey_, anchor, inputs, outputs, inputMap, outputMap, info.vpub_old, info.vpub_new, !this->testmode, &esk); // parameter expects pointer to esk, so pass in address { auto verifier = libzcash::ProofVerifier::Strict(); if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) { throw std::runtime_error("error verifying joinsplit"); } } mtx.vjoinsplit.push_back(jsdesc); // Empty output script. CScript scriptCode; CTransaction signTx(mtx); uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId_); // Add the signature if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL, dataToBeSigned.begin(), 32, joinSplitPrivKey_ ) == 0)) { throw std::runtime_error("crypto_sign_detached failed"); } // Sanity check if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0], dataToBeSigned.begin(), 32, mtx.joinSplitPubKey.begin() ) == 0)) { throw std::runtime_error("crypto_sign_verify_detached failed"); } CTransaction rawTx(mtx); tx_ = rawTx; CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); ss << rawTx; std::string encryptedNote1; std::string encryptedNote2; { CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION); ss2 << ((unsigned char) 0x00); ss2 << jsdesc.ephemeralKey; ss2 << jsdesc.ciphertexts[0]; ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_); encryptedNote1 = HexStr(ss2.begin(), ss2.end()); } { CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION); ss2 << ((unsigned char) 0x01); ss2 << jsdesc.ephemeralKey; ss2 << jsdesc.ciphertexts[1]; ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_); encryptedNote2 = HexStr(ss2.begin(), ss2.end()); } UniValue arrInputMap(UniValue::VARR); UniValue arrOutputMap(UniValue::VARR); for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) { arrInputMap.push_back(static_cast<uint64_t>(inputMap[i])); } for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) { arrOutputMap.push_back(static_cast<uint64_t>(outputMap[i])); } // !!! Payment disclosure START unsigned char buffer[32] = {0}; memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32); uint256 joinSplitPrivKey = uint256(vch); size_t js_index = tx_.vjoinsplit.size() - 1; uint256 placeholder; for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) { uint8_t mapped_index = outputMap[i]; // placeholder for txid will be filled in later when tx has been finalized and signed. PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index}; JSOutput output = outputs[mapped_index]; libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr}; paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo)); LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), EncodePaymentAddress(zaddr)); } // !!! Payment disclosure END UniValue obj(UniValue::VOBJ); obj.push_back(Pair("encryptednote1", encryptedNote1)); obj.push_back(Pair("encryptednote2", encryptedNote2)); obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end()))); obj.push_back(Pair("inputmap", arrInputMap)); obj.push_back(Pair("outputmap", arrOutputMap)); return obj; }