Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 19 additions & 18 deletions src/key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,21 +156,21 @@ bool CKey::Check(const unsigned char *vch) {
}

void CKey::MakeNewKey(bool fCompressedIn) {
MakeKeyData();
do {
GetStrongRandBytes(keydata);
} while (!Check(keydata.data()));
fValid = true;
GetStrongRandBytes(*keydata);
} while (!Check(keydata->data()));
fCompressed = fCompressedIn;
}

bool CKey::Negate()
{
assert(fValid);
return secp256k1_ec_seckey_negate(secp256k1_context_sign, keydata.data());
assert(keydata);
return secp256k1_ec_seckey_negate(secp256k1_context_sign, keydata->data());
}

CPrivKey CKey::GetPrivKey() const {
assert(fValid);
assert(keydata);
CPrivKey seckey;
int ret;
size_t seckeylen;
Expand All @@ -183,7 +183,7 @@ CPrivKey CKey::GetPrivKey() const {
}

CPubKey CKey::GetPubKey() const {
assert(fValid);
assert(keydata);
secp256k1_pubkey pubkey;
size_t clen = CPubKey::SIZE;
CPubKey result;
Expand All @@ -209,7 +209,7 @@ bool SigHasLowR(const secp256k1_ecdsa_signature* sig)
}

bool CKey::Sign(const uint256 &hash, std::vector<unsigned char>& vchSig, bool grind, uint32_t test_case) const {
if (!fValid)
if (!keydata)
return false;
vchSig.resize(CPubKey::SIGNATURE_SIZE);
size_t nSigLen = CPubKey::SIGNATURE_SIZE;
Expand Down Expand Up @@ -250,7 +250,7 @@ bool CKey::VerifyPubKey(const CPubKey& pubkey) const {
}

bool CKey::SignCompact(const uint256 &hash, std::vector<unsigned char>& vchSig) const {
if (!fValid)
if (!keydata)
return false;
vchSig.resize(CPubKey::COMPACT_SIGNATURE_SIZE);
int rec = -1;
Expand All @@ -273,10 +273,12 @@ bool CKey::SignCompact(const uint256 &hash, std::vector<unsigned char>& vchSig)
}

bool CKey::Load(const CPrivKey &seckey, const CPubKey &vchPubKey, bool fSkipCheck=false) {
if (!ec_seckey_import_der(secp256k1_context_sign, (unsigned char*)begin(), seckey.data(), seckey.size()))
MakeKeyData();
if (!ec_seckey_import_der(secp256k1_context_sign, (unsigned char*)begin(), seckey.data(), seckey.size())) {
ClearKeyData();
return false;
}
fCompressed = vchPubKey.IsCompressed();
fValid = true;

if (fSkipCheck)
return true;
Expand All @@ -297,22 +299,21 @@ bool CKey::Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const
BIP32Hash(cc, nChild, 0, begin(), vout.data());
}
memcpy(ccChild.begin(), vout.data()+32, 32);
memcpy((unsigned char*)keyChild.begin(), begin(), 32);
keyChild.Set(begin(), begin() + 32, true);
bool ret = secp256k1_ec_seckey_tweak_add(secp256k1_context_sign, (unsigned char*)keyChild.begin(), vout.data());
keyChild.fCompressed = true;
keyChild.fValid = ret;
if (!ret) keyChild.ClearKeyData();
return ret;
}

EllSwiftPubKey CKey::EllSwiftCreate(Span<const std::byte> ent32) const
{
assert(fValid);
assert(keydata);
assert(ent32.size() == 32);
std::array<std::byte, EllSwiftPubKey::size()> encoded_pubkey;

auto success = secp256k1_ellswift_create(secp256k1_context_sign,
UCharCast(encoded_pubkey.data()),
keydata.data(),
keydata->data(),
UCharCast(ent32.data()));

// Should always succeed for valid keys (asserted above).
Expand All @@ -322,7 +323,7 @@ EllSwiftPubKey CKey::EllSwiftCreate(Span<const std::byte> ent32) const

ECDHSecret CKey::ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift, const EllSwiftPubKey& our_ellswift, bool initiating) const
{
assert(fValid);
assert(keydata);

ECDHSecret output;
// BIP324 uses the initiator as party A, and the responder as party B. Remap the inputs
Expand All @@ -331,7 +332,7 @@ ECDHSecret CKey::ComputeBIP324ECDHSecret(const EllSwiftPubKey& their_ellswift, c
UCharCast(output.data()),
UCharCast(initiating ? our_ellswift.data() : their_ellswift.data()),
UCharCast(initiating ? their_ellswift.data() : our_ellswift.data()),
keydata.data(),
keydata->data(),
initiating ? 0 : 1,
secp256k1_ellswift_xdh_hash_function_bip324,
nullptr);
Expand Down
60 changes: 40 additions & 20 deletions src/key.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,57 +46,77 @@ class CKey
"COMPRESSED_SIZE is larger than SIZE");

private:
//! Whether this private key is valid. We check for correctness when modifying the key
//! data, so fValid should always correspond to the actual state.
bool fValid{false};
/** Internal data container for private key material. */
using KeyType = std::array<unsigned char, 32>;

//! Whether the public key corresponding to this private key is (to be) compressed.
bool fCompressed{false};

//! The actual byte data
std::vector<unsigned char, secure_allocator<unsigned char> > keydata;
//! The actual byte data. nullptr for invalid keys.
secure_unique_ptr<KeyType> keydata;

//! Check whether the 32-byte array pointed to by vch is valid keydata.
bool static Check(const unsigned char* vch);

void MakeKeyData()
{
if (!keydata) keydata = make_secure_unique<KeyType>();
}

void ClearKeyData()
{
keydata.reset();
}

public:
//! Construct an invalid private key.
CKey()
CKey() noexcept = default;
CKey(CKey&&) noexcept = default;
CKey& operator=(CKey&&) noexcept = default;

CKey& operator=(const CKey& other)
{
// Important: vch must be 32 bytes in length to not break serialization
keydata.resize(32);
if (other.keydata) {
MakeKeyData();
*keydata = *other.keydata;
} else {
ClearKeyData();
}
fCompressed = other.fCompressed;
return *this;
}

CKey(const CKey& other) { *this = other; }

friend bool operator==(const CKey& a, const CKey& b)
{
return a.fCompressed == b.fCompressed &&
a.size() == b.size() &&
memcmp(a.keydata.data(), b.keydata.data(), a.size()) == 0;
memcmp(a.data(), b.data(), a.size()) == 0;
}

//! Initialize using begin and end iterators to byte data.
template <typename T>
void Set(const T pbegin, const T pend, bool fCompressedIn)
{
if (size_t(pend - pbegin) != keydata.size()) {
fValid = false;
if (size_t(pend - pbegin) != std::tuple_size_v<KeyType>) {
ClearKeyData();
} else if (Check(&pbegin[0])) {
memcpy(keydata.data(), (unsigned char*)&pbegin[0], keydata.size());
fValid = true;
MakeKeyData();
memcpy(keydata->data(), (unsigned char*)&pbegin[0], keydata->size());
fCompressed = fCompressedIn;
} else {
fValid = false;
ClearKeyData();
}
}

//! Simple read-only vector-like interface.
unsigned int size() const { return (fValid ? keydata.size() : 0); }
const std::byte* data() const { return reinterpret_cast<const std::byte*>(keydata.data()); }
const unsigned char* begin() const { return keydata.data(); }
const unsigned char* end() const { return keydata.data() + size(); }
unsigned int size() const { return keydata ? keydata->size() : 0; }
const std::byte* data() const { return keydata ? reinterpret_cast<const std::byte*>(keydata->data()) : nullptr; }
const unsigned char* begin() const { return keydata ? keydata->data() : nullptr; }
const unsigned char* end() const { return begin() + size(); }

//! Check whether this private key is valid.
bool IsValid() const { return fValid; }
bool IsValid() const { return !!keydata; }

//! Check whether the public key corresponding to this private key is (to be) compressed.
bool IsCompressed() const { return fCompressed; }
Expand Down
8 changes: 4 additions & 4 deletions src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ static RPCHelpMan getnetworkhashps()

ChainstateManager& chainman = EnsureAnyChainman(request.context);
LOCK(cs_main);
return GetNetworkHashPS(!request.params[0].isNull() ? request.params[0].getInt<int>() : 120, !request.params[1].isNull() ? request.params[1].getInt<int>() : -1, chainman.ActiveChain());
return GetNetworkHashPS(self.Arg<int>(0), self.Arg<int>(1), chainman.ActiveChain());
},
};
}
Expand Down Expand Up @@ -230,12 +230,12 @@ static RPCHelpMan generatetodescriptor()
"\nGenerate 11 blocks to mydesc\n" + HelpExampleCli("generatetodescriptor", "11 \"mydesc\"")},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const int num_blocks{request.params[0].getInt<int>()};
const uint64_t max_tries{request.params[2].isNull() ? DEFAULT_MAX_TRIES : request.params[2].getInt<int>()};
const auto num_blocks{self.Arg<int>(0)};
const auto max_tries{self.Arg<uint64_t>(2)};

