// Offline NRB key generation and transparent transaction signing. // Build with the same flags and libraries as nerbus-tx. No RPC or node startup. #define main NerbusLegacyTransactionMain #include "nerbus-original-tx.inc" #undef main #include #include static const int64_t WALLET_MAX_MONEY = 209000000LL * COIN; static CAmount WalletAmount(const UniValue &v) { if (!v.isStr()) throw std::runtime_error("Amount must be a decimal string"); CAmount n; if (!ParseMoney(v.get_str(), n) || n < 0 || n > WALLET_MAX_MONEY) throw std::runtime_error("Invalid NRB amount"); return n; } static UniValue WalletKey(const CKey &key) { if (!key.IsValid() || !key.IsCompressed()) throw std::runtime_error("A compressed NRB private key is required"); UniValue result(UniValue::VOBJ); result.pushKV("address", EncodeDestination(key.GetPubKey().GetID())); result.pushKV("privateKey", EncodeSecret(key)); return result; } static UniValue WalletPayment(const UniValue &request) { const auto &inputs = request["inputs"]; const auto &keys = request["privateKeys"]; if (!inputs.isArray() || inputs.empty() || inputs.size() > 100 || !keys.isArray() || keys.empty() || keys.size() > 1000) throw std::runtime_error("Invalid payment inputs"); int height = request["height"].get_int(); if (height < 1 || height > 400000000) throw std::runtime_error("Invalid chain height"); CAmount amount = WalletAmount(request["amount"]); CAmount fee = WalletAmount(request["fee"]); if (amount <= 0 || fee < 10000 || fee > COIN) throw std::runtime_error("Invalid payment amount or fee"); CTxDestination to = DecodeDestination(request["to"].get_str()); CTxDestination change = DecodeDestination(request["change"].get_str()); if (!boost::get(&to) || !boost::get(&change)) throw std::runtime_error("A transparent NRB address is required"); CBasicKeyStore keystore; for (size_t i = 0; i < keys.size(); ++i) { CKey key = DecodeSecret(keys[i].get_str()); if (!key.IsValid() || !key.IsCompressed()) throw std::runtime_error("Invalid NRB private key"); keystore.AddKey(key); } if (!keystore.HaveKey(*boost::get(&change))) throw std::runtime_error("Change address is not owned by this wallet"); CMutableTransaction tx; tx.fOverwintered = true; tx.nVersion = SAPLING_TX_VERSION; tx.nVersionGroupId = SAPLING_VERSION_GROUP_ID; tx.nExpiryHeight = height + 40; tx.nLockTime = 0; CAmount total = 0; std::vector previous; std::set> seen; for (size_t i = 0; i < inputs.size(); ++i) { const auto &in = inputs[i]; std::string raw = in["raw"].get_str(); if (raw.size() > 4000000) throw std::runtime_error("Previous transaction is too large"); CTransaction prev; if (!DecodeHexTx(prev, raw)) throw std::runtime_error("Invalid previous transaction"); uint256 txid = ParseHashUV(in["txid"], "txid"); int index = in["vout"].get_int(); if (prev.GetHash() != txid || index < 0 || (size_t)index >= prev.vout.size()) throw std::runtime_error("Previous transaction does not match the selected input"); if (!seen.emplace(txid, (unsigned int)index).second) throw std::runtime_error("Duplicate payment input"); const CTxOut &out = prev.vout[index]; CTxDestination dest; if (!ExtractDestination(out.scriptPubKey, dest) || !boost::get(&dest) || out.scriptPubKey != GetScriptForDestination(dest) || !keystore.HaveKey(*boost::get(&dest))) throw std::runtime_error("Input is not a standard address owned by this wallet"); if (out.nValue != WalletAmount(in["amount"]) || out.nValue <= 0 || total > WALLET_MAX_MONEY - out.nValue) throw std::runtime_error("Previous output amount does not match"); total += out.nValue; previous.push_back(out); tx.vin.emplace_back(txid, (unsigned int)index); } if (total < amount + fee) throw std::runtime_error("Insufficient funds"); CAmount changeAmount = total - amount - fee; if (changeAmount != 0 && changeAmount < 1000) throw std::runtime_error("Change is too small; adjust the amount or fee"); tx.vout.emplace_back(amount, GetScriptForDestination(to)); if (changeAmount) tx.vout.emplace_back(changeAmount, GetScriptForDestination(change)); const auto branch = CurrentEpochBranchId(height, Params().GetConsensus()); for (size_t i = 0; i < previous.size(); ++i) { SignatureData sig; const auto &prev = previous[i]; if (!ProduceSignature(MutableTransactionSignatureCreator(&keystore, &tx, i, prev.nValue, prev.scriptPubKey, height, SIGHASH_ALL), prev.scriptPubKey, sig, branch)) throw std::runtime_error("Unable to sign payment input"); UpdateTransaction(tx, i, sig); if (!VerifyScript(tx.vin[i].scriptSig, prev.scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&tx, i, prev.nValue), branch)) throw std::runtime_error("Payment signature verification failed"); } CTransaction completed(tx); UniValue result(UniValue::VOBJ); result.pushKV("hex", EncodeHexTx(completed)); result.pushKV("txid", completed.GetHash().GetHex()); result.pushKV("fee", FormatMoney(fee)); result.pushKV("amount", FormatMoney(amount)); result.pushKV("change", FormatMoney(changeAmount)); return result; } int main(int argc, char *argv[]) { // The only supported interface is a single bounded JSON request on stdin. // Private keys never appear in command-line arguments or temporary files. if (argc != 2 || std::string(argv[1]) != "-wallet-json") return EXIT_FAILURE; try { SetupEnvironment(); ParseParameters(argc, argv); SelectParams(CBaseChainParams::MAIN); if (sodium_init() < 0) throw std::runtime_error("Secure randomness is unavailable"); Secp256k1Init ellipticCurve; std::string raw; char buffer[4096]; while (!feof(stdin)) { size_t n = fread(buffer, 1, sizeof(buffer), stdin); if (raw.size() + n > 16 * 1024 * 1024) throw std::runtime_error("Request is too large"); raw.append(buffer, n); if (n < sizeof(buffer)) break; } if (ferror(stdin)) throw std::runtime_error("Unable to read request"); UniValue request; if (!request.read(raw) || !request.isObject()) throw std::runtime_error("Invalid request"); sodium_memzero(&raw[0], raw.size()); sodium_memzero(buffer, sizeof(buffer)); UniValue result; std::string action = request["action"].get_str(); if (action == "newKey") { CKey key; key.MakeNewKey(true); result = WalletKey(key); } else if (action == "importKey") { result = WalletKey(DecodeSecret(request["privateKey"].get_str())); } else if (action == "payment") { result = WalletPayment(request); } else throw std::runtime_error("Unknown wallet operation"); std::string response = result.write(); fwrite(response.data(), 1, response.size(), stdout); fputc('\n', stdout); fflush(stdout); sodium_memzero(&response[0], response.size()); return EXIT_SUCCESS; } catch (const std::exception &e) { UniValue error(UniValue::VOBJ); error.pushKV("error", e.what()); fprintf(stdout, "%s\n", error.write().c_str()); return EXIT_FAILURE; } }