Publish Nerbus source snapshot and build documentation
This commit is contained in:
commit
73401987df
17 files changed
+2169
No files matched your search
@@ -0,0 +1,170 @@
|
||||
// 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 <sodium.h>
|
||||
#include <set>
|
||||
|
||||
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<CKeyID>(&to) || !boost::get<CKeyID>(&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<CKeyID>(&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<CTxOut> previous;
|
||||
std::set<std::pair<uint256, unsigned int>> 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<CKeyID>(&dest) ||
|
||||
out.scriptPubKey != GetScriptForDestination(dest) ||
|
||||
!keystore.HaveKey(*boost::get<CKeyID>(&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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user