CScript coinbase_script;
std::string error;
if (!getScriptFromDescriptor(request.params[1].get_str(), coinbase_script, error)) {
if (!getScriptFromDescriptor(self.Arg<std::string>(1), coinbase_script, error)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
}

Expand Down
46 changes: 46 additions & 0 deletions src/rpc/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,13 +525,59 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
if (request.mode == JSONRPCRequest::GET_HELP || !IsValidNumArgs(request.params.size())) {
throw std::runtime_error(ToString());
}
CHECK_NONFATAL(m_req == nullptr);
m_req = &request;
UniValue ret = m_fun(*this, request);
m_req = nullptr;
if (gArgs.GetBoolArg("-rpcdoccheck", DEFAULT_RPC_DOC_CHECK)) {
CHECK_NONFATAL(std::any_of(m_results.m_results.begin(), m_results.m_results.end(), [&ret](const RPCResult& res) { return res.MatchesType(ret); }));
}
return ret;
}

using CheckFn = void(const RPCArg&);
static const UniValue* DetailMaybeArg(CheckFn* check, const std::vector<RPCArg>& params, const JSONRPCRequest* req, size_t i)
{
CHECK_NONFATAL(i < params.size());
const UniValue& arg{CHECK_NONFATAL(req)->params[i]};
const RPCArg& param{params.at(i)};
if (check) check(param);

if (!arg.isNull()) return &arg;
if (!std::holds_alternative<RPCArg::Default>(param.m_fallback)) return nullptr;
return &std::get<RPCArg::Default>(param.m_fallback);
}
Comment on lines +539 to +549
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep MaybeArg() from materializing documented defaults.

DetailMaybeArg() feeds RPCArg::Default back to both Arg() and MaybeArg(). For MaybeArg<T>(), that makes an omitted parameter with a documented default indistinguishable from an explicitly provided value, which defeats callers that need to detect omission separately.

🛠️ One way to preserve omission for MaybeArg()
-static const UniValue* DetailMaybeArg(CheckFn* check, const std::vector<RPCArg>& params, const JSONRPCRequest* req, size_t i)
+static const UniValue* DetailMaybeArg(CheckFn* check, bool use_default, const std::vector<RPCArg>& params, const JSONRPCRequest* req, size_t i)
 {
     CHECK_NONFATAL(i < params.size());
     const UniValue& arg{CHECK_NONFATAL(req)->params[i]};
     const RPCArg& param{params.at(i)};
     if (check) check(param);

     if (!arg.isNull()) return &arg;
-    if (!std::holds_alternative<RPCArg::Default>(param.m_fallback)) return nullptr;
+    if (!use_default || !std::holds_alternative<RPCArg::Default>(param.m_fallback)) return nullptr;
     return &std::get<RPCArg::Default>(param.m_fallback);
 }

-#define TMPL_INST(check_param, ret_type, return_code)       \
+#define TMPL_INST(check_param, use_default, ret_type, return_code) \
     template <>                                             \
     ret_type RPCHelpMan::ArgValue<ret_type>(size_t i) const \
     {                                                       \
         const UniValue* maybe_arg{                          \
-            DetailMaybeArg(check_param, m_args, m_req, i),  \
+            DetailMaybeArg(check_param, use_default, m_args, m_req, i), \
         };                                                  \
         return return_code                                  \
     }                                                       \
     void force_semicolon(ret_type)

-TMPL_INST(nullptr, std::optional<double>, maybe_arg ? std::optional{maybe_arg->get_real()} : std::nullopt;);
-TMPL_INST(nullptr, std::optional<bool>, maybe_arg ? std::optional{maybe_arg->get_bool()} : std::nullopt;);
-TMPL_INST(nullptr, const std::string*, maybe_arg ? &maybe_arg->get_str() : nullptr;);
+TMPL_INST(nullptr, false, std::optional<double>, maybe_arg ? std::optional{maybe_arg->get_real()} : std::nullopt;);
+TMPL_INST(nullptr, false, std::optional<bool>, maybe_arg ? std::optional{maybe_arg->get_bool()} : std::nullopt;);
+TMPL_INST(nullptr, false, const std::string*, maybe_arg ? &maybe_arg->get_str() : nullptr;);

-TMPL_INST(CheckRequiredOrDefault, int, CHECK_NONFATAL(maybe_arg)->getInt<int>(););
-TMPL_INST(CheckRequiredOrDefault, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt<uint64_t>(););
-TMPL_INST(CheckRequiredOrDefault, const std::string&, CHECK_NONFATAL(maybe_arg)->get_str(););
+TMPL_INST(CheckRequiredOrDefault, true, int, CHECK_NONFATAL(maybe_arg)->getInt<int>(););
+TMPL_INST(CheckRequiredOrDefault, true, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt<uint64_t>(););
+TMPL_INST(CheckRequiredOrDefault, true, const std::string&, CHECK_NONFATAL(maybe_arg)->get_str(););

Also applies to: 560-579

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rpc/util.cpp` around lines 539 - 549, DetailMaybeArg() currently returns
an RPCArg::Default as if it were an actual UniValue param, causing MaybeArg<T>()
to be unable to distinguish an omitted argument from an explicitly provided
default; modify DetailMaybeArg() (and the similar block at 560-579) so that it
returns nullptr when the request omits the parameter even if param.m_fallback
holds RPCArg::Default, and only return a pointer to the documented default for
callers that expect a materialized default (e.g., Arg()); adjust callers of
DetailMaybeArg()/MaybeArg<T>() accordingly so MaybeArg<T>() treats nullptr as
"omitted" while Arg() still receives the fallback value when necessary.


static void CheckRequiredOrDefault(const RPCArg& param)
{
// Must use `Arg<Type>(i)` to get the argument or its default value.
const bool required{
std::holds_alternative<RPCArg::Optional>(param.m_fallback) && RPCArg::Optional::NO == std::get<RPCArg::Optional>(param.m_fallback),
};
CHECK_NONFATAL(required || std::holds_alternative<RPCArg::Default>(param.m_fallback));
}

#define TMPL_INST(check_param, ret_type, return_code) \
template <> \
ret_type RPCHelpMan::ArgValue<ret_type>(size_t i) const \
{ \
const UniValue* maybe_arg{ \
DetailMaybeArg(check_param, m_args, m_req, i), \
}; \
return return_code \
} \
void force_semicolon(ret_type)

// Optional arg (without default). Can also be called on required args, if needed.
TMPL_INST(nullptr, std::optional<double>, maybe_arg ? std::optional{maybe_arg->get_real()} : std::nullopt;);
TMPL_INST(nullptr, std::optional<bool>, maybe_arg ? std::optional{maybe_arg->get_bool()} : std::nullopt;);
TMPL_INST(nullptr, const std::string*, maybe_arg ? &maybe_arg->get_str() : nullptr;);

// Required arg or optional arg with default value.
TMPL_INST(CheckRequiredOrDefault, int, CHECK_NONFATAL(maybe_arg)->getInt<int>(););
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return type errors for null required RPC args

ArgValue<int>/ArgValue<const std::string&> now does CHECK_NONFATAL(maybe_arg); when a required parameter is explicitly passed as null, DetailMaybeArg() returns nullptr and this path raises NonFatalCheckError instead of a UniValue::type_error. For RPCs switched to self.Arg<...>() (e.g. generatetodescriptor), malformed input now surfaces as internal RPC_MISC_ERROR rather than a normal type error, which changes API behavior and leaks internal-bug messaging for user input.

Useful? React with 👍 / 👎.

TMPL_INST(CheckRequiredOrDefault, uint64_t, CHECK_NONFATAL(maybe_arg)->getInt<uint64_t>(););
TMPL_INST(CheckRequiredOrDefault, const std::string&, CHECK_NONFATAL(maybe_arg)->get_str(););

bool RPCHelpMan::IsValidNumArgs(size_t num_args) const
{
size_t num_required_args = 0;
Expand Down
58 changes: 58 additions & 0 deletions src/rpc/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#ifndef BITCOIN_RPC_UTIL_H
#define BITCOIN_RPC_UTIL_H

#include <consensus/amount.h>
#include <node/transaction.h>
#include <pubkey.h>
#include <rpc/protocol.h>
Expand All @@ -13,14 +14,30 @@
#include <script/sign.h>
#include <script/signingprovider.h>
#include <script/standard.h>
#include <uint256.h>
#include <univalue.h>
#include <util/check.h>
#include <util/strencodings.h>

#include <cstddef>
#include <cstdint>
#include <functional>
#include <initializer_list>
#include <map>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

class JSONRPCRequest;
enum ServiceFlags : uint64_t;
enum class OutputType;
enum class TransactionError;
struct FlatSigningProvider;
struct bilingual_str;

static constexpr bool DEFAULT_RPC_DOC_CHECK{
#ifdef RPC_DOC_CHECK
true
Expand Down Expand Up @@ -365,6 +382,44 @@ class RPCHelpMan
RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples, RPCMethodImpl fun);

UniValue HandleRequest(const JSONRPCRequest& request) const;
/**
* Helper to get a request argument.
* This function only works during m_fun(), i.e. it should only be used in
* RPC method implementations. The helper internally checks whether the
* user-passed argument isNull() and parses (from JSON) and returns the
* user-passed argument, or the default value derived from the RPCArg
* documention, or a falsy value if no default was given.
*
* Use Arg<Type>(i) to get the argument or its default value. Otherwise,
* use MaybeArg<Type>(i) to get the optional argument or a falsy value.
*
* The Type passed to this helper must match the corresponding
* RPCArg::Type.
*/
template <typename R>
auto Arg(size_t i) const
{
// Return argument (required or with default value).
if constexpr (std::is_integral_v<R> || std::is_floating_point_v<R>) {
// Return numbers by value.
return ArgValue<R>(i);
} else {
// Return everything else by reference.
return ArgValue<const R&>(i);
}
}
template <typename R>
auto MaybeArg(size_t i) const
{
// Return optional argument (without default).
if constexpr (std::is_integral_v<R> || std::is_floating_point_v<R>) {
// Return numbers by value, wrapped in optional.
return ArgValue<std::optional<R>>(i);
} else {
// Return other types by pointer.
return ArgValue<const R*>(i);
}
}
std::string ToString() const;
/** Return the named args that need to be converted from string to another JSON type */
UniValue GetArgMap() const;
Expand All @@ -381,6 +436,9 @@ class RPCHelpMan
const std::vector<RPCArg> m_args;
const RPCResults m_results;
const RPCExamples m_examples;
mutable const JSONRPCRequest* m_req{nullptr}; // A pointer to the request for the duration of m_fun()
template <typename R>
R ArgValue(size_t i) const;
};

RPCErrorCode RPCErrorFromTransactionError(TransactionError terr);
Expand Down
Loading
Loading