security: harden wallet, bootstrap, consensus, and RPC
(cherry picked from commit bed3d72099e04813393561a535b7dec9c0ac5e7f)
This commit is contained in:
+4
-4
@@ -6,7 +6,7 @@ if(POLICY CMP0167)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
project(Triangles
|
project(Triangles
|
||||||
VERSION 6.0.0
|
VERSION 6.1.7
|
||||||
DESCRIPTION "Cryptographic Triangles Wallet"
|
DESCRIPTION "Cryptographic Triangles Wallet"
|
||||||
LANGUAGES C CXX
|
LANGUAGES C CXX
|
||||||
)
|
)
|
||||||
@@ -84,10 +84,10 @@ option(BUILD_QT "Build triangles-qt (Qt5 GUI wallet)" ON)
|
|||||||
option(BUILD_DAEMON "Build trianglesd (headless daemon)" ON)
|
option(BUILD_DAEMON "Build trianglesd (headless daemon)" ON)
|
||||||
option(BUILD_CLI "Build triangles-cli (JSON-RPC client)" ON)
|
option(BUILD_CLI "Build triangles-cli (JSON-RPC client)" ON)
|
||||||
option(BUILD_TESTS "Build test_triangles (Boost.Test unit tests)" ON)
|
option(BUILD_TESTS "Build test_triangles (Boost.Test unit tests)" ON)
|
||||||
option(USE_UPNP "Enable UPnP support via miniupnpc" ON)
|
option(USE_UPNP "Enable UPnP support via miniupnpc" OFF)
|
||||||
option(USE_IPV6 "Enable IPv6 support" ON)
|
option(USE_IPV6 "Enable IPv6 support" ON)
|
||||||
option(USE_QRCODE "Enable QR code generation via libqrencode" OFF)
|
option(USE_QRCODE "Enable QR code generation via libqrencode" OFF)
|
||||||
option(USE_DBUS "Enable D-Bus notifications (Linux only)" ON)
|
option(USE_DBUS "Enable D-Bus notifications (Linux only)" OFF)
|
||||||
option(USE_ZMQ "Enable ZMQ publisher support" OFF)
|
option(USE_ZMQ "Enable ZMQ publisher support" OFF)
|
||||||
# Triangles is Tor-native. Tor is REQUIRED — disabling it at build time is
|
# Triangles is Tor-native. Tor is REQUIRED — disabling it at build time is
|
||||||
# not a supported configuration. The 2026-06-23 DNS2 clearnet-fork incident
|
# not a supported configuration. The 2026-06-23 DNS2 clearnet-fork incident
|
||||||
@@ -104,7 +104,7 @@ if(DEFINED USE_TOR_EMBEDDED AND NOT USE_TOR_EMBEDDED)
|
|||||||
"instead.")
|
"instead.")
|
||||||
endif()
|
endif()
|
||||||
option(USE_O3 "Use -O3 optimization instead of -O2" OFF)
|
option(USE_O3 "Use -O3 optimization instead of -O2" OFF)
|
||||||
option(ENABLE_PIE "Build position-independent executables" OFF)
|
option(ENABLE_PIE "Build position-independent executables" ON)
|
||||||
option(ENABLE_STATIC "Prefer static linking (Linux release builds)" OFF)
|
option(ENABLE_STATIC "Prefer static linking (Linux release builds)" OFF)
|
||||||
|
|
||||||
# Embedded I2P (i2pd) — runs an I2P router in-process alongside Tor.
|
# Embedded I2P (i2pd) — runs an I2P router in-process alongside Tor.
|
||||||
|
|||||||
+18
-16
@@ -907,46 +907,48 @@ void StartShutdown() {}
|
|||||||
# "$@" preservation. We use bash explicitly (not sh) for "$@" array
|
# "$@" preservation. We use bash explicitly (not sh) for "$@" array
|
||||||
# semantics — paths may contain spaces, so word-splitting on IFS
|
# semantics — paths may contain spaces, so word-splitting on IFS
|
||||||
# would corrupt them.
|
# would corrupt them.
|
||||||
file(WRITE "${FUZZ_LINK_WRAPPER}"
|
set(FUZZ_LINK_WRAPPER_CONTENT [=[#!/bin/bash
|
||||||
"#!/bin/bash
|
|
||||||
# Auto-generated by CMake (BUILD_FUZZ block). Discovers triangles_common +
|
# Auto-generated by CMake (BUILD_FUZZ block). Discovers triangles_common +
|
||||||
# trianglesd .o files at link time and exec's the clang++ link line.
|
# trianglesd .o files at link time and exec's the clang++ link line.
|
||||||
#
|
#
|
||||||
# Usage: link.sh clang++ [link-args...]
|
# Usage: link.sh clang++ [link-args...]
|
||||||
# Final exec: clang++ <each .o> <each original link-arg>
|
# Final exec: clang++ <each .o> <each original link-arg>
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
PROG=\"\$1\"
|
PROG="$1"
|
||||||
shift
|
shift
|
||||||
TRIANGLES_COMMON_DIR=\"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/triangles_common.dir\"
|
TRIANGLES_COMMON_DIR="@CMAKE_CURRENT_BINARY_DIR@/CMakeFiles/triangles_common.dir"
|
||||||
TRIANGLESD_DIR=\"${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/trianglesd_objects.dir\"
|
TRIANGLESD_DIR="@CMAKE_CURRENT_BINARY_DIR@/CMakeFiles/trianglesd_objects.dir"
|
||||||
# Discover .o files into a bash array. Exclude script.cpp.o (we have our
|
# Discover .o files into a bash array. Exclude script.cpp.o (we have our
|
||||||
# own clang-instrumented copy in fuzz_objs/ that we want to keep separate
|
# own clang-instrumented copy in fuzz_objs/ that we want to keep separate
|
||||||
# from the main build's copy).
|
# from the main build's copy).
|
||||||
declare -a OBJS=()
|
declare -a OBJS=()
|
||||||
for f in \"\$TRIANGLES_COMMON_DIR\"/*.o \"\$TRIANGLES_COMMON_DIR\"/*/*.o; do
|
for f in "$TRIANGLES_COMMON_DIR"/*.o "$TRIANGLES_COMMON_DIR"/*/*.o; do
|
||||||
[ -f \"\$f\" ] || continue
|
[ -f "$f" ] || continue
|
||||||
case \"\$f\" in
|
case "$f" in
|
||||||
*/script.cpp.o) continue ;;
|
*/script.cpp.o) continue ;;
|
||||||
esac
|
esac
|
||||||
OBJS+=(\"\$f\")
|
OBJS+=("$f")
|
||||||
done
|
done
|
||||||
if [ -d "\$TRIANGLESD_DIR" ]; then
|
if [ -d "$TRIANGLESD_DIR" ]; then
|
||||||
for f in "\$TRIANGLESD_DIR"/*.o; do
|
for f in "$TRIANGLESD_DIR"/*.o; do
|
||||||
[ -f "\$f" ] || continue
|
[ -f "$f" ] || continue
|
||||||
# init.cpp defines the daemon's main(); the fuzz harness has its own
|
# init.cpp defines the daemon's main(); the fuzz harness has its own
|
||||||
# (libFuzzer's). wallet.cpp, noui.cpp etc. are safe — they don't
|
# (libFuzzer's). wallet.cpp, noui.cpp etc. are safe — they don't
|
||||||
# define main and their external references (pwalletMain,
|
# define main and their external references (pwalletMain,
|
||||||
# uiInterface, nDerivationMethodIndex) are satisfied by the stub
|
# uiInterface, nDerivationMethodIndex) are satisfied by the stub
|
||||||
# object file we add at the end of the link line.
|
# object file we add at the end of the link line.
|
||||||
case "\$f" in
|
case "$f" in
|
||||||
*/init.cpp.o) continue ;;
|
*/init.cpp.o) continue ;;
|
||||||
esac
|
esac
|
||||||
OBJS+=("\$f")
|
OBJS+=("$f")
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
# Final arg list: PROG, then all .o files, then all original link args.
|
# Final arg list: PROG, then all .o files, then all original link args.
|
||||||
exec \"\$PROG\" \"\${OBJS[@]}\" \"\$@\"
|
exec "$PROG" "${OBJS[@]}" "$@"
|
||||||
")
|
]=])
|
||||||
|
string(CONFIGURE "${FUZZ_LINK_WRAPPER_CONTENT}"
|
||||||
|
FUZZ_LINK_WRAPPER_CONTENT @ONLY)
|
||||||
|
file(WRITE "${FUZZ_LINK_WRAPPER}" "${FUZZ_LINK_WRAPPER_CONTENT}")
|
||||||
file(CHMOD "${FUZZ_LINK_WRAPPER}" PERMISSIONS
|
file(CHMOD "${FUZZ_LINK_WRAPPER}" PERMISSIONS
|
||||||
OWNER_READ OWNER_WRITE OWNER_EXECUTE
|
OWNER_READ OWNER_WRITE OWNER_EXECUTE
|
||||||
GROUP_READ GROUP_EXECUTE
|
GROUP_READ GROUP_EXECUTE
|
||||||
|
|||||||
+70
-57
@@ -14,6 +14,8 @@
|
|||||||
#include <openssl/opensslv.h>
|
#include <openssl/opensslv.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <limits>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -69,16 +71,10 @@ public:
|
|||||||
throw bignum_error("CBigNum::CBigNum() : BN_new() returned NULL");
|
throw bignum_error("CBigNum::CBigNum() : BN_new() returned NULL");
|
||||||
}
|
}
|
||||||
|
|
||||||
CBigNum(const CBigNum& b)
|
CBigNum(const CBigNum& b) : CBigNum()
|
||||||
{
|
{
|
||||||
pbn = BN_new();
|
|
||||||
if (pbn == nullptr)
|
|
||||||
throw bignum_error("CBigNum::CBigNum(const CBigNum&) : BN_new() returned NULL");
|
|
||||||
if (!BN_copy(pbn, b.pbn))
|
if (!BN_copy(pbn, b.pbn))
|
||||||
{
|
|
||||||
BN_clear_free(pbn);
|
|
||||||
throw bignum_error("CBigNum::CBigNum(const CBigNum&) : BN_copy failed");
|
throw bignum_error("CBigNum::CBigNum(const CBigNum&) : BN_copy failed");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CBigNum& operator=(const CBigNum& b)
|
CBigNum& operator=(const CBigNum& b)
|
||||||
@@ -99,21 +95,20 @@ public:
|
|||||||
const BIGNUM* get() const { return pbn; }
|
const BIGNUM* get() const { return pbn; }
|
||||||
|
|
||||||
//CBigNum(char n) is not portable. Use 'signed char' or 'unsigned char'.
|
//CBigNum(char n) is not portable. Use 'signed char' or 'unsigned char'.
|
||||||
CBigNum(signed char n) { pbn = BN_new(); if (n >= 0) setulong(n); else setint64(n); }
|
CBigNum(signed char n) : CBigNum() { if (n >= 0) setulong(n); else setint64(n); }
|
||||||
CBigNum(short n) { pbn = BN_new(); if (n >= 0) setulong(n); else setint64(n); }
|
CBigNum(short n) : CBigNum() { if (n >= 0) setulong(n); else setint64(n); }
|
||||||
CBigNum(int n) { pbn = BN_new(); if (n >= 0) setulong(n); else setint64(n); }
|
CBigNum(int n) : CBigNum() { if (n >= 0) setulong(n); else setint64(n); }
|
||||||
CBigNum(long n) { pbn = BN_new(); if (n >= 0) setulong(n); else setint64(n); }
|
CBigNum(long n) : CBigNum() { if (n >= 0) setulong(n); else setint64(n); }
|
||||||
CBigNum(long long n) { pbn = BN_new(); setint64(n); }
|
CBigNum(long long n) : CBigNum() { setint64(n); }
|
||||||
CBigNum(unsigned char n) { pbn = BN_new(); setulong(n); }
|
CBigNum(unsigned char n) : CBigNum() { setulong(n); }
|
||||||
CBigNum(unsigned short n) { pbn = BN_new(); setulong(n); }
|
CBigNum(unsigned short n) : CBigNum() { setulong(n); }
|
||||||
CBigNum(unsigned int n) { pbn = BN_new(); setulong(n); }
|
CBigNum(unsigned int n) : CBigNum() { setulong(n); }
|
||||||
CBigNum(unsigned long n) { pbn = BN_new(); setulong(n); }
|
CBigNum(unsigned long n) : CBigNum() { setulong(n); }
|
||||||
CBigNum(unsigned long long n) { pbn = BN_new(); setuint64(n); }
|
CBigNum(unsigned long long n) : CBigNum() { setuint64(n); }
|
||||||
explicit CBigNum(uint256 n) { pbn = BN_new(); setuint256(n); }
|
explicit CBigNum(uint256 n) : CBigNum() { setuint256(n); }
|
||||||
|
|
||||||
explicit CBigNum(const std::vector<unsigned char>& vch)
|
explicit CBigNum(const std::vector<unsigned char>& vch) : CBigNum()
|
||||||
{
|
{
|
||||||
pbn = BN_new();
|
|
||||||
setvch(vch);
|
setvch(vch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,21 +211,23 @@ public:
|
|||||||
pch[1] = (nSize >> 16) & 0xff;
|
pch[1] = (nSize >> 16) & 0xff;
|
||||||
pch[2] = (nSize >> 8) & 0xff;
|
pch[2] = (nSize >> 8) & 0xff;
|
||||||
pch[3] = (nSize) & 0xff;
|
pch[3] = (nSize) & 0xff;
|
||||||
BN_mpi2bn(pch, p - pch, pbn);
|
if (BN_mpi2bn(pch, static_cast<int>(p - pch), pbn) == nullptr)
|
||||||
|
throw bignum_error("CBigNum::setint64() : BN_mpi2bn failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t getuint64()
|
uint64_t getuint64() const
|
||||||
{
|
{
|
||||||
unsigned int nSize = BN_bn2mpi(pbn, nullptr);
|
const int nSize = BN_bn2mpi(pbn, nullptr);
|
||||||
if (nSize < 4)
|
if (nSize <= 4)
|
||||||
return 0;
|
return 0;
|
||||||
std::vector<unsigned char> vch(nSize);
|
std::vector<unsigned char> vch(static_cast<size_t>(nSize));
|
||||||
BN_bn2mpi(pbn, &vch[0]);
|
if (BN_bn2mpi(pbn, vch.data()) != nSize)
|
||||||
|
throw bignum_error("CBigNum::getuint64() : BN_bn2mpi failed");
|
||||||
if (vch.size() > 4)
|
if (vch.size() > 4)
|
||||||
vch[4] &= 0x7f;
|
vch[4] &= 0x7f;
|
||||||
uint64_t n = 0;
|
uint64_t n = 0;
|
||||||
for (unsigned int i = 0, j = vch.size()-1; i < sizeof(n) && j >= 4; i++, j--)
|
for (size_t i = 0; i < sizeof(n) && i + 4 < vch.size(); ++i)
|
||||||
((unsigned char*)&n)[i] = vch[j];
|
n |= static_cast<uint64_t>(vch[vch.size() - 1 - i]) << (8 * i);
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +255,8 @@ public:
|
|||||||
pch[1] = (nSize >> 16) & 0xff;
|
pch[1] = (nSize >> 16) & 0xff;
|
||||||
pch[2] = (nSize >> 8) & 0xff;
|
pch[2] = (nSize >> 8) & 0xff;
|
||||||
pch[3] = (nSize) & 0xff;
|
pch[3] = (nSize) & 0xff;
|
||||||
BN_mpi2bn(pch, p - pch, pbn);
|
if (BN_mpi2bn(pch, static_cast<int>(p - pch), pbn) == nullptr)
|
||||||
|
throw bignum_error("CBigNum::setuint64() : BN_mpi2bn failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
void setuint256(uint256 n)
|
void setuint256(uint256 n)
|
||||||
@@ -286,29 +284,33 @@ public:
|
|||||||
pch[1] = (nSize >> 16) & 0xff;
|
pch[1] = (nSize >> 16) & 0xff;
|
||||||
pch[2] = (nSize >> 8) & 0xff;
|
pch[2] = (nSize >> 8) & 0xff;
|
||||||
pch[3] = (nSize >> 0) & 0xff;
|
pch[3] = (nSize >> 0) & 0xff;
|
||||||
BN_mpi2bn(pch, p - pch, pbn);
|
if (BN_mpi2bn(pch, static_cast<int>(p - pch), pbn) == nullptr)
|
||||||
|
throw bignum_error("CBigNum::setuint256() : BN_mpi2bn failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 getuint256() const
|
uint256 getuint256() const
|
||||||
{
|
{
|
||||||
unsigned int nSize = BN_bn2mpi(pbn, nullptr);
|
const int mpiSize = BN_bn2mpi(pbn, nullptr);
|
||||||
if (nSize < 4)
|
if (mpiSize <= 4)
|
||||||
return 0;
|
return 0;
|
||||||
std::vector<unsigned char> vch(nSize);
|
std::vector<unsigned char> vch(static_cast<size_t>(mpiSize));
|
||||||
BN_bn2mpi(pbn, &vch[0]);
|
if (BN_bn2mpi(pbn, vch.data()) != mpiSize)
|
||||||
if (vch.size() > 4)
|
throw bignum_error("CBigNum::getuint256() : BN_bn2mpi failed");
|
||||||
vch[4] &= 0x7f;
|
vch[4] &= 0x7f;
|
||||||
uint256 n = 0;
|
uint256 n = 0;
|
||||||
for (unsigned int i = 0, j = vch.size()-1; i < sizeof(n) && j >= 4; i++, j--)
|
for (size_t i = 0; i < sizeof(n) && i + 4 < vch.size(); ++i)
|
||||||
((unsigned char*)&n)[i] = vch[j];
|
reinterpret_cast<unsigned char*>(&n)[i] = vch[vch.size() - 1 - i];
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void setvch(const std::vector<unsigned char>& vch)
|
void setvch(const std::vector<unsigned char>& vch)
|
||||||
{
|
{
|
||||||
|
if (vch.size() > static_cast<size_t>(std::numeric_limits<int>::max() - 4))
|
||||||
|
throw bignum_error("CBigNum::setvch() : input is too large");
|
||||||
|
|
||||||
std::vector<unsigned char> vch2(vch.size() + 4);
|
std::vector<unsigned char> vch2(vch.size() + 4);
|
||||||
unsigned int nSize = vch.size();
|
const uint32_t nSize = static_cast<uint32_t>(vch.size());
|
||||||
// BIGNUM's byte stream format expects 4 bytes of
|
// BIGNUM's byte stream format expects 4 bytes of
|
||||||
// big endian size data info at the front
|
// big endian size data info at the front
|
||||||
vch2[0] = (nSize >> 24) & 0xff;
|
vch2[0] = (nSize >> 24) & 0xff;
|
||||||
@@ -316,20 +318,25 @@ public:
|
|||||||
vch2[2] = (nSize >> 8) & 0xff;
|
vch2[2] = (nSize >> 8) & 0xff;
|
||||||
vch2[3] = (nSize >> 0) & 0xff;
|
vch2[3] = (nSize >> 0) & 0xff;
|
||||||
// swap data to big endian
|
// swap data to big endian
|
||||||
reverse_copy(vch.begin(), vch.end(), vch2.begin() + 4);
|
for (size_t i = 0; i < vch.size(); ++i)
|
||||||
BN_mpi2bn(&vch2[0], vch2.size(), pbn);
|
vch2.at(i + 4) = vch.at(vch.size() - 1 - i);
|
||||||
|
if (BN_mpi2bn(vch2.data(), static_cast<int>(vch2.size()), pbn) == nullptr)
|
||||||
|
throw bignum_error("CBigNum::setvch() : BN_mpi2bn failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> getvch() const
|
std::vector<unsigned char> getvch() const
|
||||||
{
|
{
|
||||||
unsigned int nSize = BN_bn2mpi(pbn, nullptr);
|
const int mpiSize = BN_bn2mpi(pbn, nullptr);
|
||||||
if (nSize <= 4)
|
if (mpiSize <= 4)
|
||||||
return std::vector<unsigned char>();
|
return std::vector<unsigned char>();
|
||||||
std::vector<unsigned char> vch(nSize);
|
std::vector<unsigned char> mpi(static_cast<size_t>(mpiSize));
|
||||||
BN_bn2mpi(pbn, &vch[0]);
|
if (BN_bn2mpi(pbn, mpi.data()) != mpiSize)
|
||||||
vch.erase(vch.begin(), vch.begin() + 4);
|
throw bignum_error("CBigNum::getvch() : BN_bn2mpi failed");
|
||||||
reverse(vch.begin(), vch.end());
|
|
||||||
return vch;
|
std::vector<unsigned char> result(static_cast<size_t>(mpiSize - 4));
|
||||||
|
for (size_t i = 0; i < result.size(); ++i)
|
||||||
|
result.at(i) = mpi.at(mpi.size() - 1 - i);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
CBigNum& SetCompact(unsigned int nCompact)
|
CBigNum& SetCompact(unsigned int nCompact)
|
||||||
@@ -340,16 +347,20 @@ public:
|
|||||||
if (nSize >= 1) vch[4] = (nCompact >> 16) & 0xff;
|
if (nSize >= 1) vch[4] = (nCompact >> 16) & 0xff;
|
||||||
if (nSize >= 2) vch[5] = (nCompact >> 8) & 0xff;
|
if (nSize >= 2) vch[5] = (nCompact >> 8) & 0xff;
|
||||||
if (nSize >= 3) vch[6] = (nCompact >> 0) & 0xff;
|
if (nSize >= 3) vch[6] = (nCompact >> 0) & 0xff;
|
||||||
BN_mpi2bn(&vch[0], vch.size(), pbn);
|
if (BN_mpi2bn(vch.data(), static_cast<int>(vch.size()), pbn) == nullptr)
|
||||||
|
throw bignum_error("CBigNum::SetCompact() : BN_mpi2bn failed");
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int GetCompact() const
|
unsigned int GetCompact() const
|
||||||
{
|
{
|
||||||
unsigned int nSize = BN_bn2mpi(pbn, nullptr);
|
const int mpiSize = BN_bn2mpi(pbn, nullptr);
|
||||||
std::vector<unsigned char> vch(nSize);
|
if (mpiSize <= 4)
|
||||||
nSize -= 4;
|
return 0;
|
||||||
BN_bn2mpi(pbn, &vch[0]);
|
std::vector<unsigned char> vch(static_cast<size_t>(mpiSize));
|
||||||
|
if (BN_bn2mpi(pbn, vch.data()) != mpiSize)
|
||||||
|
throw bignum_error("CBigNum::GetCompact() : BN_bn2mpi failed");
|
||||||
|
const unsigned int nSize = static_cast<unsigned int>(mpiSize - 4);
|
||||||
unsigned int nCompact = nSize << 24;
|
unsigned int nCompact = nSize << 24;
|
||||||
if (nSize >= 1) nCompact |= (vch[4] << 16);
|
if (nSize >= 1) nCompact |= (vch[4] << 16);
|
||||||
if (nSize >= 2) nCompact |= (vch[5] << 8);
|
if (nSize >= 2) nCompact |= (vch[5] << 8);
|
||||||
@@ -361,7 +372,7 @@ public:
|
|||||||
{
|
{
|
||||||
// skip 0x
|
// skip 0x
|
||||||
const char* psz = str.c_str();
|
const char* psz = str.c_str();
|
||||||
while (isspace(*psz))
|
while (isspace(static_cast<unsigned char>(*psz)))
|
||||||
psz++;
|
psz++;
|
||||||
bool fNegative = false;
|
bool fNegative = false;
|
||||||
if (*psz == '-')
|
if (*psz == '-')
|
||||||
@@ -369,15 +380,15 @@ public:
|
|||||||
fNegative = true;
|
fNegative = true;
|
||||||
psz++;
|
psz++;
|
||||||
}
|
}
|
||||||
if (psz[0] == '0' && tolower(psz[1]) == 'x')
|
if (psz[0] == '0' && tolower(static_cast<unsigned char>(psz[1])) == 'x')
|
||||||
psz += 2;
|
psz += 2;
|
||||||
while (isspace(*psz))
|
while (isspace(static_cast<unsigned char>(*psz)))
|
||||||
psz++;
|
psz++;
|
||||||
|
|
||||||
// hex string to bignum
|
// hex string to bignum
|
||||||
static constexpr signed char phexdigit[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0 };
|
static constexpr signed char phexdigit[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0 };
|
||||||
*this = 0;
|
*this = 0;
|
||||||
while (isxdigit(*psz))
|
while (isxdigit(static_cast<unsigned char>(*psz)))
|
||||||
{
|
{
|
||||||
*this <<= 4;
|
*this <<= 4;
|
||||||
int n = phexdigit[(unsigned char)*psz++];
|
int n = phexdigit[(unsigned char)*psz++];
|
||||||
@@ -389,6 +400,8 @@ public:
|
|||||||
|
|
||||||
std::string ToString(int nBase=10) const
|
std::string ToString(int nBase=10) const
|
||||||
{
|
{
|
||||||
|
if (nBase < 2 || nBase > 16)
|
||||||
|
throw bignum_error("CBigNum::ToString() : base must be in [2, 16]");
|
||||||
CAutoBN_CTX pctx;
|
CAutoBN_CTX pctx;
|
||||||
CBigNum bnBase = nBase;
|
CBigNum bnBase = nBase;
|
||||||
CBigNum bn0 = 0;
|
CBigNum bn0 = 0;
|
||||||
|
|||||||
+217
-122
@@ -4,6 +4,7 @@
|
|||||||
#include "bootstrap.h"
|
#include "bootstrap.h"
|
||||||
#include "utxosnapshot.h"
|
#include "utxosnapshot.h"
|
||||||
#include "txdb.h"
|
#include "txdb.h"
|
||||||
|
#include "checkpoints.h"
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
#include "key.h"
|
#include "key.h"
|
||||||
#include "base58.h"
|
#include "base58.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "json/nlohmann_json.hpp"
|
||||||
|
|
||||||
extern const std::string strMessageMagic;
|
extern const std::string strMessageMagic;
|
||||||
|
|
||||||
@@ -30,12 +32,14 @@ extern const std::string strMessageMagic;
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
#else
|
#else
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/time.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -88,6 +92,20 @@ static SOCKET ConnectDirectTCP(const std::string& host, int port, std::string& s
|
|||||||
if (hSocket == INVALID_SOCKET)
|
if (hSocket == INVALID_SOCKET)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// A bootstrap endpoint must not be able to wedge daemon startup by
|
||||||
|
// accepting a connection and then never sending a response.
|
||||||
|
#ifdef WIN32
|
||||||
|
DWORD timeoutMs = 30000;
|
||||||
|
setsockopt(hSocket, SOL_SOCKET, SO_RCVTIMEO,
|
||||||
|
reinterpret_cast<const char*>(&timeoutMs), sizeof(timeoutMs));
|
||||||
|
setsockopt(hSocket, SOL_SOCKET, SO_SNDTIMEO,
|
||||||
|
reinterpret_cast<const char*>(&timeoutMs), sizeof(timeoutMs));
|
||||||
|
#else
|
||||||
|
struct timeval timeout = {30, 0};
|
||||||
|
setsockopt(hSocket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||||
|
setsockopt(hSocket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
|
||||||
|
#endif
|
||||||
|
|
||||||
if (connect(hSocket, rp->ai_addr, (int)rp->ai_addrlen) == 0)
|
if (connect(hSocket, rp->ai_addr, (int)rp->ai_addrlen) == 0)
|
||||||
break; // success
|
break; // success
|
||||||
|
|
||||||
@@ -154,8 +172,11 @@ struct HttpConn {
|
|||||||
strError = "Failed to create SSL context";
|
strError = "Failed to create SSL context";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Skip cert verification — we verify data integrity via checkpoint hashes
|
if (SSL_CTX_set_default_verify_paths(ctx) != 1) {
|
||||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr);
|
strError = "Failed to load the system TLS trust store";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, nullptr);
|
||||||
|
|
||||||
ssl = SSL_new(ctx);
|
ssl = SSL_new(ctx);
|
||||||
if (!ssl) {
|
if (!ssl) {
|
||||||
@@ -163,7 +184,11 @@ struct HttpConn {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
SSL_set_fd(ssl, (int)sock);
|
SSL_set_fd(ssl, (int)sock);
|
||||||
SSL_set_tlsext_host_name(ssl, hostname.c_str()); // SNI
|
if (SSL_set_tlsext_host_name(ssl, hostname.c_str()) != 1 ||
|
||||||
|
SSL_set1_host(ssl, hostname.c_str()) != 1) {
|
||||||
|
strError = "Failed to configure TLS hostname verification for " + hostname;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (SSL_connect(ssl) != 1) {
|
if (SSL_connect(ssl) != 1) {
|
||||||
unsigned long err = ERR_get_error();
|
unsigned long err = ERR_get_error();
|
||||||
@@ -172,6 +197,10 @@ struct HttpConn {
|
|||||||
strError = "TLS handshake failed with " + hostname + ": " + errBuf;
|
strError = "TLS handshake failed with " + hostname + ": " + errBuf;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (SSL_get_verify_result(ssl) != X509_V_OK) {
|
||||||
|
strError = "TLS certificate verification failed for " + hostname;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -229,13 +258,18 @@ bool DownloadFile(const std::string& host, const std::string& urlPath,
|
|||||||
ProgressCallback progressFn,
|
ProgressCallback progressFn,
|
||||||
std::string& strError,
|
std::string& strError,
|
||||||
bool noProxy,
|
bool noProxy,
|
||||||
int portOverride)
|
int portOverride,
|
||||||
|
int64_t maxDownloadBytes)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
if (maxDownloadBytes <= 0) {
|
||||||
|
strError = "Download size limit must be positive";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
std::string currentHost = host;
|
std::string currentHost = host;
|
||||||
std::string currentPath = urlPath;
|
std::string currentPath = urlPath;
|
||||||
int currentPort = (portOverride > 0) ? portOverride : PORT;
|
int currentPort = (portOverride > 0) ? portOverride : PORT;
|
||||||
bool useSSL = false;
|
bool useSSL = (currentPort == 443);
|
||||||
std::string headerData;
|
std::string headerData;
|
||||||
int redirectCount = 0;
|
int redirectCount = 0;
|
||||||
const int MAX_REDIRECTS = 5;
|
const int MAX_REDIRECTS = 5;
|
||||||
@@ -324,11 +358,23 @@ bool DownloadFile(const std::string& host, const std::string& urlPath,
|
|||||||
// Parse redirect URL — supports http://, https://, and relative paths
|
// Parse redirect URL — supports http://, https://, and relative paths
|
||||||
if (location.compare(0, 7, "http://") == 0 ||
|
if (location.compare(0, 7, "http://") == 0 ||
|
||||||
location.compare(0, 8, "https://") == 0) {
|
location.compare(0, 8, "https://") == 0) {
|
||||||
if (!ParseAbsoluteUrl(location, useSSL, currentHost,
|
bool redirectUsesSSL = false;
|
||||||
currentPort, currentPath)) {
|
std::string redirectHost;
|
||||||
|
std::string redirectPath;
|
||||||
|
int redirectPort = 0;
|
||||||
|
if (!ParseAbsoluteUrl(location, redirectUsesSSL, redirectHost,
|
||||||
|
redirectPort, redirectPath)) {
|
||||||
strError = "Unsupported redirect location: " + location;
|
strError = "Unsupported redirect location: " + location;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (useSSL && !redirectUsesSSL) {
|
||||||
|
strError = "Refusing HTTPS downgrade redirect to " + location;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
useSSL = redirectUsesSSL;
|
||||||
|
currentHost = redirectHost;
|
||||||
|
currentPath = redirectPath;
|
||||||
|
currentPort = redirectPort;
|
||||||
} else if (!location.empty() && location[0] == '/') {
|
} else if (!location.empty() && location[0] == '/') {
|
||||||
currentPath = location;
|
currentPath = location;
|
||||||
} else {
|
} else {
|
||||||
@@ -362,6 +408,10 @@ bool DownloadFile(const std::string& host, const std::string& urlPath,
|
|||||||
if (lineEnd != std::string::npos)
|
if (lineEnd != std::string::npos)
|
||||||
content_length = std::stoll(headerData.substr(valStart, lineEnd - valStart));
|
content_length = std::stoll(headerData.substr(valStart, lineEnd - valStart));
|
||||||
}
|
}
|
||||||
|
if (content_length < 0 || content_length > maxDownloadBytes) {
|
||||||
|
strError = "Download response exceeds the configured size limit";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Open output file
|
// Open output file
|
||||||
FILE* file = fopen(destPath.string().c_str(), "wb");
|
FILE* file = fopen(destPath.string().c_str(), "wb");
|
||||||
@@ -385,7 +435,18 @@ bool DownloadFile(const std::string& host, const std::string& urlPath,
|
|||||||
}
|
}
|
||||||
if (n == 0) break; // EOF
|
if (n == 0) break; // EOF
|
||||||
|
|
||||||
fwrite(chunk, 1, n, file);
|
if (bytes_written > maxDownloadBytes - n) {
|
||||||
|
fclose(file);
|
||||||
|
fs::remove(destPath);
|
||||||
|
strError = "Download response exceeded the configured size limit";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (fwrite(chunk, 1, n, file) != static_cast<size_t>(n)) {
|
||||||
|
fclose(file);
|
||||||
|
fs::remove(destPath);
|
||||||
|
strError = "Failed writing bootstrap data to disk";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
bytes_written += n;
|
bytes_written += n;
|
||||||
|
|
||||||
if (progressFn && (bytes_written - last_progress >= 262144)) {
|
if (progressFn && (bytes_written - last_progress >= 262144)) {
|
||||||
@@ -422,7 +483,8 @@ bool FetchFileList(const std::string& host,
|
|||||||
fs::path tmpPath = fs::temp_directory_path() / "triangles_bootstrap_filelist.txt";
|
fs::path tmpPath = fs::temp_directory_path() / "triangles_bootstrap_filelist.txt";
|
||||||
|
|
||||||
std::string urlPath = std::string(BASE_PATH) + "filelist.txt";
|
std::string urlPath = std::string(BASE_PATH) + "filelist.txt";
|
||||||
if (!DownloadFile(host, urlPath, tmpPath, nullptr, strError, noProxy))
|
if (!DownloadFile(host, urlPath, tmpPath, nullptr, strError, noProxy,
|
||||||
|
-1, 1024 * 1024))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Read lines
|
// Read lines
|
||||||
@@ -452,23 +514,6 @@ bool FetchFileList(const std::string& host,
|
|||||||
|
|
||||||
// --- tar.gz bootstrap support ---
|
// --- tar.gz bootstrap support ---
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// Parse a tar octal field (ASCII octal, null/space terminated)
|
|
||||||
static int64_t ParseTarOctal(const char* field, size_t len)
|
|
||||||
{
|
|
||||||
int64_t result = 0;
|
|
||||||
for (size_t i = 0; i < len && field[i] != '\0' && field[i] != ' '; i++) {
|
|
||||||
if (field[i] < '0' || field[i] > '7') continue;
|
|
||||||
result = (result << 3) | (field[i] - '0');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract a tar.gz file to a destination directory
|
|
||||||
|
|
||||||
} // anonymous namespace
|
|
||||||
|
|
||||||
bool ParseManifest(const fs::path& manifestPath,
|
bool ParseManifest(const fs::path& manifestPath,
|
||||||
SnapshotManifest& manifest,
|
SnapshotManifest& manifest,
|
||||||
std::string& strError)
|
std::string& strError)
|
||||||
@@ -575,12 +620,9 @@ bool VerifyManifest(const SnapshotManifest& manifest,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── Signature verification (#11) ─────────────────────────────────────
|
// ─── Signature verification (#11) ─────────────────────────────────────
|
||||||
// If the manifest includes a signature, verify it against the
|
// Legacy pre-built indexes are never accepted without authentication.
|
||||||
// compiled-in snapshot signing key. This prevents MITM attacks
|
// This format is disabled below, but keep its verifier fail-closed so a
|
||||||
// where an attacker replaces the snapshot file on the bootstrap server.
|
// future caller cannot silently revive the old trust behavior.
|
||||||
//
|
|
||||||
// If no signature is present, print a warning but continue (backward
|
|
||||||
// compatibility with older snapshots that pre-date signing).
|
|
||||||
if (!manifest.signature.empty()) {
|
if (!manifest.signature.empty()) {
|
||||||
// Build the message that was signed: "height||hash" (ASCII)
|
// Build the message that was signed: "height||hash" (ASCII)
|
||||||
std::string message = std::to_string(manifest.height) + "||" + manifest.hash;
|
std::string message = std::to_string(manifest.height) + "||" + manifest.hash;
|
||||||
@@ -656,12 +698,12 @@ bool VerifyManifest(const SnapshotManifest& manifest,
|
|||||||
strError = "Snapshot manifest signature INVALID — possible tampering detected";
|
strError = "Snapshot manifest signature INVALID — possible tampering detected";
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
// rc < 0 means error (e.g., placeholder zero pubkey not yet deployed)
|
strError = "Snapshot manifest signature verification error";
|
||||||
printf("WARNING: Snapshot manifest signature verification error (rc=%d). "
|
return false;
|
||||||
"Signing key may not be deployed yet. Proceeding without verification.\n", rc);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printf("WARNING: Snapshot manifest has no signature — loading WITHOUT signature verification\n");
|
strError = "Snapshot manifest has no signature";
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -672,6 +714,13 @@ bool DownloadBootstrap(const std::string& host,
|
|||||||
ProgressCallback progressFn,
|
ProgressCallback progressFn,
|
||||||
std::string& strError)
|
std::string& strError)
|
||||||
{
|
{
|
||||||
|
(void)host;
|
||||||
|
(void)dataDir;
|
||||||
|
(void)progressFn;
|
||||||
|
strError = "Legacy file-list bootstrap is disabled; use a compiled-hash UTXO snapshot or sync from genesis";
|
||||||
|
return false;
|
||||||
|
|
||||||
|
#if 0
|
||||||
bool gotBlockFile = false;
|
bool gotBlockFile = false;
|
||||||
|
|
||||||
// FastImport removed (commit bdb7253). v2 UTXO snapshot is the ONLY
|
// FastImport removed (commit bdb7253). v2 UTXO snapshot is the ONLY
|
||||||
@@ -758,8 +807,10 @@ bool DownloadBootstrap(const std::string& host,
|
|||||||
fs::remove(manifestPath);
|
fs::remove(manifestPath);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Try to find the canonical UTXO snapshot entry in the bootstrap server's
|
// Try to find the canonical UTXO snapshot entry in the bootstrap server's
|
||||||
@@ -892,6 +943,7 @@ bool UnsetTrustedSnapshotPublisher(std::string& strError)
|
|||||||
fs::remove(filePath);
|
fs::remove(filePath);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Re-enter anonymous namespace for the remaining file-private helpers.
|
// Re-enter anonymous namespace for the remaining file-private helpers.
|
||||||
// (IsTrustedSnapshotSigner / VerifySignedMessage / ExtractJsonString are
|
// (IsTrustedSnapshotSigner / VerifySignedMessage / ExtractJsonString are
|
||||||
@@ -899,6 +951,7 @@ bool UnsetTrustedSnapshotPublisher(std::string& strError)
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
#if 0
|
||||||
bool IsTrustedSnapshotSigner(const std::string& addr)
|
bool IsTrustedSnapshotSigner(const std::string& addr)
|
||||||
{
|
{
|
||||||
// 1. Runtime override (set via RPC).
|
// 1. Runtime override (set via RPC).
|
||||||
@@ -1072,6 +1125,7 @@ bool FindCanonicalSnapshotInManifest(const std::string& manifestText,
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Read an entire file into a string. Empty string on error.
|
// Read an entire file into a string. Empty string on error.
|
||||||
std::string ReadFileToString(const fs::path& path)
|
std::string ReadFileToString(const fs::path& path)
|
||||||
@@ -1114,6 +1168,80 @@ std::string Sha256OfFile(const fs::path& path)
|
|||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool IsHexString(const std::string& value, size_t expectedLength)
|
||||||
|
{
|
||||||
|
if (value.size() != expectedLength)
|
||||||
|
return false;
|
||||||
|
for (unsigned char c : value) {
|
||||||
|
if (!std::isxdigit(c))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
bool ParseRemoteSnapshotManifest(const std::string& manifestText,
|
||||||
|
RemoteSnapshot& snapshot,
|
||||||
|
std::string& strError)
|
||||||
|
{
|
||||||
|
snapshot = RemoteSnapshot{};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const nlohmann::json root = nlohmann::json::parse(manifestText);
|
||||||
|
if (!root.is_object() || !root.contains("canonical") ||
|
||||||
|
!root.contains("files") || !root.contains("chain_tip")) {
|
||||||
|
strError = "manifest.json is missing canonical, files, or chain_tip";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.filename = root.at("canonical").at("snapshot").get<std::string>();
|
||||||
|
if (snapshot.filename.empty() || snapshot.filename == "." ||
|
||||||
|
snapshot.filename == ".." ||
|
||||||
|
snapshot.filename.find('/') != std::string::npos ||
|
||||||
|
snapshot.filename.find('\\') != std::string::npos) {
|
||||||
|
strError = "manifest snapshot filename must be a plain filename";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nlohmann::json& files = root.at("files");
|
||||||
|
if (!files.is_object() || !files.contains(snapshot.filename)) {
|
||||||
|
strError = "canonical snapshot is absent from the files object";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nlohmann::json& file = files.at(snapshot.filename);
|
||||||
|
const std::string type = file.at("type").get<std::string>();
|
||||||
|
if (type.rfind("utxo_snapshot", 0) != 0) {
|
||||||
|
strError = "canonical file is not a UTXO snapshot";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.sha256 = file.at("sha256").get<std::string>();
|
||||||
|
std::transform(snapshot.sha256.begin(), snapshot.sha256.end(),
|
||||||
|
snapshot.sha256.begin(),
|
||||||
|
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||||
|
snapshot.height = root.at("chain_tip").at("height").get<int>();
|
||||||
|
snapshot.blockHash = root.at("chain_tip").at("blockhash").get<std::string>();
|
||||||
|
std::transform(snapshot.blockHash.begin(), snapshot.blockHash.end(),
|
||||||
|
snapshot.blockHash.begin(),
|
||||||
|
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||||
|
|
||||||
|
if (snapshot.height <= 0 || !IsHexString(snapshot.sha256, 64) ||
|
||||||
|
!IsHexString(snapshot.blockHash, 64)) {
|
||||||
|
strError = "manifest snapshot height or hash fields are invalid";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
strError = std::string("invalid manifest.json: ") + e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool DownloadUtxoSnapshot(const std::string& host,
|
bool DownloadUtxoSnapshot(const std::string& host,
|
||||||
const fs::path& dataDir,
|
const fs::path& dataDir,
|
||||||
ProgressCallback progressFn,
|
ProgressCallback progressFn,
|
||||||
@@ -1121,79 +1249,51 @@ bool DownloadUtxoSnapshot(const std::string& host,
|
|||||||
{
|
{
|
||||||
const bool noProxy = true;
|
const bool noProxy = true;
|
||||||
|
|
||||||
// Step 1: discover the canonical snapshot filename + expected SHA256 +
|
// manifest.json is discovery metadata, not a trust root. The only accepted
|
||||||
// per-snapshot manifest filename from the big manifest.json. Falls back
|
// snapshot hash is the one compiled into this release for the same height.
|
||||||
// to legacy URL if manifest unavailable.
|
|
||||||
std::string snapshotFilename = "utxo-snapshot.bin";
|
|
||||||
std::string expectedSha256;
|
|
||||||
std::string snapshotManifestFilename;
|
|
||||||
bool haveManifest = false;
|
|
||||||
|
|
||||||
fs::path tmpManifest = dataDir / "manifest.json.tmp";
|
fs::path tmpManifest = dataDir / "manifest.json.tmp";
|
||||||
if (DownloadFile(host, "manifest.json", tmpManifest, nullptr, strError, noProxy)) {
|
if (!DownloadFile(host, std::string(BASE_PATH) + "manifest.json",
|
||||||
std::string text = ReadFileToString(tmpManifest);
|
tmpManifest, nullptr, strError, noProxy,
|
||||||
|
-1, 4 * 1024 * 1024)) {
|
||||||
fs::remove(tmpManifest);
|
fs::remove(tmpManifest);
|
||||||
|
return false;
|
||||||
std::string mFile, mSha, mManifest;
|
|
||||||
std::string mErr;
|
|
||||||
if (FindCanonicalSnapshotInManifest(text, mFile, mSha, mManifest, mErr)) {
|
|
||||||
snapshotFilename = mFile;
|
|
||||||
expectedSha256 = mSha;
|
|
||||||
snapshotManifestFilename = mManifest;
|
|
||||||
haveManifest = true;
|
|
||||||
printf("Bootstrap: manifest declares canonical snapshot %s (sha256=%s)\n",
|
|
||||||
snapshotFilename.c_str(), expectedSha256.substr(0, 16).c_str());
|
|
||||||
} else {
|
|
||||||
printf("Bootstrap: manifest parse failed (%s) — falling back to legacy URL\n",
|
|
||||||
mErr.c_str());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
printf("Bootstrap: no manifest.json available — falling back to legacy URL\n");
|
|
||||||
strError.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: verify the per-snapshot manifest's signature. This is the
|
const std::string manifestText = ReadFileToString(tmpManifest);
|
||||||
// AUTHENTICATION gate — the signature attests that the listed snapshot
|
fs::remove(tmpManifest);
|
||||||
// file came from a trusted operator. No checkpoint required; signature
|
if (manifestText.empty()) {
|
||||||
// alone proves authenticity.
|
strError = "Cannot read downloaded manifest.json";
|
||||||
if (!snapshotManifestFilename.empty()) {
|
return false;
|
||||||
fs::path tmpSnapManifest = dataDir / "snapshot-manifest.tmp";
|
|
||||||
if (!DownloadFile(host, snapshotManifestFilename, tmpSnapManifest, nullptr, strError, noProxy)) {
|
|
||||||
fs::remove(tmpSnapManifest);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::string snapManifestText = ReadFileToString(tmpSnapManifest);
|
|
||||||
fs::remove(tmpSnapManifest);
|
|
||||||
|
|
||||||
std::string signerAddr = ExtractJsonString(snapManifestText, "signing_address");
|
|
||||||
std::string message = ExtractJsonString(snapManifestText, "message");
|
|
||||||
std::string signature = ExtractJsonString(snapManifestText, "signature");
|
|
||||||
std::string declaredSha = ExtractJsonString(snapManifestText, "snapshot_sha256");
|
|
||||||
|
|
||||||
if (signerAddr.empty() || message.empty() || signature.empty()) {
|
|
||||||
strError = "per-snapshot manifest missing required fields (signing_address/message/signature)";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!IsTrustedSnapshotSigner(signerAddr)) {
|
|
||||||
strError = "snapshot manifest signer " + signerAddr + " is not in trusted signers list";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::string vErr;
|
|
||||||
if (!VerifySignedMessage(signerAddr, signature, message, vErr)) {
|
|
||||||
strError = "snapshot signature verification failed: " + vErr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!declaredSha.empty())
|
|
||||||
expectedSha256 = declaredSha;
|
|
||||||
printf("Bootstrap: snapshot signature verified (signer=%s)\n", signerAddr.c_str());
|
|
||||||
} else {
|
|
||||||
printf("Bootstrap: WARNING — no per-snapshot manifest available; "
|
|
||||||
"loading snapshot WITHOUT signature verification\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: download the canonical snapshot file.
|
RemoteSnapshot snapshot;
|
||||||
|
if (!ParseRemoteSnapshotManifest(manifestText, snapshot, strError))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const uint256 manifestBlockHash(snapshot.blockHash);
|
||||||
|
if (!Checkpoints::IsKnownCheckpoint(snapshot.height, manifestBlockHash)) {
|
||||||
|
strError = "Server snapshot tip is not a hardened checkpoint in this release";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 compiledFileHash;
|
||||||
|
if (!Checkpoints::GetSnapshotHash(snapshot.height, compiledFileHash)) {
|
||||||
|
strError = "Snapshot height " + std::to_string(snapshot.height) +
|
||||||
|
" has no file hash compiled into this release";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::string compiledSha256 = compiledFileHash.ToString();
|
||||||
|
if (snapshot.sha256 != compiledSha256) {
|
||||||
|
strError = "Server snapshot hash does not match the hash compiled into this release";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Bootstrap: manifest selects compiled snapshot %s at height %d (sha256=%s)\n",
|
||||||
|
snapshot.filename.c_str(), snapshot.height,
|
||||||
|
compiledSha256.substr(0, 16).c_str());
|
||||||
|
|
||||||
fs::path tmpPath = dataDir / "utxo-snapshot.bin.tmp";
|
fs::path tmpPath = dataDir / "utxo-snapshot.bin.tmp";
|
||||||
std::string urlPath = std::string(BASE_PATH) + snapshotFilename;
|
std::string urlPath = std::string(BASE_PATH) + snapshot.filename;
|
||||||
|
|
||||||
printf("Bootstrap: downloading UTXO snapshot from %s%s...\n", host.c_str(), urlPath.c_str());
|
printf("Bootstrap: downloading UTXO snapshot from %s%s...\n", host.c_str(), urlPath.c_str());
|
||||||
|
|
||||||
@@ -1202,30 +1302,25 @@ bool DownloadUtxoSnapshot(const std::string& host,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: verify the downloaded file's SHA256 against the manifest.
|
const std::string actualSha256 = Sha256OfFile(tmpPath);
|
||||||
if (!expectedSha256.empty()) {
|
if (actualSha256.empty()) {
|
||||||
std::string actualSha = Sha256OfFile(tmpPath);
|
strError = "Cannot read downloaded snapshot for SHA256 verification";
|
||||||
if (actualSha.empty()) {
|
fs::remove(tmpPath);
|
||||||
strError = "Cannot read downloaded snapshot for SHA256 verification";
|
return false;
|
||||||
fs::remove(tmpPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (actualSha != expectedSha256) {
|
|
||||||
strError = "Snapshot SHA256 mismatch: expected " + expectedSha256
|
|
||||||
+ ", got " + actualSha
|
|
||||||
+ " (manifest/snapshot tampering or server misconfiguration)";
|
|
||||||
fs::remove(tmpPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
printf("Bootstrap: snapshot SHA256 verified (%s)\n", actualSha.substr(0, 16).c_str());
|
|
||||||
}
|
}
|
||||||
|
if (actualSha256 != compiledSha256) {
|
||||||
|
strError = "Snapshot SHA256 does not match the hash compiled into this release";
|
||||||
|
fs::remove(tmpPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
printf("Bootstrap: compiled snapshot SHA256 verified (%s)\n",
|
||||||
|
actualSha256.substr(0, 16).c_str());
|
||||||
|
|
||||||
printf("Bootstrap: UTXO snapshot downloaded, loading into database...\n");
|
printf("Bootstrap: UTXO snapshot downloaded, loading into database...\n");
|
||||||
|
|
||||||
// Step 5: load the snapshot. requireCheckpoint is FALSE — signature is
|
// File hash and tip checkpoint are independent gates. The hash commits to
|
||||||
// the authentication gate; checkpoints would force snapshots only at
|
// the complete serialized UTXO set; the checkpoint commits to chain identity.
|
||||||
// specific heights. Signature alone is sufficient.
|
if (!UtxoSnapshot::LoadSnapshot(tmpPath, dataDir, strError, /*requireCheckpoint=*/true)) {
|
||||||
if (!UtxoSnapshot::LoadSnapshot(tmpPath, dataDir, strError, /*requireCheckpoint=*/false)) {
|
|
||||||
fs::remove(tmpPath);
|
fs::remove(tmpPath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-25
@@ -8,13 +8,14 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
namespace Bootstrap {
|
namespace Bootstrap {
|
||||||
|
|
||||||
// Bootstrap server configuration
|
// Bootstrap server configuration
|
||||||
static const char* DEFAULT_HOST = "bootstrap.cryptographic-triangles.org";
|
inline constexpr const char* DEFAULT_HOST = "bootstrap.cryptographic-triangles.org";
|
||||||
static const char* BASE_PATH = "/";
|
inline constexpr const char* BASE_PATH = "/";
|
||||||
static const int PORT = 80;
|
inline constexpr int PORT = 443;
|
||||||
|
|
||||||
// Progress callback: (bytesDownloaded, totalBytes)
|
// Progress callback: (bytesDownloaded, totalBytes)
|
||||||
typedef std::function<void(int64_t, int64_t)> ProgressCallback;
|
typedef std::function<void(int64_t, int64_t)> ProgressCallback;
|
||||||
@@ -31,7 +32,8 @@ namespace Bootstrap {
|
|||||||
ProgressCallback progressFn,
|
ProgressCallback progressFn,
|
||||||
std::string& strError,
|
std::string& strError,
|
||||||
bool noProxy = false,
|
bool noProxy = false,
|
||||||
int portOverride = -1);
|
int portOverride = -1,
|
||||||
|
int64_t maxDownloadBytes = 4LL * 1024 * 1024 * 1024);
|
||||||
|
|
||||||
// Fetch the file manifest (list of relative paths to download)
|
// Fetch the file manifest (list of relative paths to download)
|
||||||
bool FetchFileList(const std::string& host,
|
bool FetchFileList(const std::string& host,
|
||||||
@@ -46,6 +48,23 @@ namespace Bootstrap {
|
|||||||
ProgressCallback progressFn,
|
ProgressCallback progressFn,
|
||||||
std::string& strError);
|
std::string& strError);
|
||||||
|
|
||||||
|
// Advertised identity of a snapshot listed by manifest.json.
|
||||||
|
// The advertised SHA256 is accepted only when it matches the hash compiled
|
||||||
|
// into checkpoints.cpp for the same height.
|
||||||
|
struct RemoteSnapshot {
|
||||||
|
std::string filename;
|
||||||
|
std::string sha256;
|
||||||
|
int height;
|
||||||
|
std::string blockHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse and validate the small, untrusted bootstrap manifest. This routine
|
||||||
|
// performs no network I/O and is exposed so malformed-input behavior can be
|
||||||
|
// covered by unit tests.
|
||||||
|
bool ParseRemoteSnapshotManifest(const std::string& manifestText,
|
||||||
|
RemoteSnapshot& snapshot,
|
||||||
|
std::string& strError);
|
||||||
|
|
||||||
// Snapshot manifest (parsed from snapshot.manifest in bootstrap archive)
|
// Snapshot manifest (parsed from snapshot.manifest in bootstrap archive)
|
||||||
struct SnapshotManifest {
|
struct SnapshotManifest {
|
||||||
int format; // format version, must be 1
|
int format; // format version, must be 1
|
||||||
@@ -73,27 +92,6 @@ namespace Bootstrap {
|
|||||||
ProgressCallback progressFn,
|
ProgressCallback progressFn,
|
||||||
std::string& strError);
|
std::string& strError);
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
// Trusted snapshot publisher — RPC-driven single-slot rotation
|
|
||||||
// ===================================================================
|
|
||||||
// Returns the currently active trusted publisher, or empty string if
|
|
||||||
// only the built-in fallback is in effect.
|
|
||||||
std::string GetActiveTrustedSnapshotPublisher();
|
|
||||||
|
|
||||||
// Atomically replaces the active publisher. The previous one is dropped
|
|
||||||
// immediately (Design A: single-slot, no grace period). Persists to
|
|
||||||
// <datadir>/snapshot-publisher.json so the choice survives restarts.
|
|
||||||
bool SetTrustedSnapshotPublisher(const std::string& addr,
|
|
||||||
std::string& strError);
|
|
||||||
|
|
||||||
// Clears the runtime override and reverts to the built-in fallback
|
|
||||||
// list. Also removes snapshot-publisher.json from disk.
|
|
||||||
bool UnsetTrustedSnapshotPublisher(std::string& strError);
|
|
||||||
|
|
||||||
// Called once at daemon startup (from init.cpp) to load any persisted
|
|
||||||
// runtime override.
|
|
||||||
void LoadTrustedSnapshotPublisher();
|
|
||||||
|
|
||||||
} // namespace Bootstrap
|
} // namespace Bootstrap
|
||||||
|
|
||||||
#endif // TRIANGLES_BOOTSTRAP_H
|
#endif // TRIANGLES_BOOTSTRAP_H
|
||||||
|
|||||||
@@ -425,7 +425,8 @@ bool LoadSignedCheckpoints(
|
|||||||
if (Bootstrap::DownloadFile(host, "signed-checkpoints.json",
|
if (Bootstrap::DownloadFile(host, "signed-checkpoints.json",
|
||||||
std::filesystem::temp_directory_path() / "signed-checkpoints.json.tmp",
|
std::filesystem::temp_directory_path() / "signed-checkpoints.json.tmp",
|
||||||
nullptr, strError,
|
nullptr, strError,
|
||||||
/*noProxy=*/true)) {
|
/*noProxy=*/true, /*portOverride=*/-1,
|
||||||
|
/*maxDownloadBytes=*/10 * 1024 * 1024)) {
|
||||||
std::filesystem::path tmp = std::filesystem::temp_directory_path() / "signed-checkpoints.json.tmp";
|
std::filesystem::path tmp = std::filesystem::temp_directory_path() / "signed-checkpoints.json.tmp";
|
||||||
FILE* f = fopen(tmp.string().c_str(), "rb");
|
FILE* f = fopen(tmp.string().c_str(), "rb");
|
||||||
if (f) {
|
if (f) {
|
||||||
|
|||||||
+7
-48
@@ -353,53 +353,14 @@ namespace Checkpoints
|
|||||||
|
|
||||||
bool SetCheckpointPrivKey(std::string strPrivKey)
|
bool SetCheckpointPrivKey(std::string strPrivKey)
|
||||||
{
|
{
|
||||||
// Test signing a sync-checkpoint with genesis block
|
(void)strPrivKey;
|
||||||
CSyncCheckpoint checkpoint;
|
return error("SetCheckpointPrivKey: synchronized checkpoints are disabled");
|
||||||
checkpoint.hashCheckpoint = !fTestNet ? hashGenesisBlockOfficial : hashGenesisBlockTestNet;
|
|
||||||
CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION);
|
|
||||||
sMsg << (CUnsignedSyncCheckpoint)checkpoint;
|
|
||||||
checkpoint.vchMsg = std::vector<unsigned char>(sMsg.begin(), sMsg.end());
|
|
||||||
|
|
||||||
std::vector<unsigned char> vchPrivKey = ParseHex(strPrivKey);
|
|
||||||
CKey key;
|
|
||||||
key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash
|
|
||||||
if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Test signing successful, proceed
|
|
||||||
CSyncCheckpoint::strMasterPrivKey = strPrivKey;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SendSyncCheckpoint(uint256 hashCheckpoint)
|
bool SendSyncCheckpoint(uint256 hashCheckpoint)
|
||||||
{
|
{
|
||||||
CSyncCheckpoint checkpoint;
|
(void)hashCheckpoint;
|
||||||
checkpoint.hashCheckpoint = hashCheckpoint;
|
return error("SendSyncCheckpoint: synchronized checkpoints are disabled");
|
||||||
CDataStream sMsg(SER_NETWORK, PROTOCOL_VERSION);
|
|
||||||
sMsg << (CUnsignedSyncCheckpoint)checkpoint;
|
|
||||||
checkpoint.vchMsg = std::vector<unsigned char>(sMsg.begin(), sMsg.end());
|
|
||||||
|
|
||||||
if (CSyncCheckpoint::strMasterPrivKey.empty())
|
|
||||||
return error("SendSyncCheckpoint: Checkpoint master key unavailable.");
|
|
||||||
std::vector<unsigned char> vchPrivKey = ParseHex(CSyncCheckpoint::strMasterPrivKey);
|
|
||||||
CKey key;
|
|
||||||
key.SetPrivKey(CPrivKey(vchPrivKey.begin(), vchPrivKey.end())); // if key is not correct openssl may crash
|
|
||||||
if (!key.Sign(Hash(checkpoint.vchMsg.begin(), checkpoint.vchMsg.end()), checkpoint.vchSig))
|
|
||||||
return error("SendSyncCheckpoint: Unable to sign checkpoint, check private key?");
|
|
||||||
|
|
||||||
if(!checkpoint.ProcessSyncCheckpoint(nullptr))
|
|
||||||
{
|
|
||||||
printf("WARNING: SendSyncCheckpoint: Failed to process checkpoint.\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Relay checkpoint
|
|
||||||
{
|
|
||||||
LOCK(cs_vNodes);
|
|
||||||
for (CNode* pnode : vNodes)
|
|
||||||
checkpoint.RelayTo(pnode);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the sync-checkpoint outside maturity window?
|
// Is the sync-checkpoint outside maturity window?
|
||||||
@@ -420,13 +381,11 @@ const std::string CSyncCheckpoint::strMasterPubKey = "";
|
|||||||
std::string CSyncCheckpoint::strMasterPrivKey = "";
|
std::string CSyncCheckpoint::strMasterPrivKey = "";
|
||||||
|
|
||||||
// triangles: verify signature of sync-checkpoint message
|
// triangles: verify signature of sync-checkpoint message
|
||||||
// Master key system disabled - checkpoint signatures are no longer required
|
// The master-key system is disabled. Reject these legacy messages instead of
|
||||||
|
// treating unsigned data as authenticated if a dispatcher is added later.
|
||||||
bool CSyncCheckpoint::CheckSignature()
|
bool CSyncCheckpoint::CheckSignature()
|
||||||
{
|
{
|
||||||
// Deserialize the checkpoint data without signature verification
|
return error("CSyncCheckpoint::CheckSignature: synchronized checkpoints are disabled");
|
||||||
CDataStream sMsg(vchMsg, SER_NETWORK, PROTOCOL_VERSION);
|
|
||||||
sMsg >> *(CUnsignedSyncCheckpoint*)this;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// triangles: process synchronized checkpoint
|
// triangles: process synchronized checkpoint
|
||||||
|
|||||||
+92
-61
@@ -30,8 +30,11 @@
|
|||||||
#include "addressindex.h"
|
#include "addressindex.h"
|
||||||
#include "chaindb_migrate.h"
|
#include "chaindb_migrate.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdlib>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <cerrno>
|
||||||
|
|
||||||
// Forward declaration: InitError / InitWarning are defined further down
|
// Forward declaration: InitError / InitWarning are defined further down
|
||||||
// in this file but referenced by AppInit (line ~423) before the definition.
|
// in this file but referenced by AppInit (line ~423) before the definition.
|
||||||
@@ -66,6 +69,8 @@ using namespace std;
|
|||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
std::atomic<int> g_shutdownExitCode{EXIT_SUCCESS};
|
||||||
|
|
||||||
// Acquire an exclusive, non-blocking advisory lock on the datadir .lock file
|
// Acquire an exclusive, non-blocking advisory lock on the datadir .lock file
|
||||||
// and hold it for the lifetime of the process. Replaces
|
// and hold it for the lifetime of the process. Replaces
|
||||||
// boost::interprocess::file_lock. The descriptor/handle is intentionally never
|
// boost::interprocess::file_lock. The descriptor/handle is intentionally never
|
||||||
@@ -96,8 +101,32 @@ bool LockDataDirectory(const std::filesystem::path& pathLockFile)
|
|||||||
return true; // fd held until process exit
|
return true; // fd held until process exit
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
bool EnsureOwnerOnlyFile(const std::filesystem::path& path, std::string& error)
|
||||||
|
{
|
||||||
|
struct stat fileStat;
|
||||||
|
if (::lstat(path.string().c_str(), &fileStat) != 0)
|
||||||
|
return errno == ENOENT;
|
||||||
|
if (!S_ISREG(fileStat.st_mode) || fileStat.st_uid != geteuid()) {
|
||||||
|
error = path.string() + " must be a regular file owned by the daemon user";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((fileStat.st_mode & (S_IRWXG | S_IRWXO)) != 0 &&
|
||||||
|
::chmod(path.string().c_str(), S_IRUSR | S_IWUSR) != 0) {
|
||||||
|
error = "could not restrict permissions on " + path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
void MarkShutdownFailure()
|
||||||
|
{
|
||||||
|
g_shutdownExitCode.store(EXIT_FAILURE, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<CWallet> pwalletMain;
|
std::unique_ptr<CWallet> pwalletMain;
|
||||||
CClientUIInterface uiInterface;
|
CClientUIInterface uiInterface;
|
||||||
std::string strWalletFileName;
|
std::string strWalletFileName;
|
||||||
@@ -144,7 +173,8 @@ void ExitTimeout(void* parg)
|
|||||||
{
|
{
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
MilliSleep(5000);
|
MilliSleep(5000);
|
||||||
ExitProcess(0);
|
ExitProcess(static_cast<UINT>(
|
||||||
|
g_shutdownExitCode.load(std::memory_order_relaxed)));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,7 +451,11 @@ void Shutdown(void* parg)
|
|||||||
// MakeChainDB()->Close();
|
// MakeChainDB()->Close();
|
||||||
bitdb.Flush(false);
|
bitdb.Flush(false);
|
||||||
bitdb.Flush(true);
|
bitdb.Flush(true);
|
||||||
fs::remove(GetPidFile());
|
std::error_code pidFileError;
|
||||||
|
fs::remove(GetPidFile(), pidFileError);
|
||||||
|
if (pidFileError)
|
||||||
|
printf("Warning: could not remove PID file: %s\n",
|
||||||
|
pidFileError.message().c_str());
|
||||||
UnregisterWallet(pwalletMain.get());
|
UnregisterWallet(pwalletMain.get());
|
||||||
pwalletMain.reset();
|
pwalletMain.reset();
|
||||||
// DB is flushed and wallet saved - safe to force-exit if something hangs
|
// DB is flushed and wallet saved - safe to force-exit if something hangs
|
||||||
@@ -431,7 +465,7 @@ void Shutdown(void* parg)
|
|||||||
fExit = true;
|
fExit = true;
|
||||||
#ifndef QT_GUI
|
#ifndef QT_GUI
|
||||||
// ensure non-UI client gets exited here, but let Triangles-Qt reach 'return 0;' in triangles.cpp
|
// ensure non-UI client gets exited here, but let Triangles-Qt reach 'return 0;' in triangles.cpp
|
||||||
exit(0);
|
exit(g_shutdownExitCode.load(std::memory_order_relaxed));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -528,8 +562,10 @@ bool AppInit(int argc, char* argv[])
|
|||||||
} catch (...) {
|
} catch (...) {
|
||||||
PrintException(nullptr, "AppInit()");
|
PrintException(nullptr, "AppInit()");
|
||||||
}
|
}
|
||||||
if (!fRet)
|
if (!fRet) {
|
||||||
|
MarkShutdownFailure();
|
||||||
Shutdown(nullptr);
|
Shutdown(nullptr);
|
||||||
|
}
|
||||||
return fRet;
|
return fRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,8 +646,8 @@ std::string HelpMessage()
|
|||||||
//" -onlynet=<net> " + _("Only connect to nodes in network <net> (IPv4, IPv6 or Tor)") + "\n" +
|
//" -onlynet=<net> " + _("Only connect to nodes in network <net> (IPv4, IPv6 or Tor)") + "\n" +
|
||||||
//" -discover " + _("Discover own IP address (default: 1 when listening and no -externalip)") + "\n" +
|
//" -discover " + _("Discover own IP address (default: 1 when listening and no -externalip)") + "\n" +
|
||||||
//" -irc " + _("Find peers using internet relay chat (default: 0)") + "\n" +
|
//" -irc " + _("Find peers using internet relay chat (default: 0)") + "\n" +
|
||||||
//" -listen " + _("Accept connections from outside (default: 1 if no -proxy or -connect)") + "\n" +
|
" -listen " + _("Accept inbound peer connections (default: 1 unless -proxy or -connect is set)") + "\n" +
|
||||||
//" -bind=<addr> " + _("Bind to given address. Use [host]:port notation for IPv6") + "\n" +
|
" -bind=<addr> " + _("Bind inbound peers to this address. Use [host]:port notation for IPv6") + "\n" +
|
||||||
// -dnsseed " + _("Find peers using DNS lookup (default: 1)") + "\n" +
|
// -dnsseed " + _("Find peers using DNS lookup (default: 1)") + "\n" +
|
||||||
" -staking " + _("Stake your coins to support network and gain reward (default: 1)") + "\n" +
|
" -staking " + _("Stake your coins to support network and gain reward (default: 1)") + "\n" +
|
||||||
" -synctime " + _("Sync time with other nodes. Disable if time on your system is precise e.g. syncing with NTP (default: 1)") + "\n" +
|
" -synctime " + _("Sync time with other nodes. Disable if time on your system is precise e.g. syncing with NTP (default: 1)") + "\n" +
|
||||||
@@ -652,8 +688,11 @@ std::string HelpMessage()
|
|||||||
#endif
|
#endif
|
||||||
" -rpcuser=<user> " + _("Username for JSON-RPC connections") + "\n" +
|
" -rpcuser=<user> " + _("Username for JSON-RPC connections") + "\n" +
|
||||||
" -rpcpassword=<pw> " + _("Password for JSON-RPC connections") + "\n" +
|
" -rpcpassword=<pw> " + _("Password for JSON-RPC connections") + "\n" +
|
||||||
" -rpcport=<port> " + _("Listen for JSON-RPC connections on <port> (default: 19111 or testnet: 19112)") + "\n" +
|
" -rpcport=<port> " + _("Listen for JSON-RPC connections on <port> (default: 19112 or testnet: 19111)") + "\n" +
|
||||||
" -rpcallowip=<ip> " + _("Allow JSON-RPC connections from specified IP address") + "\n" +
|
" -rpcbind=<addr> " + _("Bind JSON-RPC to this address (default: loopback only; use * explicitly for all interfaces)") + "\n" +
|
||||||
|
" -rpcallowip=<ip> " + _("Allow JSON-RPC clients matching this address pattern; does not change the bind address") + "\n" +
|
||||||
|
" -rpcallowmethod=<name> " + _("Allow only this JSON-RPC method (repeat for each method; default: all)") + "\n" +
|
||||||
|
" -rpcservertimeout=<n> " + _("RPC socket read/write timeout in seconds (default: 30, range: 1-600)") + "\n" +
|
||||||
" -rpcconnect=<ip> " + _("Send commands to node running on <ip> (default: 127.0.0.1)") + "\n" +
|
" -rpcconnect=<ip> " + _("Send commands to node running on <ip> (default: 127.0.0.1)") + "\n" +
|
||||||
" -blocknotify=<cmd> " + _("Execute command when the best block changes (%s in cmd is replaced by block hash)") + "\n" +
|
" -blocknotify=<cmd> " + _("Execute command when the best block changes (%s in cmd is replaced by block hash)") + "\n" +
|
||||||
" -walletnotify=<cmd> " + _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)") + "\n" +
|
" -walletnotify=<cmd> " + _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)") + "\n" +
|
||||||
@@ -1120,24 +1159,19 @@ bool AppInit2()
|
|||||||
fUseUPnP = GetBoolArg("-upnp", USE_UPNP);
|
fUseUPnP = GetBoolArg("-upnp", USE_UPNP);
|
||||||
#endif
|
#endif
|
||||||
bool fBound = false;
|
bool fBound = false;
|
||||||
if (true) {
|
if (!fNoListen) {
|
||||||
if (true) {
|
if (mapArgs.count("-bind")) {
|
||||||
do {
|
for (const std::string& bindAddress : mapMultiArgs["-bind"]) {
|
||||||
// W1: Bind to all interfaces so external peers can connect.
|
|
||||||
//
|
|
||||||
// The previous code went through Lookup("0.0.0.0", ...) which
|
|
||||||
// hands the literal string to getaddrinfo(). On Windows that
|
|
||||||
// resolver can fail to map "0.0.0.0" to INADDR_ANY and the
|
|
||||||
// daemon would abort at startup with "Cannot resolve binding
|
|
||||||
// address". Construct the CService directly from INADDR_ANY
|
|
||||||
// instead — this is the canonical "any-address" binding and
|
|
||||||
// works on every platform without consulting the resolver.
|
|
||||||
CService addrBind;
|
CService addrBind;
|
||||||
struct in_addr any;
|
if (!Lookup(bindAddress.c_str(), addrBind, GetListenPort(), false))
|
||||||
any.s_addr = htonl(INADDR_ANY);
|
return InitError(strprintf(_("Cannot resolve -bind address: '%s'"),
|
||||||
addrBind = CService(any, GetListenPort());
|
bindAddress.c_str()));
|
||||||
fBound |= Bind(addrBind);
|
fBound |= Bind(addrBind);
|
||||||
} while (false);
|
}
|
||||||
|
} else {
|
||||||
|
struct in_addr any;
|
||||||
|
any.s_addr = htonl(INADDR_ANY);
|
||||||
|
fBound = Bind(CService(any, GetListenPort()));
|
||||||
}
|
}
|
||||||
if (!fBound)
|
if (!fBound)
|
||||||
return InitError(_("Failed to listen on any port."));
|
return InitError(_("Failed to listen on any port."));
|
||||||
@@ -1167,10 +1201,9 @@ bool AppInit2()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mapArgs.count("-checkpointkey")) // triangles: checkpoint master priv key
|
if (mapArgs.count("-checkpointkey"))
|
||||||
{
|
{
|
||||||
if (!Checkpoints::SetCheckpointPrivKey(GetArg(std::string_view{"-checkpointkey"}, std::string_view{""})))
|
return InitError(_("Synchronized checkpoint signing is disabled."));
|
||||||
InitError(_("Unable to sign checkpoint, wrong checkpointkey?\n"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (string strDest : mapMultiArgs["-seednode"])
|
for (string strDest : mapMultiArgs["-seednode"])
|
||||||
@@ -1178,8 +1211,8 @@ bool AppInit2()
|
|||||||
StartupPerfLog("network_init", GetTimeMillis() - nStart, strprintf("listen=%d seednodes=%" PRIszu, !fNoListen, mapMultiArgs["-seednode"].size()));
|
StartupPerfLog("network_init", GetTimeMillis() - nStart, strprintf("listen=%d seednodes=%" PRIszu, !fNoListen, mapMultiArgs["-seednode"].size()));
|
||||||
|
|
||||||
// ********************************************************* Step 6b: bootstrap download (daemon)
|
// ********************************************************* Step 6b: bootstrap download (daemon)
|
||||||
// Automatic: if data dir has no blockchain, bootstrap without asking.
|
// Remote HTTP bootstrap is opt-in via -bootstrap. Fresh nodes otherwise
|
||||||
// Can also be forced with -bootstrap flag, or disabled with -nobootstrap.
|
// use the compiled-hash P2P snapshot path or sync from genesis.
|
||||||
//
|
//
|
||||||
// v5.9.5: P2P UTXO snapshot fetch is the default for fresh installs (Step 11.6).
|
// v5.9.5: P2P UTXO snapshot fetch is the default for fresh installs (Step 11.6).
|
||||||
// The legacy clearnet HTTP bootstrap only runs when the user explicitly requests
|
// The legacy clearnet HTTP bootstrap only runs when the user explicitly requests
|
||||||
@@ -1187,20 +1220,13 @@ bool AppInit2()
|
|||||||
// Bootstrap auto-download works for both GUI and daemon.
|
// Bootstrap auto-download works for both GUI and daemon.
|
||||||
// GUI users get the same automatic bootstrap on fresh installs.
|
// GUI users get the same automatic bootstrap on fresh installs.
|
||||||
{
|
{
|
||||||
bool wantsBootstrap = GetBoolArg("-bootstrap", false);
|
|
||||||
bool noBootstrap = GetBoolArg("-nobootstrap", false);
|
bool noBootstrap = GetBoolArg("-nobootstrap", false);
|
||||||
bool snapshotMode = GetBoolArg("-snapshot", true);
|
bool wantsBootstrap = GetBoolArg("-bootstrap", false) && !noBootstrap;
|
||||||
fs::path dataPath = GetDataDir();
|
fs::path dataPath = GetDataDir();
|
||||||
// Load any runtime trusted snapshot publisher override that was
|
|
||||||
// persisted by a previous settrustedv2snapshotpublisher call.
|
|
||||||
Bootstrap::LoadTrustedSnapshotPublisher();
|
|
||||||
bool needsBootstrap = Bootstrap::NeedsBootstrap(dataPath);
|
bool needsBootstrap = Bootstrap::NeedsBootstrap(dataPath);
|
||||||
|
|
||||||
if (needsBootstrap && !noBootstrap) {
|
if (needsBootstrap && !wantsBootstrap) {
|
||||||
printf("Bootstrap: no blockchain data found — downloading UTXO snapshot automatically.\n");
|
printf("Bootstrap: no blockchain data found; remote bootstrap is disabled unless -bootstrap is set.\n");
|
||||||
printf("Bootstrap: (use -nobootstrap to skip)\n");
|
|
||||||
uiInterface.InitMessage(_("Downloading UTXO snapshot..."));
|
|
||||||
wantsBootstrap = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wantsBootstrap)
|
if (wantsBootstrap)
|
||||||
@@ -1208,7 +1234,6 @@ bool AppInit2()
|
|||||||
int64_t nBootstrapStart = GetTimeMillis();
|
int64_t nBootstrapStart = GetTimeMillis();
|
||||||
fs::path dataPath = GetDataDir();
|
fs::path dataPath = GetDataDir();
|
||||||
std::string host = Bootstrap::DEFAULT_HOST;
|
std::string host = Bootstrap::DEFAULT_HOST;
|
||||||
std::string strError;
|
|
||||||
|
|
||||||
int64_t lastGuiUpdate = 0;
|
int64_t lastGuiUpdate = 0;
|
||||||
auto progressFn = [&lastGuiUpdate](int64_t bytesDownloaded, int64_t totalBytes) {
|
auto progressFn = [&lastGuiUpdate](int64_t bytesDownloaded, int64_t totalBytes) {
|
||||||
@@ -1250,19 +1275,10 @@ bool AppInit2()
|
|||||||
triedUtxoSnapshot = true;
|
triedUtxoSnapshot = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to full bootstrap.tar.gz if UTXO snapshot failed
|
// Never consume a server-directed file list. If the authenticated
|
||||||
|
// snapshot is unavailable, normal peer-to-peer sync is the safe fallback.
|
||||||
if (!success) {
|
if (!success) {
|
||||||
uiInterface.InitMessage(_("Downloading blockchain snapshot..."));
|
printf("Bootstrap: no trusted compiled-hash snapshot available; syncing from peers.\n");
|
||||||
printf("Bootstrap: contacting %s...\n", host.c_str());
|
|
||||||
|
|
||||||
success = Bootstrap::DownloadBootstrap(host, dataPath, progressFn, strError);
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
printf("\nBootstrap: failed: %s\n", strError.c_str());
|
|
||||||
printf("Bootstrap: skipping, will sync from network.\n");
|
|
||||||
} else {
|
|
||||||
printf("\nBootstrap: done.\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StartupPerfLog("bootstrap_download", GetTimeMillis() - nBootstrapStart,
|
StartupPerfLog("bootstrap_download", GetTimeMillis() - nBootstrapStart,
|
||||||
@@ -1282,13 +1298,23 @@ bool AppInit2()
|
|||||||
printf("Found utxo-snapshot.bin — loading UTXO snapshot...\n");
|
printf("Found utxo-snapshot.bin — loading UTXO snapshot...\n");
|
||||||
uiInterface.InitMessage(_("Loading UTXO snapshot..."));
|
uiInterface.InitMessage(_("Loading UTXO snapshot..."));
|
||||||
|
|
||||||
// Local file load: skip the checkpoint gate. The operator has
|
|
||||||
// filesystem access, so the trust model is already equivalent
|
|
||||||
// to direct chain state modification — a malicious local file
|
|
||||||
// is no worse than a malicious chain DB. P2P-delivered
|
|
||||||
// snapshots (SnapshotNet) keep the checkpoint gate on.
|
|
||||||
std::string strError;
|
std::string strError;
|
||||||
if (UtxoSnapshot::LoadSnapshot(snapshotFile, dataPath, strError, /*requireCheckpoint=*/false)) {
|
const int snapshotHeight = Checkpoints::GetBestSnapshotHeight();
|
||||||
|
uint256 compiledHash;
|
||||||
|
uint256 actualHash;
|
||||||
|
const bool hasCompiledHash = snapshotHeight > 0 &&
|
||||||
|
Checkpoints::GetSnapshotHash(snapshotHeight, compiledHash);
|
||||||
|
const bool hashVerified = hasCompiledHash &&
|
||||||
|
SnapshotNet::ComputeSnapshotFileHash(snapshotFile, actualHash, strError) &&
|
||||||
|
actualHash == compiledHash;
|
||||||
|
|
||||||
|
if (!hashVerified) {
|
||||||
|
if (strError.empty())
|
||||||
|
strError = "snapshot SHA256 is not compiled into this release";
|
||||||
|
printf("UTXO snapshot rejected before import: %s\n", strError.c_str());
|
||||||
|
printf("Will proceed with normal sync.\n");
|
||||||
|
} else if (UtxoSnapshot::LoadSnapshot(snapshotFile, dataPath, strError,
|
||||||
|
/*requireCheckpoint=*/true)) {
|
||||||
printf("UTXO snapshot loaded successfully.\n");
|
printf("UTXO snapshot loaded successfully.\n");
|
||||||
} else {
|
} else {
|
||||||
printf("UTXO snapshot load failed: %s\n", strError.c_str());
|
printf("UTXO snapshot load failed: %s\n", strError.c_str());
|
||||||
@@ -1501,6 +1527,11 @@ bool AppInit2()
|
|||||||
{
|
{
|
||||||
fs::path walletPath = GetDataDir() / strWalletFileName;
|
fs::path walletPath = GetDataDir() / strWalletFileName;
|
||||||
if (fs::exists(walletPath)) {
|
if (fs::exists(walletPath)) {
|
||||||
|
#ifndef WIN32
|
||||||
|
std::string permissionError;
|
||||||
|
if (!EnsureOwnerOnlyFile(walletPath, permissionError))
|
||||||
|
return InitError(permissionError);
|
||||||
|
#endif
|
||||||
uintmax_t wsize = fs::file_size(walletPath);
|
uintmax_t wsize = fs::file_size(walletPath);
|
||||||
printf("Wallet file size: %llu bytes\n", (unsigned long long)wsize);
|
printf("Wallet file size: %llu bytes\n", (unsigned long long)wsize);
|
||||||
if (wsize < 1024) {
|
if (wsize < 1024) {
|
||||||
@@ -1933,10 +1964,10 @@ bool AppInit2()
|
|||||||
printf("mapAddressBook.size() = %" PRIszu "\n", pwalletMain->mapAddressBook.size());
|
printf("mapAddressBook.size() = %" PRIszu "\n", pwalletMain->mapAddressBook.size());
|
||||||
|
|
||||||
if (!NewThread(StartNode, nullptr))
|
if (!NewThread(StartNode, nullptr))
|
||||||
InitError(_("Error: could not start node"));
|
return InitError(_("Error: could not start node"));
|
||||||
|
|
||||||
if (fServer)
|
if (fServer && !NewThread(ThreadRPCServer, nullptr))
|
||||||
NewThread(ThreadRPCServer, nullptr);
|
return InitError(_("Error: could not start the RPC server"));
|
||||||
|
|
||||||
// ********************************************************* Step 11.6: P2P UTXO snapshot fetch
|
// ********************************************************* Step 11.6: P2P UTXO snapshot fetch
|
||||||
// If the chain is empty and snapshot mode is enabled (default), spawn a
|
// If the chain is empty and snapshot mode is enabled (default), spawn a
|
||||||
|
|||||||
+1
-1
@@ -12,6 +12,7 @@
|
|||||||
extern std::unique_ptr<CWallet> pwalletMain;
|
extern std::unique_ptr<CWallet> pwalletMain;
|
||||||
extern std::string strWalletFileName;
|
extern std::string strWalletFileName;
|
||||||
void StartShutdown();
|
void StartShutdown();
|
||||||
|
void MarkShutdownFailure();
|
||||||
bool ShutdownRequested();
|
bool ShutdownRequested();
|
||||||
void Shutdown(void* parg);
|
void Shutdown(void* parg);
|
||||||
bool AppInit2();
|
bool AppInit2();
|
||||||
@@ -19,4 +20,3 @@ std::string HelpMessage();
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+41
-20
@@ -190,28 +190,49 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
|
bool CCryptoKeyStore::PrepareKeyEncryption(CKeyingMaterial& vMasterKeyIn,
|
||||||
|
CryptedKeyMap& cryptedKeysOut) const
|
||||||
{
|
{
|
||||||
{
|
LOCK(cs_KeyStore);
|
||||||
LOCK(cs_KeyStore);
|
if (!mapCryptedKeys.empty() || IsCrypted())
|
||||||
if (!mapCryptedKeys.empty() || IsCrypted())
|
return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
fUseCrypto = true;
|
cryptedKeysOut.clear();
|
||||||
for (KeyMap::value_type& mKey : mapKeys)
|
for (const KeyMap::value_type& mKey : mapKeys)
|
||||||
{
|
{
|
||||||
CKey key;
|
CKey key;
|
||||||
if (!key.SetSecret(mKey.second.first, mKey.second.second))
|
if (!key.SetSecret(mKey.second.first, mKey.second.second))
|
||||||
return false;
|
return false;
|
||||||
const CPubKey vchPubKey = key.GetPubKey();
|
const CPubKey vchPubKey = key.GetPubKey();
|
||||||
std::vector<unsigned char> vchCryptedSecret;
|
std::vector<unsigned char> vchCryptedSecret;
|
||||||
bool fCompressed;
|
bool fCompressed;
|
||||||
if (!EncryptSecret(vMasterKeyIn, key.GetSecret(fCompressed), vchPubKey.GetHash(), vchCryptedSecret))
|
if (!EncryptSecret(vMasterKeyIn, key.GetSecret(fCompressed),
|
||||||
return false;
|
vchPubKey.GetHash(), vchCryptedSecret))
|
||||||
if (!AddCryptedKey(vchPubKey, vchCryptedSecret))
|
return false;
|
||||||
return false;
|
if (!cryptedKeysOut.emplace(vchPubKey.GetID(),
|
||||||
}
|
std::make_pair(vchPubKey,
|
||||||
mapKeys.clear();
|
std::move(vchCryptedSecret))).second)
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return cryptedKeysOut.size() == mapKeys.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CCryptoKeyStore::CommitKeyEncryption(CryptedKeyMap&& cryptedKeys)
|
||||||
|
{
|
||||||
|
LOCK(cs_KeyStore);
|
||||||
|
if (!mapCryptedKeys.empty() || IsCrypted() || cryptedKeys.size() != mapKeys.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
mapCryptedKeys = std::move(cryptedKeys);
|
||||||
|
mapKeys.clear();
|
||||||
|
fUseCrypto = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
|
||||||
|
{
|
||||||
|
CryptedKeyMap cryptedKeys;
|
||||||
|
if (!PrepareKeyEncryption(vMasterKeyIn, cryptedKeys))
|
||||||
|
return false;
|
||||||
|
return CommitKeyEncryption(std::move(cryptedKeys));
|
||||||
|
}
|
||||||
|
|||||||
+9
-1
@@ -9,6 +9,8 @@
|
|||||||
#include "util_signal.h"
|
#include "util_signal.h"
|
||||||
#include "sync.h"
|
#include "sync.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
class CScript;
|
class CScript;
|
||||||
|
|
||||||
/** A virtual base class for key stores */
|
/** A virtual base class for key stores */
|
||||||
@@ -112,7 +114,13 @@ protected:
|
|||||||
|
|
||||||
bool SetCrypted();
|
bool SetCrypted();
|
||||||
|
|
||||||
// will encrypt previously unencrypted keys
|
// Stage and commit wallet-key encryption separately so callers can make
|
||||||
|
// the on-disk update atomic before discarding plaintext keys in memory.
|
||||||
|
bool PrepareKeyEncryption(CKeyingMaterial& vMasterKeyIn,
|
||||||
|
CryptedKeyMap& cryptedKeysOut) const;
|
||||||
|
bool CommitKeyEncryption(CryptedKeyMap&& cryptedKeys);
|
||||||
|
|
||||||
|
// Encrypt previously unencrypted keys in memory.
|
||||||
bool EncryptKeys(CKeyingMaterial& vMasterKeyIn);
|
bool EncryptKeys(CKeyingMaterial& vMasterKeyIn);
|
||||||
|
|
||||||
bool Unlock(const CKeyingMaterial& vMasterKeyIn);
|
bool Unlock(const CKeyingMaterial& vMasterKeyIn);
|
||||||
|
|||||||
+22
-6
@@ -1747,6 +1747,11 @@ bool IsConsensusAssumeValidHeight(int nHeight)
|
|||||||
|| (nHeight <= nAssumeValidThreshold);
|
|| (nHeight <= nAssumeValidThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsBlockSignatureRequiredAtHeight(int nHeight)
|
||||||
|
{
|
||||||
|
return nHeight > Checkpoints::GetTotalBlocksEstimate();
|
||||||
|
}
|
||||||
|
|
||||||
void static InvalidChainFound(CBlockIndex* pindexNew)
|
void static InvalidChainFound(CBlockIndex* pindexNew)
|
||||||
{
|
{
|
||||||
if (pindexNew->nChainTrust > nBestInvalidTrust)
|
if (pindexNew->nChainTrust > nBestInvalidTrust)
|
||||||
@@ -3401,6 +3406,12 @@ bool CBlock::AcceptBlock()
|
|||||||
uint256 hashProofOfStake = 0, targetProofOfStake = 0;
|
uint256 hashProofOfStake = 0, targetProofOfStake = 0;
|
||||||
if (IsProofOfStake())
|
if (IsProofOfStake())
|
||||||
{
|
{
|
||||||
|
// The rolling validation optimization is not a signature trust root.
|
||||||
|
// Every PoS block above the compiled checkpoint must authorize its
|
||||||
|
// exact block contents, including while the local tip is stale.
|
||||||
|
if (IsBlockSignatureRequiredAtHeight(nHeight) && !CheckBlockSignature())
|
||||||
|
return DoS(100, error("AcceptBlock() : bad proof-of-stake block signature at height %d", nHeight));
|
||||||
|
|
||||||
if (IsConsensusAssumeValidHeight(nHeight))
|
if (IsConsensusAssumeValidHeight(nHeight))
|
||||||
{
|
{
|
||||||
// Historical fast path: blocks at/below hardcoded checkpoint or
|
// Historical fast path: blocks at/below hardcoded checkpoint or
|
||||||
@@ -3539,10 +3550,16 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock)
|
|||||||
if (pblock->IsProofOfStake() && !GetBoolArg("-ignoredupstake", false) && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash))
|
if (pblock->IsProofOfStake() && !GetBoolArg("-ignoredupstake", false) && setStakeSeen.count(pblock->GetProofOfStake()) && !mapOrphanBlocksByPrev.count(hash))
|
||||||
return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str());
|
return error("ProcessBlock() : duplicate proof-of-stake (%s, %d) for block %s", pblock->GetProofOfStake().first.ToString().c_str(), pblock->GetProofOfStake().second, hash.ToString().c_str());
|
||||||
|
|
||||||
// Preliminary checks
|
// Operational IBD state is never permission to skip a live proof-of-stake
|
||||||
// Skip block signature verification during initial block download (below checkpoint).
|
// block signature. Only a candidate height committed by the latest
|
||||||
// The hardcoded checkpoint guarantees historical chain integrity.
|
// hardened checkpoint uses the historical fast path.
|
||||||
if (!pblock->CheckBlock(true, true, !IsInitialBlockDownload()))
|
bool checkBlockSignature = true;
|
||||||
|
const auto prevIt = mapBlockIndex.find(pblock->hashPrevBlock);
|
||||||
|
if (prevIt != mapBlockIndex.end()) {
|
||||||
|
const int candidateHeight = prevIt->second->nHeight + 1;
|
||||||
|
checkBlockSignature = IsBlockSignatureRequiredAtHeight(candidateHeight);
|
||||||
|
}
|
||||||
|
if (!pblock->CheckBlock(true, true, checkBlockSignature))
|
||||||
{
|
{
|
||||||
printf("IBD-DIAG: CheckBlock FAILED for %s (PoS=%d, IBD=%d)\n",
|
printf("IBD-DIAG: CheckBlock FAILED for %s (PoS=%d, IBD=%d)\n",
|
||||||
hash.ToString().substr(0,20).c_str(), pblock->IsProofOfStake(), IsInitialBlockDownload());
|
hash.ToString().substr(0,20).c_str(), pblock->IsProofOfStake(), IsInitialBlockDownload());
|
||||||
@@ -3786,6 +3803,7 @@ bool CheckDiskSpace(uint64_t nAdditionalBytes)
|
|||||||
strMiscWarning = strMessage;
|
strMiscWarning = strMessage;
|
||||||
printf("*** %s\n", strMessage.c_str());
|
printf("*** %s\n", strMessage.c_str());
|
||||||
uiInterface.ThreadSafeMessageBox(strMessage, "Triangles", CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL);
|
uiInterface.ThreadSafeMessageBox(strMessage, "Triangles", CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL);
|
||||||
|
MarkShutdownFailure();
|
||||||
StartShutdown();
|
StartShutdown();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -6039,5 +6057,3 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ bool IsStakingSafe(const CWallet* pwallet, const std::vector<CNode*>& vNodesSnap
|
|||||||
// assume-valid validation. This intentionally excludes operational IBD states
|
// assume-valid validation. This intentionally excludes operational IBD states
|
||||||
// such as a stale tip; stale-tip IBD must not disable live PoS checks.
|
// such as a stale tip; stale-tip IBD must not disable live PoS checks.
|
||||||
[[nodiscard]] bool IsConsensusAssumeValidHeight(int nHeight);
|
[[nodiscard]] bool IsConsensusAssumeValidHeight(int nHeight);
|
||||||
|
[[nodiscard]] bool IsBlockSignatureRequiredAtHeight(int nHeight);
|
||||||
std::string GetWarnings(std::string strFor);
|
std::string GetWarnings(std::string strFor);
|
||||||
bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock);
|
bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock);
|
||||||
uint256 WantedByOrphan(const CBlock* pblockOrphan);
|
uint256 WantedByOrphan(const CBlock* pblockOrphan);
|
||||||
|
|||||||
+39
-5
@@ -605,7 +605,8 @@ CNode* ConnectNode(CAddress addrConnect, const char *pszDest)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fDebug) {
|
if (fDebug) {
|
||||||
printf("ConnectNode(): pszDest: %s\n", pszDest);
|
printf("ConnectNode(): destination: %s\n",
|
||||||
|
pszDest ? pszDest : addrConnect.ToString().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// debug print
|
/// debug print
|
||||||
@@ -1738,7 +1739,7 @@ void ThreadOnionSeed(void* parg)
|
|||||||
|
|
||||||
// Fetch dynamic seeds with retry — up to 4 attempts with increasing backoff.
|
// Fetch dynamic seeds with retry — up to 4 attempts with increasing backoff.
|
||||||
// This is the primary discovery mechanism — seeds.cryptographic-triangles.org
|
// This is the primary discovery mechanism — seeds.cryptographic-triangles.org
|
||||||
{
|
if (!GetBoolArg("-noseedurl", false)) {
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
int delays[] = {0, 30, 60, 120};
|
int delays[] = {0, 30, 60, 120};
|
||||||
for (int attempt = 0; attempt < 4 && !ok && !fShutdown; attempt++) {
|
for (int attempt = 0; attempt < 4 && !ok && !fShutdown; attempt++) {
|
||||||
@@ -1806,7 +1807,8 @@ void ThreadOnionSeed(void* parg)
|
|||||||
else
|
else
|
||||||
printf("ThreadOnionSeed: low outbound peers (%d), re-seeding...\n", nOutbound);
|
printf("ThreadOnionSeed: low outbound peers (%d), re-seeding...\n", nOutbound);
|
||||||
|
|
||||||
ThreadHTTPSeedFetch2(nullptr);
|
if (!GetBoolArg("-noseedurl", false))
|
||||||
|
ThreadHTTPSeedFetch2(nullptr);
|
||||||
|
|
||||||
// Re-queue hardcoded seeds for direct connection
|
// Re-queue hardcoded seeds for direct connection
|
||||||
for (unsigned int seed_idx = 0; strOnionSeed[seed_idx][0] != nullptr; seed_idx++) {
|
for (unsigned int seed_idx = 0; strOnionSeed[seed_idx][0] != nullptr; seed_idx++) {
|
||||||
@@ -1891,6 +1893,12 @@ bool ThreadHTTPSeedFetch2(void* parg)
|
|||||||
seedPath = seedHost.substr(slashPos);
|
seedPath = seedHost.substr(slashPos);
|
||||||
seedHost = seedHost.substr(0, slashPos);
|
seedHost = seedHost.substr(0, slashPos);
|
||||||
}
|
}
|
||||||
|
if (seedHost.empty() || seedHost.find_first_of("\r\n") != std::string::npos ||
|
||||||
|
seedPath.empty() || seedPath[0] != '/' ||
|
||||||
|
seedPath.find_first_of("\r\n") != std::string::npos) {
|
||||||
|
printf("HTTPS seed fetch: invalid -seedurl value\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
printf("Fetching seed list from https://%s%s (via Tor)...\n", seedHost.c_str(), seedPath.c_str());
|
printf("Fetching seed list from https://%s%s (via Tor)...\n", seedHost.c_str(), seedPath.c_str());
|
||||||
|
|
||||||
@@ -1929,7 +1937,14 @@ bool ThreadHTTPSeedFetch2(void* parg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set SNI hostname (required for Caddy/Let's Encrypt)
|
// Set SNI hostname (required for Caddy/Let's Encrypt)
|
||||||
SSL_set_tlsext_host_name(ssl, seedHost.c_str());
|
if (SSL_set_tlsext_host_name(ssl, seedHost.c_str()) != 1 ||
|
||||||
|
SSL_set1_host(ssl, seedHost.c_str()) != 1) {
|
||||||
|
printf("HTTPS seed fetch: failed to configure TLS hostname verification\n");
|
||||||
|
SSL_free(ssl);
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
closesocket(hSocket);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
SSL_set_fd(ssl, (int)hSocket);
|
SSL_set_fd(ssl, (int)hSocket);
|
||||||
|
|
||||||
int ret = SSL_connect(ssl);
|
int ret = SSL_connect(ssl);
|
||||||
@@ -1944,6 +1959,15 @@ bool ThreadHTTPSeedFetch2(void* parg)
|
|||||||
closesocket(hSocket);
|
closesocket(hSocket);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (SSL_get_verify_result(ssl) != X509_V_OK) {
|
||||||
|
printf("HTTPS seed fetch: certificate verification failed for %s\n",
|
||||||
|
seedHost.c_str());
|
||||||
|
SSL_shutdown(ssl);
|
||||||
|
SSL_free(ssl);
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
closesocket(hSocket);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
printf("HTTPS seed fetch: TLS connection established to %s\n", seedHost.c_str());
|
printf("HTTPS seed fetch: TLS connection established to %s\n", seedHost.c_str());
|
||||||
|
|
||||||
@@ -1973,10 +1997,19 @@ bool ThreadHTTPSeedFetch2(void* parg)
|
|||||||
// Read response over TLS
|
// Read response over TLS
|
||||||
std::string response;
|
std::string response;
|
||||||
char buf[4096];
|
char buf[4096];
|
||||||
|
static constexpr size_t MAX_SEED_RESPONSE_SIZE = 1024 * 1024;
|
||||||
while (true) {
|
while (true) {
|
||||||
int nBytes = SSL_read(ssl, buf, sizeof(buf));
|
int nBytes = SSL_read(ssl, buf, sizeof(buf));
|
||||||
if (nBytes <= 0)
|
if (nBytes <= 0)
|
||||||
break;
|
break;
|
||||||
|
if (response.size() + static_cast<size_t>(nBytes) > MAX_SEED_RESPONSE_SIZE) {
|
||||||
|
printf("HTTPS seed fetch: response exceeds 1 MiB limit\n");
|
||||||
|
SSL_shutdown(ssl);
|
||||||
|
SSL_free(ssl);
|
||||||
|
SSL_CTX_free(ctx);
|
||||||
|
closesocket(hSocket);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
response.append(buf, nBytes);
|
response.append(buf, nBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2002,7 +2035,8 @@ bool ThreadHTTPSeedFetch2(void* parg)
|
|||||||
|
|
||||||
// Check status code
|
// Check status code
|
||||||
std::string statusLine = response.substr(0, response.find("\r\n"));
|
std::string statusLine = response.substr(0, response.find("\r\n"));
|
||||||
if (statusLine.find("200") == std::string::npos) {
|
if (statusLine.size() < 12 || statusLine.compare(0, 7, "HTTP/1.") != 0 ||
|
||||||
|
statusLine.compare(9, 3, "200") != 0) {
|
||||||
printf("HTTPS seed fetch: %s from %s\n", statusLine.c_str(), seedHost.c_str());
|
printf("HTTPS seed fetch: %s from %s\n", statusLine.c_str(), seedHost.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-10
@@ -141,12 +141,27 @@ inline SOCKET ConnectRPCSocket(const std::string& host, int port)
|
|||||||
return hSocket;
|
return hSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create listening sockets for the RPC server. When loopbackOnly is true the
|
inline bool SetRPCSocketTimeouts(SOCKET socket, int timeoutSeconds)
|
||||||
// server binds the loopback interface(s) only; otherwise it binds the wildcard
|
{
|
||||||
// address(es). IPv4 and IPv6 are bound on separate sockets (IPV6_V6ONLY) so the
|
#ifdef WIN32
|
||||||
// two never conflict. Returns the bound, listening sockets; empty + strError on
|
DWORD timeout = static_cast<DWORD>(timeoutSeconds * 1000);
|
||||||
// total failure (partial success — e.g. only IPv4 — is returned as success).
|
#else
|
||||||
inline std::vector<SOCKET> BindRPCSockets(int port, bool loopbackOnly, std::string& strError)
|
struct timeval timeout;
|
||||||
|
timeout.tv_sec = timeoutSeconds;
|
||||||
|
timeout.tv_usec = 0;
|
||||||
|
#endif
|
||||||
|
const char* value = reinterpret_cast<const char*>(&timeout);
|
||||||
|
const socklen_t valueSize = sizeof(timeout);
|
||||||
|
return ::setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, value, valueSize) == 0 &&
|
||||||
|
::setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, value, valueSize) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create listening sockets for the RPC server. An empty bindAddress binds only
|
||||||
|
// localhost. A non-empty value binds exactly that address; "*" explicitly
|
||||||
|
// requests wildcard addresses. IPv4 and IPv6 use separate sockets when the
|
||||||
|
// selected name resolves to both families.
|
||||||
|
inline std::vector<SOCKET> BindRPCSockets(int port, const std::string& bindAddress,
|
||||||
|
std::string& strError)
|
||||||
{
|
{
|
||||||
std::vector<SOCKET> vListen;
|
std::vector<SOCKET> vListen;
|
||||||
|
|
||||||
@@ -154,13 +169,15 @@ inline std::vector<SOCKET> BindRPCSockets(int port, bool loopbackOnly, std::stri
|
|||||||
std::memset(&hints, 0, sizeof(hints));
|
std::memset(&hints, 0, sizeof(hints));
|
||||||
hints.ai_family = AF_UNSPEC;
|
hints.ai_family = AF_UNSPEC;
|
||||||
hints.ai_socktype = SOCK_STREAM;
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
hints.ai_flags = AI_PASSIVE; // wildcard when node == nullptr
|
|
||||||
|
const bool wildcard = bindAddress == "*";
|
||||||
|
if (wildcard)
|
||||||
|
hints.ai_flags = AI_PASSIVE;
|
||||||
|
|
||||||
struct addrinfo* res = nullptr;
|
struct addrinfo* res = nullptr;
|
||||||
const std::string portStr = std::to_string(port);
|
const std::string portStr = std::to_string(port);
|
||||||
// "localhost" resolves to the loopback addresses (127.0.0.1 and ::1);
|
const char* node = wildcard ? nullptr :
|
||||||
// nullptr + AI_PASSIVE yields the wildcard addresses.
|
(bindAddress.empty() ? "localhost" : bindAddress.c_str());
|
||||||
const char* node = loopbackOnly ? "localhost" : nullptr;
|
|
||||||
int gai = ::getaddrinfo(node, portStr.c_str(), &hints, &res);
|
int gai = ::getaddrinfo(node, portStr.c_str(), &hints, &res);
|
||||||
if (gai != 0) {
|
if (gai != 0) {
|
||||||
strError = std::string("RPC bind: getaddrinfo failed: ") + gai_strerror(gai);
|
strError = std::string("RPC bind: getaddrinfo failed: ") + gai_strerror(gai);
|
||||||
|
|||||||
@@ -1323,91 +1323,3 @@ Value dumputxoset(const Array& params, bool fHelp)
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Trusted snapshot publisher RPCs (Design A: single-slot rotation)
|
|
||||||
// ============================================================================
|
|
||||||
//
|
|
||||||
// settrustedv2snapshotpublisher <address>
|
|
||||||
// - Atomically replaces the active trusted snapshot publisher.
|
|
||||||
// - The previous publisher is dropped immediately (no grace period).
|
|
||||||
// - The new publisher is persisted to <datadir>/snapshot-publisher.json
|
|
||||||
// so the choice survives daemon restarts.
|
|
||||||
//
|
|
||||||
// gettrustedv2snapshotpublisher
|
|
||||||
// - Returns the currently active runtime override.
|
|
||||||
// - Empty string means no runtime override; built-in fallback list is
|
|
||||||
// the source of truth (which contains "TG8f76ykt...").
|
|
||||||
//
|
|
||||||
// unsettrustedv2snapshotpublisher
|
|
||||||
// - Clears the runtime override.
|
|
||||||
// - The built-in fallback list (read-only, compiled in) becomes the
|
|
||||||
// source of truth again.
|
|
||||||
// - Removes <datadir>/snapshot-publisher.json.
|
|
||||||
Value settrustedv2snapshotpublisher(const Array& params, bool fHelp)
|
|
||||||
{
|
|
||||||
if (fHelp || params.size() != 1)
|
|
||||||
throw runtime_error(
|
|
||||||
"settrustedv2snapshotpublisher <address>\n"
|
|
||||||
"Atomically replace the trusted snapshot publisher.\n"
|
|
||||||
"The previous publisher is dropped immediately (no grace period).\n"
|
|
||||||
"The new publisher is persisted to <datadir>/snapshot-publisher.json.\n"
|
|
||||||
"\nArguments:\n"
|
|
||||||
"1. address (string, required) Triangles T-address (34 chars, starts with 'T')\n"
|
|
||||||
"\nResult:\n"
|
|
||||||
"{ previous: 'T...', current: 'T...' } (previous is empty if first set)\n"
|
|
||||||
"\nExample:\n"
|
|
||||||
" triangles-cli settrustedv2snapshotpublisher TGotWuftzH7rD9tXC7whE8EXiyC3mr1CrH");
|
|
||||||
|
|
||||||
std::string addr = params[0].get_str();
|
|
||||||
std::string previous = Bootstrap::GetActiveTrustedSnapshotPublisher();
|
|
||||||
std::string err;
|
|
||||||
if (!Bootstrap::SetTrustedSnapshotPublisher(addr, err)) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, err);
|
|
||||||
}
|
|
||||||
Object result;
|
|
||||||
result.push_back(Pair("previous", previous));
|
|
||||||
result.push_back(Pair("current", addr));
|
|
||||||
if (!err.empty())
|
|
||||||
result.push_back(Pair("warning", err));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value gettrustedv2snapshotpublisher(const Array& params, bool fHelp)
|
|
||||||
{
|
|
||||||
if (fHelp || params.size() != 0)
|
|
||||||
throw runtime_error(
|
|
||||||
"gettrustedv2snapshotpublisher\n"
|
|
||||||
"Returns the currently active trusted snapshot publisher.\n"
|
|
||||||
"Empty string means no runtime override is set; the built-in\n"
|
|
||||||
"fallback list (compiled in) is the source of truth.\n"
|
|
||||||
"\nResult:\n"
|
|
||||||
"{ active: 'T...', has_runtime_override: true|false }");
|
|
||||||
|
|
||||||
std::string active = Bootstrap::GetActiveTrustedSnapshotPublisher();
|
|
||||||
Object result;
|
|
||||||
result.push_back(Pair("active", active));
|
|
||||||
result.push_back(Pair("has_runtime_override", !active.empty()));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value unsettrustedv2snapshotpublisher(const Array& params, bool fHelp)
|
|
||||||
{
|
|
||||||
if (fHelp || params.size() != 0)
|
|
||||||
throw runtime_error(
|
|
||||||
"unsettrustedv2snapshotpublisher\n"
|
|
||||||
"Clear the runtime trusted snapshot publisher override.\n"
|
|
||||||
"The built-in fallback list (compiled in) becomes the source of truth again.\n"
|
|
||||||
"Removes <datadir>/snapshot-publisher.json.\n"
|
|
||||||
"\nResult:\n"
|
|
||||||
"{ unset: true, fallback_in_effect: true }");
|
|
||||||
|
|
||||||
std::string err;
|
|
||||||
if (!Bootstrap::UnsetTrustedSnapshotPublisher(err)) {
|
|
||||||
throw JSONRPCError(RPC_INTERNAL_ERROR, err);
|
|
||||||
}
|
|
||||||
Object result;
|
|
||||||
result.push_back(Pair("unset", true));
|
|
||||||
result.push_back(Pair("fallback_in_effect", true));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|||||||
+21
-8
@@ -13,6 +13,8 @@
|
|||||||
#include "tor/onion_v3.h"
|
#include "tor/onion_v3.h"
|
||||||
#include "tor/tor_embedded.h"
|
#include "tor/tor_embedded.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
using namespace json_spirit;
|
using namespace json_spirit;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@@ -1519,7 +1521,7 @@ Value walletpassphrase(const Array& params, bool fHelp)
|
|||||||
if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 3))
|
if (pwalletMain->IsCrypted() && (fHelp || params.size() < 2 || params.size() > 3))
|
||||||
throw runtime_error(
|
throw runtime_error(
|
||||||
"walletpassphrase <passphrase> <timeout> [stakingonly]\n"
|
"walletpassphrase <passphrase> <timeout> [stakingonly]\n"
|
||||||
"Stores the wallet decryption key in memory for <timeout> seconds.\n"
|
"Stores the wallet decryption key in memory for <timeout> seconds (1-604800).\n"
|
||||||
"if [stakingonly] is true sending functions are disabled.");
|
"if [stakingonly] is true sending functions are disabled.");
|
||||||
if (fHelp)
|
if (fHelp)
|
||||||
return true;
|
return true;
|
||||||
@@ -1530,6 +1532,14 @@ Value walletpassphrase(const Array& params, bool fHelp)
|
|||||||
|
|
||||||
if (!pwalletMain->IsLocked())
|
if (!pwalletMain->IsLocked())
|
||||||
throw JSONRPCError(RPC_WALLET_ALREADY_UNLOCKED, "Error: Wallet is already unlocked, use walletlock first if need to change unlock settings.");
|
throw JSONRPCError(RPC_WALLET_ALREADY_UNLOCKED, "Error: Wallet is already unlocked, use walletlock first if need to change unlock settings.");
|
||||||
|
|
||||||
|
const int64_t timeoutSeconds = params[1].get_int64();
|
||||||
|
if (timeoutSeconds < 1 || timeoutSeconds > 7 * 24 * 60 * 60)
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER,
|
||||||
|
"Wallet unlock timeout must be between 1 and 604800 seconds.");
|
||||||
|
const bool stakingOnly = params.size() > 2 ? params[2].get_bool() : false;
|
||||||
|
std::unique_ptr<int64_t> sleepTime(new int64_t(timeoutSeconds));
|
||||||
|
|
||||||
// Note that the walletpassphrase is stored in params[0] which is not mlock()ed
|
// Note that the walletpassphrase is stored in params[0] which is not mlock()ed
|
||||||
SecureString strWalletPass;
|
SecureString strWalletPass;
|
||||||
strWalletPass.reserve(100);
|
strWalletPass.reserve(100);
|
||||||
@@ -1545,15 +1555,18 @@ Value walletpassphrase(const Array& params, bool fHelp)
|
|||||||
"walletpassphrase <passphrase> <timeout>\n"
|
"walletpassphrase <passphrase> <timeout>\n"
|
||||||
"Stores the wallet decryption key in memory for <timeout> seconds.");
|
"Stores the wallet decryption key in memory for <timeout> seconds.");
|
||||||
|
|
||||||
NewThread(ThreadTopUpKeyPool, nullptr);
|
|
||||||
int64_t* pnSleepTime = new int64_t(params[1].get_int64());
|
|
||||||
NewThread(ThreadCleanWalletPassphrase, pnSleepTime);
|
|
||||||
|
|
||||||
// triangles: if user OS account compromised prevent trivial sendmoney commands
|
// triangles: if user OS account compromised prevent trivial sendmoney commands
|
||||||
if (params.size() > 2)
|
fWalletUnlockStakingOnly = stakingOnly;
|
||||||
fWalletUnlockStakingOnly = params[2].get_bool();
|
if (!NewThread(ThreadCleanWalletPassphrase, sleepTime.get())) {
|
||||||
else
|
pwalletMain->Lock();
|
||||||
fWalletUnlockStakingOnly = false;
|
fWalletUnlockStakingOnly = false;
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR,
|
||||||
|
"Could not start the wallet relock timer; wallet was locked again.");
|
||||||
|
}
|
||||||
|
sleepTime.release();
|
||||||
|
|
||||||
|
if (!NewThread(ThreadTopUpKeyPool, nullptr))
|
||||||
|
printf("walletpassphrase: could not start background keypool refill\n");
|
||||||
|
|
||||||
return Value::null;
|
return Value::null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -553,3 +553,8 @@ scrypt_core_loop2:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__ELF__)
|
||||||
|
.section .note.GNU-stack,"",%progbits
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -910,3 +910,7 @@ xmm_scrypt_core_loop2:
|
|||||||
ret
|
ret
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(__ELF__)
|
||||||
|
.section .note.GNU-stack,"",@progbits
|
||||||
|
#endif
|
||||||
|
|||||||
+5
-1
@@ -856,4 +856,8 @@ xmm_scrypt_core_loop2:
|
|||||||
popq %rbx
|
popq %rbx
|
||||||
ret
|
ret
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(__ELF__)
|
||||||
|
.section .note.GNU-stack,"",@progbits
|
||||||
|
#endif
|
||||||
|
|||||||
+18
-8
@@ -3555,6 +3555,8 @@ int SecureMsgValidate(unsigned char *pHeader, unsigned char *pPayload, uint32_t
|
|||||||
memcpy(civ+i, &nonse, 4);
|
memcpy(civ+i, &nonse, 4);
|
||||||
|
|
||||||
HMAC_CTX *ctx = HMAC_CTX_new();
|
HMAC_CTX *ctx = HMAC_CTX_new();
|
||||||
|
if (ctx == nullptr)
|
||||||
|
return 1;
|
||||||
|
|
||||||
unsigned int nBytes;
|
unsigned int nBytes;
|
||||||
if (!HMAC_Init_ex(ctx, &civ[0], 32, EVP_sha256(), nullptr)
|
if (!HMAC_Init_ex(ctx, &civ[0], 32, EVP_sha256(), nullptr)
|
||||||
@@ -3571,7 +3573,7 @@ int SecureMsgValidate(unsigned char *pHeader, unsigned char *pPayload, uint32_t
|
|||||||
{
|
{
|
||||||
if (sha256Hash[31] == 0
|
if (sha256Hash[31] == 0
|
||||||
&& sha256Hash[30] == 0
|
&& sha256Hash[30] == 0
|
||||||
&& (~(sha256Hash[29]) & ((1<<0) || (1<<1) || (1<<2)) ))
|
&& (sha256Hash[29] & 1U) == 0)
|
||||||
{
|
{
|
||||||
if (fDebugSmsg)
|
if (fDebugSmsg)
|
||||||
printf("Hash Valid.\n");
|
printf("Hash Valid.\n");
|
||||||
@@ -3614,6 +3616,8 @@ int SecureMsgSetHash(unsigned char *pHeader, unsigned char *pPayload, uint32_t n
|
|||||||
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
HMAC_CTX *ctx = HMAC_CTX_new();
|
HMAC_CTX *ctx = HMAC_CTX_new();
|
||||||
|
if (ctx == nullptr)
|
||||||
|
return 1;
|
||||||
|
|
||||||
uint32_t nonse = 0;
|
uint32_t nonse = 0;
|
||||||
|
|
||||||
@@ -3655,7 +3659,7 @@ int SecureMsgSetHash(unsigned char *pHeader, unsigned char *pPayload, uint32_t n
|
|||||||
|
|
||||||
if (sha256Hash[31] == 0
|
if (sha256Hash[31] == 0
|
||||||
&& sha256Hash[30] == 0
|
&& sha256Hash[30] == 0
|
||||||
&& (~(sha256Hash[29]) & ((1<<0) || (1<<1) || (1<<2)) ))
|
&& (sha256Hash[29] & 1U) == 0)
|
||||||
// && sha256Hash[29] == 0)
|
// && sha256Hash[29] == 0)
|
||||||
{
|
{
|
||||||
found = true;
|
found = true;
|
||||||
@@ -3794,7 +3798,10 @@ int SecureMsgEncrypt(SecureMessage& smsg, std::string& addressFrom, std::string&
|
|||||||
|
|
||||||
// -- Generate 16 random bytes as IV.
|
// -- Generate 16 random bytes as IV.
|
||||||
RandAddSeedPerfmon();
|
RandAddSeedPerfmon();
|
||||||
RAND_bytes(&smsg.iv[0], 16);
|
if (RAND_bytes(&smsg.iv[0], 16) != 1) {
|
||||||
|
printf("Could not generate a secure message IV.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// -- Generate a new random EC key pair with private key called r and public key called R.
|
// -- Generate a new random EC key pair with private key called r and public key called R.
|
||||||
@@ -3959,14 +3966,16 @@ int SecureMsgEncrypt(SecureMessage& smsg, std::string& addressFrom, std::string&
|
|||||||
unsigned int nBytes = 32;
|
unsigned int nBytes = 32;
|
||||||
HMAC_CTX *ctx = HMAC_CTX_new();
|
HMAC_CTX *ctx = HMAC_CTX_new();
|
||||||
|
|
||||||
if (!HMAC_Init_ex(ctx, &key_m[0], 32, EVP_sha256(), nullptr)
|
if (ctx == nullptr
|
||||||
|
|| !HMAC_Init_ex(ctx, &key_m[0], 32, EVP_sha256(), nullptr)
|
||||||
|| !HMAC_Update(ctx, (unsigned char*) &smsg.timestamp, sizeof(smsg.timestamp))
|
|| !HMAC_Update(ctx, (unsigned char*) &smsg.timestamp, sizeof(smsg.timestamp))
|
||||||
|| !HMAC_Update(ctx, &vchCiphertext[0], vchCiphertext.size())
|
|| !HMAC_Update(ctx, &vchCiphertext[0], vchCiphertext.size())
|
||||||
|| !HMAC_Final(ctx, smsg.mac, &nBytes)
|
|| !HMAC_Final(ctx, smsg.mac, &nBytes)
|
||||||
|| nBytes != 32)
|
|| nBytes != 32)
|
||||||
fHmacOk = false;
|
fHmacOk = false;
|
||||||
|
|
||||||
HMAC_CTX_free(ctx);
|
if (ctx != nullptr)
|
||||||
|
HMAC_CTX_free(ctx);
|
||||||
|
|
||||||
if (!fHmacOk)
|
if (!fHmacOk)
|
||||||
{
|
{
|
||||||
@@ -4269,14 +4278,16 @@ int SecureMsgDecrypt(bool fTestOnly, std::string& address, unsigned char *pHeade
|
|||||||
unsigned int nBytes = 32;
|
unsigned int nBytes = 32;
|
||||||
HMAC_CTX *ctx = HMAC_CTX_new();
|
HMAC_CTX *ctx = HMAC_CTX_new();
|
||||||
|
|
||||||
if (!HMAC_Init_ex(ctx, &key_m[0], 32, EVP_sha256(), nullptr)
|
if (ctx == nullptr
|
||||||
|
|| !HMAC_Init_ex(ctx, &key_m[0], 32, EVP_sha256(), nullptr)
|
||||||
|| !HMAC_Update(ctx, (unsigned char*) &psmsg->timestamp, sizeof(psmsg->timestamp))
|
|| !HMAC_Update(ctx, (unsigned char*) &psmsg->timestamp, sizeof(psmsg->timestamp))
|
||||||
|| !HMAC_Update(ctx, pPayload, nPayload)
|
|| !HMAC_Update(ctx, pPayload, nPayload)
|
||||||
|| !HMAC_Final(ctx, MAC, &nBytes)
|
|| !HMAC_Final(ctx, MAC, &nBytes)
|
||||||
|| nBytes != 32)
|
|| nBytes != 32)
|
||||||
fHmacOk = false;
|
fHmacOk = false;
|
||||||
|
|
||||||
HMAC_CTX_free(ctx);
|
if (ctx != nullptr)
|
||||||
|
HMAC_CTX_free(ctx);
|
||||||
|
|
||||||
if (!fHmacOk)
|
if (!fHmacOk)
|
||||||
{
|
{
|
||||||
@@ -4430,4 +4441,3 @@ int SecureMsgDecrypt(bool fTestOnly, std::string& address, SecureMessage& smsg,
|
|||||||
{
|
{
|
||||||
return SecureMsgDecrypt(fTestOnly, address, &smsg.hash[0], smsg.pPayload, smsg.nPayload, msg);
|
return SecureMsgDecrypt(fTestOnly, address, &smsg.hash[0], smsg.pPayload, smsg.nPayload, msg);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+52
-32
@@ -120,26 +120,18 @@ static bool VerifyDestFileHash(std::string& strErr)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
fflush(g_fetch.fpDest);
|
fflush(g_fetch.fpDest);
|
||||||
fseek(g_fetch.fpDest, 0, SEEK_SET);
|
|
||||||
|
|
||||||
SHA256_CTX ctx;
|
std::error_code ec;
|
||||||
SHA256_Init(&ctx);
|
const int64_t total = static_cast<int64_t>(fs::file_size(g_fetch.destPath, ec));
|
||||||
|
if (ec || total != g_fetch.totalSize) {
|
||||||
std::vector<unsigned char> buf(64 * 1024);
|
strErr = strprintf("size mismatch: have %" PRId64 " want %" PRId64,
|
||||||
int64_t total = 0;
|
ec ? -1 : total, g_fetch.totalSize);
|
||||||
while (true) {
|
|
||||||
size_t n = fread(buf.data(), 1, buf.size(), g_fetch.fpDest);
|
|
||||||
if (n == 0) break;
|
|
||||||
SHA256_Update(&ctx, buf.data(), n);
|
|
||||||
total += (int64_t)n;
|
|
||||||
}
|
|
||||||
if (total != g_fetch.totalSize) {
|
|
||||||
strErr = strprintf("size mismatch: have %" PRId64 " want %" PRId64, total, g_fetch.totalSize);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint256 actual;
|
uint256 actual;
|
||||||
SHA256_Final((unsigned char*)&actual, &ctx);
|
if (!ComputeSnapshotFileHash(g_fetch.destPath, actual, strErr))
|
||||||
|
return false;
|
||||||
if (actual != g_fetch.expectedFileHash) {
|
if (actual != g_fetch.expectedFileHash) {
|
||||||
strErr = "snapshot file hash mismatch";
|
strErr = "snapshot file hash mismatch";
|
||||||
return false;
|
return false;
|
||||||
@@ -243,6 +235,46 @@ static void ReissueStalledChunks(int64_t timeoutMicros)
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
bool ComputeSnapshotFileHash(const fs::path& path,
|
||||||
|
uint256& fileHash,
|
||||||
|
std::string& strError)
|
||||||
|
{
|
||||||
|
FILE* file = fopen(path.string().c_str(), "rb");
|
||||||
|
if (!file) {
|
||||||
|
strError = "cannot open snapshot for hashing: " + path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SHA256_CTX ctx;
|
||||||
|
SHA256_Init(&ctx);
|
||||||
|
std::vector<unsigned char> buffer(64 * 1024);
|
||||||
|
while (true) {
|
||||||
|
const size_t count = fread(buffer.data(), 1, buffer.size(), file);
|
||||||
|
if (count > 0)
|
||||||
|
SHA256_Update(&ctx, buffer.data(), count);
|
||||||
|
if (count < buffer.size()) {
|
||||||
|
if (ferror(file)) {
|
||||||
|
fclose(file);
|
||||||
|
strError = "failed reading snapshot while hashing";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
unsigned char digest[SHA256_DIGEST_LENGTH];
|
||||||
|
SHA256_Final(digest, &ctx);
|
||||||
|
static const char hex[] = "0123456789abcdef";
|
||||||
|
std::string digestHex(SHA256_DIGEST_LENGTH * 2, '0');
|
||||||
|
for (size_t i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
|
||||||
|
digestHex[2 * i] = hex[(digest[i] >> 4) & 0x0f];
|
||||||
|
digestHex[2 * i + 1] = hex[digest[i] & 0x0f];
|
||||||
|
}
|
||||||
|
fileHash.SetHex(digestHex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Public: TryFetchSnapshot
|
// Public: TryFetchSnapshot
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -424,24 +456,12 @@ static bool ScanLocalSnapshot()
|
|||||||
int64_t sz = (int64_t)fs::file_size(g_localPath, ec);
|
int64_t sz = (int64_t)fs::file_size(g_localPath, ec);
|
||||||
if (ec) return false;
|
if (ec) return false;
|
||||||
|
|
||||||
// Hash the file once on first scan to confirm it matches the compiled-in
|
|
||||||
// snapshot hash. A node won't advertise NODE_SNAPSHOT if the local file is
|
|
||||||
// corrupt or for a different height.
|
|
||||||
FILE* f = fopen(g_localPath.string().c_str(), "rb");
|
|
||||||
if (!f) return false;
|
|
||||||
|
|
||||||
SHA256_CTX ctx;
|
|
||||||
SHA256_Init(&ctx);
|
|
||||||
std::vector<unsigned char> buf(64 * 1024);
|
|
||||||
while (true) {
|
|
||||||
size_t n = fread(buf.data(), 1, buf.size(), f);
|
|
||||||
if (n == 0) break;
|
|
||||||
SHA256_Update(&ctx, buf.data(), n);
|
|
||||||
}
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
uint256 actual;
|
uint256 actual;
|
||||||
SHA256_Final((unsigned char*)&actual, &ctx);
|
std::string hashError;
|
||||||
|
if (!ComputeSnapshotFileHash(g_localPath, actual, hashError)) {
|
||||||
|
printf("SnapshotNet: cannot hash local snapshot: %s\n", hashError.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (actual != expectedHash) {
|
if (actual != expectedHash) {
|
||||||
printf("SnapshotNet: local utxo-snapshot.bin hash mismatch — not advertising\n");
|
printf("SnapshotNet: local utxo-snapshot.bin hash mismatch — not advertising\n");
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ bool TryFetchSnapshot(const std::filesystem::path& dataDir,
|
|||||||
int timeoutSec,
|
int timeoutSec,
|
||||||
std::string& strError);
|
std::string& strError);
|
||||||
|
|
||||||
|
// Return SHA256 in conventional display byte order, matching sha256sum and
|
||||||
|
// the hexadecimal values compiled into checkpoints.cpp.
|
||||||
|
bool ComputeSnapshotFileHash(const std::filesystem::path& path,
|
||||||
|
uint256& fileHash,
|
||||||
|
std::string& strError);
|
||||||
|
|
||||||
// Server-side message dispatch. Called from main.cpp ProcessMessage.
|
// Server-side message dispatch. Called from main.cpp ProcessMessage.
|
||||||
// Returns true if strCommand was a snapshot-protocol message (handled or
|
// Returns true if strCommand was a snapshot-protocol message (handled or
|
||||||
// rejected for malformed input).
|
// rejected for malformed input).
|
||||||
|
|||||||
+1
-3
@@ -65,8 +65,7 @@ public:
|
|||||||
if (!lock.owns_lock())
|
if (!lock.owns_lock())
|
||||||
{
|
{
|
||||||
EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex()), true);
|
EnterCritical(pszName, pszFile, nLine, (void*)(lock.mutex()), true);
|
||||||
lock.try_lock();
|
if (!lock.try_lock())
|
||||||
if (!lock.owns_lock())
|
|
||||||
LeaveCritical();
|
LeaveCritical();
|
||||||
}
|
}
|
||||||
return lock.owns_lock();
|
return lock.owns_lock();
|
||||||
@@ -204,4 +203,3 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
+45
-96
@@ -1,125 +1,74 @@
|
|||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "bignum.h"
|
#include "bignum.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(bignum_tests)
|
BOOST_AUTO_TEST_SUITE(bignum_tests)
|
||||||
|
|
||||||
// Unfortunately there's no standard way of preventing a function from being
|
|
||||||
// inlined, so we define a macro for it.
|
|
||||||
//
|
|
||||||
// You should use it like this:
|
|
||||||
// NOINLINE void function() {...}
|
|
||||||
#if defined(__GNUC__)
|
#if defined(__GNUC__)
|
||||||
// This also works and will be defined for any compiler implementing GCC
|
|
||||||
// extensions, such as Clang and ICC.
|
|
||||||
#define NOINLINE __attribute__((noinline))
|
#define NOINLINE __attribute__((noinline))
|
||||||
#elif defined(_MSC_VER)
|
#elif defined(_MSC_VER)
|
||||||
#define NOINLINE __declspec(noinline)
|
#define NOINLINE __declspec(noinline)
|
||||||
#else
|
#else
|
||||||
// We give out a warning because it impacts the correctness of one bignum test.
|
|
||||||
#warning You should define NOINLINE for your compiler.
|
|
||||||
#define NOINLINE
|
#define NOINLINE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// For the following test case, it is useful to use additional tools.
|
|
||||||
//
|
|
||||||
// The simplest one to use is the compiler flag -ftrapv, which detects integer
|
|
||||||
// overflows and similar errors. However, due to optimizations and compilers
|
|
||||||
// taking advantage of undefined behavior sometimes it may not actually detect
|
|
||||||
// anything.
|
|
||||||
//
|
|
||||||
// You can also use compiler-based stack protection to possibly detect possible
|
|
||||||
// stack buffer overruns.
|
|
||||||
//
|
|
||||||
// For more accurate diagnostics, you can use an undefined arithmetic operation
|
|
||||||
// detector such as the clang-based tool:
|
|
||||||
//
|
|
||||||
// "IOC: An Integer Overflow Checker for C/C++"
|
|
||||||
//
|
|
||||||
// Available at: http://embed.cs.utah.edu/ioc/
|
|
||||||
//
|
|
||||||
// It might also be useful to use Google's AddressSanitizer to detect
|
|
||||||
// stack buffer overruns, which valgrind can't currently detect.
|
|
||||||
|
|
||||||
// Let's force this code not to be inlined, in order to actually
|
|
||||||
// test a generic version of the function. This increases the chance
|
|
||||||
// that -ftrapv will detect overflows.
|
|
||||||
NOINLINE void mysetint64(CBigNum& num, int64_t n)
|
NOINLINE void mysetint64(CBigNum& num, int64_t n)
|
||||||
{
|
{
|
||||||
num.setint64(n);
|
num.setint64(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For each number, we do 2 tests: one with inline code, then we reset the
|
|
||||||
// value to 0, then the second one with a non-inlined function.
|
|
||||||
BOOST_AUTO_TEST_CASE(bignum_setint64)
|
BOOST_AUTO_TEST_CASE(bignum_setint64)
|
||||||
{
|
{
|
||||||
int64_t n;
|
const int64_t values[] = {
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
-1,
|
||||||
|
5,
|
||||||
|
-5,
|
||||||
|
std::numeric_limits<int64_t>::min(),
|
||||||
|
std::numeric_limits<int64_t>::max(),
|
||||||
|
};
|
||||||
|
|
||||||
{
|
for (int64_t value : values) {
|
||||||
n = 0;
|
CBigNum num(value);
|
||||||
CBigNum num(n);
|
BOOST_CHECK_EQUAL(num.ToString(), std::to_string(value));
|
||||||
BOOST_CHECK(num.ToString() == "0");
|
|
||||||
num.setulong(0);
|
num.setulong(0);
|
||||||
BOOST_CHECK(num.ToString() == "0");
|
BOOST_CHECK_EQUAL(num.ToString(), "0");
|
||||||
mysetint64(num, n);
|
mysetint64(num, value);
|
||||||
BOOST_CHECK(num.ToString() == "0");
|
BOOST_CHECK_EQUAL(num.ToString(), std::to_string(value));
|
||||||
}
|
|
||||||
{
|
|
||||||
n = 1;
|
|
||||||
CBigNum num(n);
|
|
||||||
BOOST_CHECK(num.ToString() == "1");
|
|
||||||
num.setulong(0);
|
|
||||||
BOOST_CHECK(num.ToString() == "0");
|
|
||||||
mysetint64(num, n);
|
|
||||||
BOOST_CHECK(num.ToString() == "1");
|
|
||||||
}
|
|
||||||
{
|
|
||||||
n = -1;
|
|
||||||
CBigNum num(n);
|
|
||||||
BOOST_CHECK(num.ToString() == "-1");
|
|
||||||
num.setulong(0);
|
|
||||||
BOOST_CHECK(num.ToString() == "0");
|
|
||||||
mysetint64(num, n);
|
|
||||||
BOOST_CHECK(num.ToString() == "-1");
|
|
||||||
}
|
|
||||||
{
|
|
||||||
n = 5;
|
|
||||||
CBigNum num(n);
|
|
||||||
BOOST_CHECK(num.ToString() == "5");
|
|
||||||
num.setulong(0);
|
|
||||||
BOOST_CHECK(num.ToString() == "0");
|
|
||||||
mysetint64(num, n);
|
|
||||||
BOOST_CHECK(num.ToString() == "5");
|
|
||||||
}
|
|
||||||
{
|
|
||||||
n = -5;
|
|
||||||
CBigNum num(n);
|
|
||||||
BOOST_CHECK(num.ToString() == "-5");
|
|
||||||
num.setulong(0);
|
|
||||||
BOOST_CHECK(num.ToString() == "0");
|
|
||||||
mysetint64(num, n);
|
|
||||||
BOOST_CHECK(num.ToString() == "-5");
|
|
||||||
}
|
|
||||||
{
|
|
||||||
n = std::numeric_limits<int64_t>::min();
|
|
||||||
CBigNum num(n);
|
|
||||||
BOOST_CHECK(num.ToString() == "-9223372036854775808");
|
|
||||||
num.setulong(0);
|
|
||||||
BOOST_CHECK(num.ToString() == "0");
|
|
||||||
mysetint64(num, n);
|
|
||||||
BOOST_CHECK(num.ToString() == "-9223372036854775808");
|
|
||||||
}
|
|
||||||
{
|
|
||||||
n = std::numeric_limits<int64_t>::max();
|
|
||||||
CBigNum num(n);
|
|
||||||
BOOST_CHECK(num.ToString() == "9223372036854775807");
|
|
||||||
num.setulong(0);
|
|
||||||
BOOST_CHECK(num.ToString() == "0");
|
|
||||||
mysetint64(num, n);
|
|
||||||
BOOST_CHECK(num.ToString() == "9223372036854775807");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(bignum_uint64_roundtrip_boundaries)
|
||||||
|
{
|
||||||
|
const uint64_t values[] = {
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0x7f,
|
||||||
|
0x80,
|
||||||
|
uint64_t{1} << 32,
|
||||||
|
uint64_t{1} << 63,
|
||||||
|
std::numeric_limits<uint64_t>::max(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (uint64_t value : values) {
|
||||||
|
CBigNum num(value);
|
||||||
|
BOOST_CHECK_EQUAL(num.getuint64(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
CBigNum negative(-1);
|
||||||
|
BOOST_CHECK_EQUAL(negative.getuint64(), uint64_t{1});
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(bignum_rejects_invalid_output_base)
|
||||||
|
{
|
||||||
|
CBigNum value(42);
|
||||||
|
BOOST_CHECK_THROW(value.ToString(0), bignum_error);
|
||||||
|
BOOST_CHECK_THROW(value.ToString(1), bignum_error);
|
||||||
|
BOOST_CHECK_THROW(value.ToString(17), bignum_error);
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// Copyright (c) 2026 Triangles developers
|
||||||
|
// Distributed under the MIT/X11 software license
|
||||||
|
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
|
#include "../bootstrap.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string ManifestWithFilename(const std::string& filename)
|
||||||
|
{
|
||||||
|
return std::string(R"json({
|
||||||
|
"version": "1.6",
|
||||||
|
"chain_tip": {
|
||||||
|
"height": 2206004,
|
||||||
|
"blockhash": "b34e8e6a7bb7f52167d81aaad4d26f87a876898fdd0fce860916fc1aaf9a2a46"
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
")json") + filename + R"json(": {
|
||||||
|
"sha256": "1419282DAE817315EE1B955543F6248233FE5800F5E8488734A0ECE5BD6781EA",
|
||||||
|
"type": "utxo_snapshot_v3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"canonical": { "snapshot": ")json" + filename + R"json(" }
|
||||||
|
})json";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(bootstrap_security_tests)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(remote_manifest_parses_canonical_snapshot)
|
||||||
|
{
|
||||||
|
Bootstrap::RemoteSnapshot snapshot;
|
||||||
|
std::string error;
|
||||||
|
BOOST_REQUIRE(Bootstrap::ParseRemoteSnapshotManifest(
|
||||||
|
ManifestWithFilename("utxo-snapshot.bin"), snapshot, error));
|
||||||
|
BOOST_CHECK_EQUAL(snapshot.filename, "utxo-snapshot.bin");
|
||||||
|
BOOST_CHECK_EQUAL(snapshot.height, 2206004);
|
||||||
|
BOOST_CHECK_EQUAL(
|
||||||
|
snapshot.sha256,
|
||||||
|
"1419282dae817315ee1b955543f6248233fe5800f5e8488734a0ece5bd6781ea");
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(remote_manifest_rejects_path_traversal)
|
||||||
|
{
|
||||||
|
Bootstrap::RemoteSnapshot snapshot;
|
||||||
|
std::string error;
|
||||||
|
BOOST_CHECK(!Bootstrap::ParseRemoteSnapshotManifest(
|
||||||
|
ManifestWithFilename("../../wallet.dat"), snapshot, error));
|
||||||
|
BOOST_CHECK_NE(error.find("plain filename"), std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(remote_manifest_rejects_malformed_hashes)
|
||||||
|
{
|
||||||
|
std::string manifest = ManifestWithFilename("utxo-snapshot.bin");
|
||||||
|
const std::string validHash =
|
||||||
|
"1419282DAE817315EE1B955543F6248233FE5800F5E8488734A0ECE5BD6781EA";
|
||||||
|
manifest.replace(manifest.find(validHash), validHash.size(), "not-a-sha256");
|
||||||
|
|
||||||
|
Bootstrap::RemoteSnapshot snapshot;
|
||||||
|
std::string error;
|
||||||
|
BOOST_CHECK(!Bootstrap::ParseRemoteSnapshotManifest(manifest, snapshot, error));
|
||||||
|
BOOST_CHECK_NE(error.find("invalid"), std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(remote_manifest_rejects_non_snapshot_canonical_file)
|
||||||
|
{
|
||||||
|
std::string manifest = ManifestWithFilename("wallet.dat");
|
||||||
|
const std::string snapshotType = "utxo_snapshot_v3";
|
||||||
|
manifest.replace(manifest.find(snapshotType), snapshotType.size(), "wallet_backup");
|
||||||
|
|
||||||
|
Bootstrap::RemoteSnapshot snapshot;
|
||||||
|
std::string error;
|
||||||
|
BOOST_CHECK(!Bootstrap::ParseRemoteSnapshotManifest(manifest, snapshot, error));
|
||||||
|
BOOST_CHECK_NE(error.find("not a UTXO snapshot"), std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
@@ -34,6 +34,7 @@ bool fUseFastIndex = false;
|
|||||||
enum Checkpoints::CPMode CheckpointsMode = Checkpoints::STRICT;
|
enum Checkpoints::CPMode CheckpointsMode = Checkpoints::STRICT;
|
||||||
|
|
||||||
void StartShutdown() { /* no-op for tests */ }
|
void StartShutdown() { /* no-op for tests */ }
|
||||||
|
void MarkShutdownFailure() { /* no-op for tests */ }
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ bool fUseFastIndex = false;
|
|||||||
enum Checkpoints::CPMode CheckpointsMode = Checkpoints::STRICT;
|
enum Checkpoints::CPMode CheckpointsMode = Checkpoints::STRICT;
|
||||||
|
|
||||||
void StartShutdown() { /* no-op */ }
|
void StartShutdown() { /* no-op */ }
|
||||||
|
void MarkShutdownFailure() { /* no-op for tests */ }
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|||||||
@@ -358,6 +358,23 @@ BOOST_AUTO_TEST_CASE(pos_validation_skip_is_only_historical_fast_path)
|
|||||||
nAssumeValidThreshold = oldAssumeValid;
|
nAssumeValidThreshold = oldAssumeValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(pos_block_signature_is_required_above_hardened_checkpoint)
|
||||||
|
{
|
||||||
|
const int oldAssumeValid = nAssumeValidThreshold;
|
||||||
|
const int checkpointHeight = Checkpoints::GetTotalBlocksEstimate();
|
||||||
|
|
||||||
|
BOOST_CHECK(!IsBlockSignatureRequiredAtHeight(checkpointHeight));
|
||||||
|
BOOST_CHECK(IsBlockSignatureRequiredAtHeight(checkpointHeight + 1));
|
||||||
|
|
||||||
|
// A rolling performance threshold must never authorize unsigned live
|
||||||
|
// blocks, including when stale-tip state makes the node report IBD.
|
||||||
|
nAssumeValidThreshold = checkpointHeight + 100;
|
||||||
|
BOOST_CHECK(IsConsensusAssumeValidHeight(checkpointHeight + 50));
|
||||||
|
BOOST_CHECK(IsBlockSignatureRequiredAtHeight(checkpointHeight + 50));
|
||||||
|
|
||||||
|
nAssumeValidThreshold = oldAssumeValid;
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Orphan block cap (P1 — DoS) ──────────────────────────────────────────
|
// ─── Orphan block cap (P1 — DoS) ──────────────────────────────────────────
|
||||||
// The cap on stored orphan blocks prevents an attacker from filling
|
// The cap on stored orphan blocks prevents an attacker from filling
|
||||||
// memory with garbage. If too low, legitimate orphans are dropped. If
|
// memory with garbage. If too low, legitimate orphans are dropped. If
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ bool fUseFastIndex = false;
|
|||||||
enum Checkpoints::CPMode CheckpointsMode = Checkpoints::STRICT;
|
enum Checkpoints::CPMode CheckpointsMode = Checkpoints::STRICT;
|
||||||
|
|
||||||
void StartShutdown() { /* no-op for tests */ }
|
void StartShutdown() { /* no-op for tests */ }
|
||||||
|
void MarkShutdownFailure() { /* no-op for tests */ }
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -100,19 +101,9 @@ struct TmpDataDir
|
|||||||
// Compute SHA-256 of a file's bytes.
|
// Compute SHA-256 of a file's bytes.
|
||||||
uint256 Sha256OfFile(const fs::path& p)
|
uint256 Sha256OfFile(const fs::path& p)
|
||||||
{
|
{
|
||||||
FILE* f = fopen(p.string().c_str(), "rb");
|
|
||||||
BOOST_REQUIRE_MESSAGE(f != nullptr, "open failed: " << p.string());
|
|
||||||
SHA256_CTX ctx;
|
|
||||||
SHA256_Init(&ctx);
|
|
||||||
std::vector<unsigned char> buf(64 * 1024);
|
|
||||||
while (true) {
|
|
||||||
size_t n = fread(buf.data(), 1, buf.size(), f);
|
|
||||||
if (n == 0) break;
|
|
||||||
SHA256_Update(&ctx, buf.data(), n);
|
|
||||||
}
|
|
||||||
fclose(f);
|
|
||||||
uint256 out;
|
uint256 out;
|
||||||
SHA256_Final(reinterpret_cast<unsigned char*>(&out), &ctx);
|
std::string error;
|
||||||
|
BOOST_REQUIRE_MESSAGE(SnapshotNet::ComputeSnapshotFileHash(p, out, error), error);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,8 +112,16 @@ uint256 Sha256OfBytes(const std::vector<unsigned char>& bytes)
|
|||||||
SHA256_CTX ctx;
|
SHA256_CTX ctx;
|
||||||
SHA256_Init(&ctx);
|
SHA256_Init(&ctx);
|
||||||
SHA256_Update(&ctx, bytes.data(), bytes.size());
|
SHA256_Update(&ctx, bytes.data(), bytes.size());
|
||||||
|
unsigned char digest[SHA256_DIGEST_LENGTH];
|
||||||
|
SHA256_Final(digest, &ctx);
|
||||||
|
static const char hex[] = "0123456789abcdef";
|
||||||
|
std::string digestHex(SHA256_DIGEST_LENGTH * 2, '0');
|
||||||
|
for (size_t i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
|
||||||
|
digestHex[2 * i] = hex[(digest[i] >> 4) & 0x0f];
|
||||||
|
digestHex[2 * i + 1] = hex[digest[i] & 0x0f];
|
||||||
|
}
|
||||||
uint256 out;
|
uint256 out;
|
||||||
SHA256_Final(reinterpret_cast<unsigned char*>(&out), &ctx);
|
out.SetHex(digestHex);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,6 +176,17 @@ BOOST_AUTO_TEST_SUITE_END()
|
|||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(snapshotnet_hash)
|
BOOST_AUTO_TEST_SUITE(snapshotnet_hash)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(file_hash_uses_standard_sha256_display_order)
|
||||||
|
{
|
||||||
|
TmpDataDir td;
|
||||||
|
fs::path p = td.path / "abc.bin";
|
||||||
|
WriteFile(p, {'a', 'b', 'c'});
|
||||||
|
|
||||||
|
BOOST_CHECK_EQUAL(
|
||||||
|
Sha256OfFile(p).ToString(),
|
||||||
|
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(file_hash_matches_inline_sha256)
|
BOOST_AUTO_TEST_CASE(file_hash_matches_inline_sha256)
|
||||||
{
|
{
|
||||||
// Synthesize a payload, hash it via stdlib openssl directly, then hash
|
// Synthesize a payload, hash it via stdlib openssl directly, then hash
|
||||||
|
|||||||
@@ -69,3 +69,4 @@ void StartShutdown()
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MarkShutdownFailure() { /* no-op for tests */ }
|
||||||
|
|||||||
@@ -168,6 +168,15 @@ BOOST_AUTO_TEST_CASE(util_WildcardMatch)
|
|||||||
BOOST_CHECK(WildcardMatch("abcdef", "a*f"));
|
BOOST_CHECK(WildcardMatch("abcdef", "a*f"));
|
||||||
BOOST_CHECK(!WildcardMatch("abcdef", "a*x"));
|
BOOST_CHECK(!WildcardMatch("abcdef", "a*x"));
|
||||||
BOOST_CHECK(WildcardMatch("", "*"));
|
BOOST_CHECK(WildcardMatch("", "*"));
|
||||||
|
|
||||||
|
const std::string address = "192.0.2.44";
|
||||||
|
const std::string allow = "192.0.2.*";
|
||||||
|
BOOST_CHECK(WildcardMatch(std::string_view(address), std::string_view(allow)));
|
||||||
|
|
||||||
|
// A long non-match must not recurse once per wildcard/input combination.
|
||||||
|
const std::string longInput(4096, 'a');
|
||||||
|
const std::string longMask = "*a*a*a*a*a*a*a*a*a*a*b";
|
||||||
|
BOOST_CHECK(!WildcardMatch(std::string_view(longInput), std::string_view(longMask)));
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(util_FormatMoney)
|
BOOST_AUTO_TEST_CASE(util_FormatMoney)
|
||||||
|
|||||||
@@ -294,6 +294,57 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
|
|||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(wallet_security_tests)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(hd_key_generation_fails_when_seed_is_unavailable)
|
||||||
|
{
|
||||||
|
CWallet wallet;
|
||||||
|
wallet.fHDEnabled = true;
|
||||||
|
|
||||||
|
BOOST_CHECK_THROW(wallet.GenerateNewKey(), std::runtime_error);
|
||||||
|
BOOST_CHECK_EQUAL(wallet.nHDChainIndex, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(memory_wallet_encryption_roundtrip_preserves_keys)
|
||||||
|
{
|
||||||
|
CWallet wallet;
|
||||||
|
CKey original;
|
||||||
|
original.MakeNewKey(true);
|
||||||
|
BOOST_REQUIRE(wallet.AddKey(original));
|
||||||
|
|
||||||
|
SecureString passphrase;
|
||||||
|
passphrase.reserve(100);
|
||||||
|
passphrase = "correct horse battery staple";
|
||||||
|
|
||||||
|
const auto keypoolIt = mapArgs.find("-keypool");
|
||||||
|
const bool hadKeypoolArg = keypoolIt != mapArgs.end();
|
||||||
|
const std::string oldKeypoolArg = hadKeypoolArg ? keypoolIt->second : std::string();
|
||||||
|
mapArgs["-keypool"] = "0";
|
||||||
|
const bool encrypted = wallet.EncryptWallet(passphrase);
|
||||||
|
if (hadKeypoolArg)
|
||||||
|
mapArgs["-keypool"] = oldKeypoolArg;
|
||||||
|
else
|
||||||
|
mapArgs.erase("-keypool");
|
||||||
|
|
||||||
|
BOOST_REQUIRE(encrypted);
|
||||||
|
BOOST_CHECK(wallet.IsCrypted());
|
||||||
|
BOOST_CHECK(wallet.IsLocked());
|
||||||
|
|
||||||
|
SecureString wrongPassphrase;
|
||||||
|
wrongPassphrase.reserve(100);
|
||||||
|
wrongPassphrase = "wrong passphrase";
|
||||||
|
BOOST_CHECK(!wallet.Unlock(wrongPassphrase));
|
||||||
|
BOOST_CHECK(wallet.IsLocked());
|
||||||
|
|
||||||
|
BOOST_REQUIRE(wallet.Unlock(passphrase));
|
||||||
|
CKey recovered;
|
||||||
|
BOOST_REQUIRE(wallet.GetKey(original.GetPubKey().GetID(), recovered));
|
||||||
|
BOOST_CHECK(recovered.GetPubKey() == original.GetPubKey());
|
||||||
|
BOOST_CHECK(wallet.Lock());
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// AbandonTransaction tests
|
// AbandonTransaction tests
|
||||||
//
|
//
|
||||||
|
|||||||
+26
-8
@@ -19,7 +19,7 @@
|
|||||||
// Connection parameters (highest precedence first):
|
// Connection parameters (highest precedence first):
|
||||||
// 1. Command line flags: -rpcuser/-rpcpassword/-rpcconnect/-rpcport
|
// 1. Command line flags: -rpcuser/-rpcpassword/-rpcconnect/-rpcport
|
||||||
// 2. triangles.conf in the data directory (or -conf=<path>)
|
// 2. triangles.conf in the data directory (or -conf=<path>)
|
||||||
// 3. Defaults: 127.0.0.1:19111 mainnet, 19112 testnet; no auth (must be set in conf)
|
// 3. Defaults: 127.0.0.1:19112 mainnet, 19111 testnet; no auth (must be set in conf)
|
||||||
//
|
//
|
||||||
// Usage:
|
// Usage:
|
||||||
// triangles-cli help List commands (delegates to daemon)
|
// triangles-cli help List commands (delegates to daemon)
|
||||||
@@ -70,6 +70,7 @@
|
|||||||
#define TRI_CLI_CLOSE_SOCKET(s) closesocket(s)
|
#define TRI_CLI_CLOSE_SOCKET(s) closesocket(s)
|
||||||
#else
|
#else
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
@@ -108,10 +109,20 @@ static bool GetBoolArg(const string& key, bool def)
|
|||||||
return (v != "0" && v != "false" && v != "no");
|
return (v != "0" && v != "false" && v != "no");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ReadConfigFile(const string& path)
|
static bool ReadConfigFile(const string& path, string& error)
|
||||||
{
|
{
|
||||||
|
#ifndef _WIN32
|
||||||
|
struct stat configStat;
|
||||||
|
if (::lstat(path.c_str(), &configStat) == 0 &&
|
||||||
|
(!S_ISREG(configStat.st_mode) || configStat.st_uid != geteuid() ||
|
||||||
|
(configStat.st_mode & (S_IRWXG | S_IRWXO)) != 0)) {
|
||||||
|
error = "refusing insecure configuration file " + path +
|
||||||
|
"; require a regular file owned by the current user with no group or other access";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
ifstream f(path);
|
ifstream f(path);
|
||||||
if (!f.good()) return;
|
if (!f.good()) return true;
|
||||||
string line;
|
string line;
|
||||||
while (getline(f, line)) {
|
while (getline(f, line)) {
|
||||||
if (!line.empty() && line.back() == '\r') line.pop_back();
|
if (!line.empty() && line.back() == '\r') line.pop_back();
|
||||||
@@ -141,6 +152,7 @@ static void ReadConfigFile(const string& path)
|
|||||||
mapMultiArgs[dashKey].push_back(value);
|
mapMultiArgs[dashKey].push_back(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fs::path GetDefaultDataDir()
|
static fs::path GetDefaultDataDir()
|
||||||
@@ -231,7 +243,7 @@ static void ParseCommandLine(int argc, char* const argv[])
|
|||||||
|
|
||||||
struct RPCConn {
|
struct RPCConn {
|
||||||
string host = "127.0.0.1";
|
string host = "127.0.0.1";
|
||||||
string port = "19111";
|
string port = "19112";
|
||||||
string user;
|
string user;
|
||||||
string pass;
|
string pass;
|
||||||
};
|
};
|
||||||
@@ -244,11 +256,17 @@ static int AppInitRPCConn(RPCConn& conn)
|
|||||||
{
|
{
|
||||||
std::ifstream f(confPath);
|
std::ifstream f(confPath);
|
||||||
confExisted = f.good();
|
confExisted = f.good();
|
||||||
if (confExisted) ReadConfigFile(confPath.string());
|
if (confExisted) {
|
||||||
|
string configError;
|
||||||
|
if (!ReadConfigFile(confPath.string(), configError)) {
|
||||||
|
cerr << "triangles-cli: " << configError << "\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fTestNet = GetBoolArg("-testnet", false);
|
bool fTestNet = GetBoolArg("-testnet", false);
|
||||||
conn.port = GetArg("-rpcport", fTestNet ? "19112" : "19111");
|
conn.port = GetArg("-rpcport", fTestNet ? "19111" : "19112");
|
||||||
conn.host = GetArg("-rpcconnect", "127.0.0.1");
|
conn.host = GetArg("-rpcconnect", "127.0.0.1");
|
||||||
conn.user = GetArg("-rpcuser", "");
|
conn.user = GetArg("-rpcuser", "");
|
||||||
conn.pass = GetArg("-rpcpassword", "");
|
conn.pass = GetArg("-rpcpassword", "");
|
||||||
@@ -595,9 +613,9 @@ static int CommandLineHelp(ostream& out)
|
|||||||
<< "Options:\n"
|
<< "Options:\n"
|
||||||
<< " -conf=<file> Specify configuration file (default: triangles.conf)\n"
|
<< " -conf=<file> Specify configuration file (default: triangles.conf)\n"
|
||||||
<< " -datadir=<dir> Specify data directory\n"
|
<< " -datadir=<dir> Specify data directory\n"
|
||||||
<< " -testnet Use testnet (RPC port 19112)\n"
|
<< " -testnet Use testnet (RPC port 19111)\n"
|
||||||
<< " -rpcconnect=<ip> Send commands to node running on <ip> (default: 127.0.0.1)\n"
|
<< " -rpcconnect=<ip> Send commands to node running on <ip> (default: 127.0.0.1)\n"
|
||||||
<< " -rpcport=<port> Connect to JSON-RPC on <port> (default: 19111 or testnet: 19112)\n"
|
<< " -rpcport=<port> Connect to JSON-RPC on <port> (default: 19112 or testnet: 19111)\n"
|
||||||
<< " -rpcuser=<user> Username for JSON-RPC connections\n"
|
<< " -rpcuser=<user> Username for JSON-RPC connections\n"
|
||||||
<< " -rpcpassword=<pw> Password for JSON-RPC connections\n"
|
<< " -rpcpassword=<pw> Password for JSON-RPC connections\n"
|
||||||
<< " -stdin Read extra params from standard input, one per line\n"
|
<< " -stdin Read extra params from standard input, one per line\n"
|
||||||
|
|||||||
+132
-59
@@ -17,10 +17,12 @@
|
|||||||
|
|
||||||
#undef printf
|
#undef printf
|
||||||
#include "rpc_httpsocket.h" // raw-socket HTTP transport (replaces Boost.Asio)
|
#include "rpc_httpsocket.h" // raw-socket HTTP transport (replaces Boost.Asio)
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
#include <sys/select.h>
|
#include <sys/select.h>
|
||||||
@@ -34,13 +36,26 @@ namespace fs = std::filesystem;
|
|||||||
|
|
||||||
void ThreadRPCServer2(void* parg);
|
void ThreadRPCServer2(void* parg);
|
||||||
|
|
||||||
static std::string strRPCUserColonPass;
|
static std::string strRPCUserColonPass;
|
||||||
|
|
||||||
const Object emptyobj;
|
const Object emptyobj;
|
||||||
|
|
||||||
CNotificationQueue* pNotificationQueue = nullptr;
|
CNotificationQueue* pNotificationQueue = nullptr;
|
||||||
|
|
||||||
void ThreadRPCServer3(void* parg);
|
void ThreadRPCServer3(void* parg);
|
||||||
|
|
||||||
|
static bool RPCMethodAllowed(const std::string& method)
|
||||||
|
{
|
||||||
|
const auto it = mapMultiArgs.find("-rpcallowmethod");
|
||||||
|
if (it == mapMultiArgs.end() || it->second.empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (const std::string& allowed : it->second) {
|
||||||
|
if (allowed == method)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static inline unsigned short GetDefaultRPCPort()
|
static inline unsigned short GetDefaultRPCPort()
|
||||||
{
|
{
|
||||||
@@ -316,11 +331,8 @@ static const CRPCCommand vRPCCommands[] =
|
|||||||
{ "sendrawtransaction", &sendrawtransaction, false, false },
|
{ "sendrawtransaction", &sendrawtransaction, false, false },
|
||||||
{ "getcheckpoint", &getcheckpoint, true, false },
|
{ "getcheckpoint", &getcheckpoint, true, false },
|
||||||
{ "gencheckpoints", &gencheckpoints, true, false },
|
{ "gencheckpoints", &gencheckpoints, true, false },
|
||||||
{ "publishcheckpoint", &publishcheckpoint, true, false },
|
{ "publishcheckpoint", &publishcheckpoint, true, false },
|
||||||
{ "settrustedv2snapshotpublisher", &settrustedv2snapshotpublisher, false, false },
|
{ "getchaintips", &getchaintips, true, false },
|
||||||
{ "gettrustedv2snapshotpublisher", &gettrustedv2snapshotpublisher, false, false },
|
|
||||||
{ "unsettrustedv2snapshotpublisher", &unsettrustedv2snapshotpublisher, false, false },
|
|
||||||
{ "getchaintips", &getchaintips, true, false },
|
|
||||||
{ "invalidateblock", &invalidateblock, false, false },
|
{ "invalidateblock", &invalidateblock, false, false },
|
||||||
{ "reconsiderblock", &reconsiderblock, false, false },
|
{ "reconsiderblock", &reconsiderblock, false, false },
|
||||||
{ "recalculatesupply", &recalculatesupply, false, false },
|
{ "recalculatesupply", &recalculatesupply, false, false },
|
||||||
@@ -455,11 +467,27 @@ static string HTTPReply(int nStatus, const string& strMsg, bool keepalive)
|
|||||||
strMsg.c_str());
|
strMsg.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto,
|
static bool ReadHTTPLine(std::basic_istream<char>& stream, std::string& line,
|
||||||
string& strMethodHTTP, string& strURI)
|
size_t maxLength)
|
||||||
{
|
{
|
||||||
string str;
|
line.clear();
|
||||||
getline(stream, str);
|
char c = 0;
|
||||||
|
while (stream.get(c)) {
|
||||||
|
if (c == '\n')
|
||||||
|
return true;
|
||||||
|
if (line.size() >= maxLength)
|
||||||
|
return false;
|
||||||
|
line.push_back(c);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto,
|
||||||
|
string& strMethodHTTP, string& strURI)
|
||||||
|
{
|
||||||
|
string str;
|
||||||
|
if (!ReadHTTPLine(stream, str, 8192))
|
||||||
|
return HTTP_BAD_REQUEST;
|
||||||
// Trim trailing \r
|
// Trim trailing \r
|
||||||
if (!str.empty() && str[str.size()-1] == '\r')
|
if (!str.empty() && str[str.size()-1] == '\r')
|
||||||
str.resize(str.size()-1);
|
str.resize(str.size()-1);
|
||||||
@@ -482,27 +510,45 @@ int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto,
|
|||||||
return atoi(vWords[1].c_str());
|
return atoi(vWords[1].c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
int ReadHTTPHeader(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet)
|
int ReadHTTPHeader(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet)
|
||||||
{
|
{
|
||||||
int nLen = 0;
|
int nLen = 0;
|
||||||
while (true)
|
size_t totalHeaderBytes = 0;
|
||||||
{
|
while (true)
|
||||||
string str;
|
{
|
||||||
std::getline(stream, str);
|
string str;
|
||||||
if (str.empty() || str == "\r")
|
if (!ReadHTTPLine(stream, str, 8192))
|
||||||
break;
|
return -1;
|
||||||
|
totalHeaderBytes += str.size() + 1;
|
||||||
|
if (totalHeaderBytes > 64 * 1024)
|
||||||
|
return -1;
|
||||||
|
if (str.empty() || str == "\r")
|
||||||
|
break;
|
||||||
string::size_type nColon = str.find(":");
|
string::size_type nColon = str.find(":");
|
||||||
if (nColon != string::npos)
|
if (nColon != string::npos)
|
||||||
{
|
{
|
||||||
string strHeader = str.substr(0, nColon);
|
string strHeader = str.substr(0, nColon);
|
||||||
strHeader = TrimString(strHeader);
|
strHeader = TrimString(strHeader);
|
||||||
strHeader = ToLower(strHeader);
|
strHeader = ToLower(strHeader);
|
||||||
string strValue = str.substr(nColon+1);
|
string strValue = str.substr(nColon+1);
|
||||||
strValue = TrimString(strValue);
|
strValue = TrimString(strValue);
|
||||||
mapHeadersRet[strHeader] = strValue;
|
if (strHeader == "transfer-encoding" ||
|
||||||
if (strHeader == "content-length")
|
(strHeader == "content-length" && mapHeadersRet.count(strHeader) != 0)) {
|
||||||
nLen = atoi(strValue.c_str());
|
return -1;
|
||||||
}
|
}
|
||||||
|
mapHeadersRet[strHeader] = strValue;
|
||||||
|
if (strHeader == "content-length") {
|
||||||
|
errno = 0;
|
||||||
|
char* end = nullptr;
|
||||||
|
const unsigned long long parsed = std::strtoull(strValue.c_str(), &end, 10);
|
||||||
|
if (errno != 0 || end == strValue.c_str() || *end != '\0' ||
|
||||||
|
parsed > static_cast<unsigned long long>(MAX_SIZE) ||
|
||||||
|
parsed > static_cast<unsigned long long>(std::numeric_limits<int>::max())) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
nLen = static_cast<int>(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nLen;
|
return nLen;
|
||||||
}
|
}
|
||||||
@@ -702,8 +748,16 @@ void ThreadRPCServer2(void* parg)
|
|||||||
if ((mapArgs["-rpcpassword"] == "") ||
|
if ((mapArgs["-rpcpassword"] == "") ||
|
||||||
(mapArgs["-rpcuser"] == mapArgs["-rpcpassword"]))
|
(mapArgs["-rpcuser"] == mapArgs["-rpcpassword"]))
|
||||||
{
|
{
|
||||||
unsigned char rand_pwd[32];
|
unsigned char rand_pwd[32];
|
||||||
RAND_bytes(rand_pwd, 32);
|
if (RAND_bytes(rand_pwd, sizeof(rand_pwd)) != 1) {
|
||||||
|
uiInterface.ThreadSafeMessageBox(
|
||||||
|
_("Unable to generate a secure suggested RPC password. "
|
||||||
|
"The RPC server will not start."),
|
||||||
|
_("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL);
|
||||||
|
MarkShutdownFailure();
|
||||||
|
StartShutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
string strWhatAmI = "To use trianglesd";
|
string strWhatAmI = "To use trianglesd";
|
||||||
if (mapArgs.count("-server"))
|
if (mapArgs.count("-server"))
|
||||||
strWhatAmI = strprintf(_("To use the %s option"), "\"-server\"");
|
strWhatAmI = strprintf(_("To use the %s option"), "\"-server\"");
|
||||||
@@ -720,8 +774,9 @@ void ThreadRPCServer2(void* parg)
|
|||||||
strWhatAmI.c_str(),
|
strWhatAmI.c_str(),
|
||||||
GetConfigFile().string().c_str(),
|
GetConfigFile().string().c_str(),
|
||||||
EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str()),
|
EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str()),
|
||||||
_("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL);
|
_("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL);
|
||||||
StartShutdown();
|
MarkShutdownFailure();
|
||||||
|
StartShutdown();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -732,23 +787,23 @@ void ThreadRPCServer2(void* parg)
|
|||||||
"or Tor.\n");
|
"or Tor.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind the loopback interface(s) unless the operator explicitly opened the
|
const int nPort = (int)GetArg("-rpcport", GetDefaultRPCPort());
|
||||||
// RPC port to other hosts with -rpcallowip.
|
const std::string rpcBind = GetArg(
|
||||||
const bool loopbackOnly = !mapArgs.count("-rpcallowip");
|
std::string_view{"-rpcbind"}, std::string_view{""});
|
||||||
const int nPort = (int)GetArg("-rpcport", GetDefaultRPCPort());
|
|
||||||
|
std::string strBindError;
|
||||||
std::string strBindError;
|
std::vector<SOCKET> vListen = BindRPCSockets(nPort, rpcBind, strBindError);
|
||||||
std::vector<SOCKET> vListen = BindRPCSockets(nPort, loopbackOnly, strBindError);
|
|
||||||
if (vListen.empty()) {
|
if (vListen.empty()) {
|
||||||
uiInterface.ThreadSafeMessageBox(
|
uiInterface.ThreadSafeMessageBox(
|
||||||
strprintf(_("An error occurred while setting up the RPC port %d for listening: %s"),
|
strprintf(_("An error occurred while setting up the RPC port %d for listening: %s"),
|
||||||
nPort, strBindError.c_str()),
|
nPort, strBindError.c_str()),
|
||||||
_("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL);
|
_("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL);
|
||||||
StartShutdown();
|
MarkShutdownFailure();
|
||||||
|
StartShutdown();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
printf("RPC server listening on port %d (%s)\n", nPort,
|
printf("RPC server listening on port %d (%s)\n", nPort,
|
||||||
loopbackOnly ? "loopback only" : "all interfaces");
|
rpcBind.empty() ? "loopback only" : rpcBind.c_str());
|
||||||
|
|
||||||
// Accept loop. select() with a short timeout keeps the listener responsive
|
// Accept loop. select() with a short timeout keeps the listener responsive
|
||||||
// to fShutdown. Each accepted connection is handed to its own handler thread
|
// to fShutdown. Each accepted connection is handed to its own handler thread
|
||||||
@@ -780,10 +835,19 @@ void ThreadRPCServer2(void* parg)
|
|||||||
struct sockaddr_storage ss;
|
struct sockaddr_storage ss;
|
||||||
socklen_t len = sizeof(ss);
|
socklen_t len = sizeof(ss);
|
||||||
SOCKET hConn = accept(s, (struct sockaddr*)&ss, &len);
|
SOCKET hConn = accept(s, (struct sockaddr*)&ss, &len);
|
||||||
if (hConn == INVALID_SOCKET) {
|
if (hConn == INVALID_SOCKET) {
|
||||||
printf("RPC accept() failed\n");
|
printf("RPC accept() failed\n");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t timeoutSeconds = GetArg("-rpcservertimeout", 30);
|
||||||
|
if (timeoutSeconds < 1)
|
||||||
|
timeoutSeconds = 1;
|
||||||
|
if (timeoutSeconds > 600)
|
||||||
|
timeoutSeconds = 600;
|
||||||
|
if (!SetRPCSocketTimeouts(hConn, static_cast<int>(timeoutSeconds))) {
|
||||||
|
printf("RPC warning: failed to set connection timeouts\n");
|
||||||
|
}
|
||||||
|
|
||||||
const std::string strPeer = SockaddrToString((struct sockaddr*)&ss, len);
|
const std::string strPeer = SockaddrToString((struct sockaddr*)&ss, len);
|
||||||
|
|
||||||
@@ -979,7 +1043,13 @@ void ThreadRPCServer3(void* parg)
|
|||||||
map<string, string> mapHeaders;
|
map<string, string> mapHeaders;
|
||||||
string strRequest;
|
string strRequest;
|
||||||
|
|
||||||
ReadHTTP(conn->stream(), mapHeaders, strRequest);
|
const int requestStatus = ReadHTTP(conn->stream(), mapHeaders, strRequest);
|
||||||
|
if (requestStatus != 0) {
|
||||||
|
conn->stream() << HTTPReply(HTTP_BAD_REQUEST,
|
||||||
|
"{\"error\":\"Malformed HTTP request\"}",
|
||||||
|
false) << std::flush;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle REST API requests
|
// Handle REST API requests
|
||||||
string strHTTPMethod = mapHeaders.count("_method") ? mapHeaders["_method"] : "POST";
|
string strHTTPMethod = mapHeaders.count("_method") ? mapHeaders["_method"] : "POST";
|
||||||
@@ -1097,9 +1167,12 @@ void ThreadRPCServer3(void* parg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
json_spirit::Value CRPCTable::execute(const std::string &strMethod, const json_spirit::Array ¶ms) const
|
json_spirit::Value CRPCTable::execute(const std::string &strMethod, const json_spirit::Array ¶ms) const
|
||||||
{
|
{
|
||||||
// Find method
|
if (!RPCMethodAllowed(strMethod))
|
||||||
|
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found");
|
||||||
|
|
||||||
|
// Find method
|
||||||
const CRPCCommand *pcmd = tableRPC[strMethod];
|
const CRPCCommand *pcmd = tableRPC[strMethod];
|
||||||
if (!pcmd)
|
if (!pcmd)
|
||||||
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found");
|
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found");
|
||||||
|
|||||||
@@ -228,9 +228,6 @@ extern json_spirit::Value getblockbynumber(const json_spirit::Array& params, boo
|
|||||||
extern json_spirit::Value getcheckpoint(const json_spirit::Array& params, bool fHelp);
|
extern json_spirit::Value getcheckpoint(const json_spirit::Array& params, bool fHelp);
|
||||||
extern json_spirit::Value gencheckpoints(const json_spirit::Array& params, bool fHelp);
|
extern json_spirit::Value gencheckpoints(const json_spirit::Array& params, bool fHelp);
|
||||||
extern json_spirit::Value publishcheckpoint(const json_spirit::Array& params, bool fHelp);
|
extern json_spirit::Value publishcheckpoint(const json_spirit::Array& params, bool fHelp);
|
||||||
extern json_spirit::Value settrustedv2snapshotpublisher(const json_spirit::Array& params, bool fHelp);
|
|
||||||
extern json_spirit::Value gettrustedv2snapshotpublisher(const json_spirit::Array& params, bool fHelp);
|
|
||||||
extern json_spirit::Value unsettrustedv2snapshotpublisher(const json_spirit::Array& params, bool fHelp);
|
|
||||||
extern json_spirit::Value getchaintips(const json_spirit::Array& params, bool fHelp);
|
extern json_spirit::Value getchaintips(const json_spirit::Array& params, bool fHelp);
|
||||||
extern json_spirit::Value invalidateblock(const json_spirit::Array& params, bool fHelp);
|
extern json_spirit::Value invalidateblock(const json_spirit::Array& params, bool fHelp);
|
||||||
extern json_spirit::Value reconsiderblock(const json_spirit::Array& params, bool fHelp);
|
extern json_spirit::Value reconsiderblock(const json_spirit::Array& params, bool fHelp);
|
||||||
@@ -259,4 +256,3 @@ extern json_spirit::Value smsgbroadcast(const json_spirit::Array& params, bool f
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+45
-30
@@ -33,6 +33,8 @@
|
|||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
#include <execinfo.h>
|
#include <execinfo.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
@@ -174,9 +176,10 @@ uint64_t GetRand(uint64_t nMax)
|
|||||||
// to give every possible output value an equal possibility
|
// to give every possible output value an equal possibility
|
||||||
uint64_t nRange = (std::numeric_limits<uint64_t>::max() / nMax) * nMax;
|
uint64_t nRange = (std::numeric_limits<uint64_t>::max() / nMax) * nMax;
|
||||||
uint64_t nRand = 0;
|
uint64_t nRand = 0;
|
||||||
do
|
do {
|
||||||
RAND_bytes((unsigned char*)&nRand, sizeof(nRand));
|
if (RAND_bytes(reinterpret_cast<unsigned char*>(&nRand), sizeof(nRand)) != 1)
|
||||||
while (nRand >= nRange);
|
throw std::runtime_error("OpenSSL CSPRNG failure in GetRand");
|
||||||
|
} while (nRand >= nRange);
|
||||||
return (nRand % nMax);
|
return (nRand % nMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +191,8 @@ int GetRandInt(int nMax)
|
|||||||
uint256 GetRandHash()
|
uint256 GetRandHash()
|
||||||
{
|
{
|
||||||
uint256 hash;
|
uint256 hash;
|
||||||
RAND_bytes((unsigned char*)&hash, sizeof(hash));
|
if (RAND_bytes(reinterpret_cast<unsigned char*>(&hash), sizeof(hash)) != 1)
|
||||||
|
throw std::runtime_error("OpenSSL CSPRNG failure in GetRandHash");
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,7 +620,9 @@ bool SoftSetBoolArg(std::string_view strArg, bool fValue)
|
|||||||
|
|
||||||
bool WildcardMatch(std::string_view str, std::string_view mask)
|
bool WildcardMatch(std::string_view str, std::string_view mask)
|
||||||
{
|
{
|
||||||
return WildcardMatch(std::string(str), std::string(mask));
|
const std::string strOwned(str);
|
||||||
|
const std::string maskOwned(mask);
|
||||||
|
return WildcardMatch(strOwned.c_str(), maskOwned.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -954,31 +960,27 @@ string DecodeBase32(const string& str)
|
|||||||
|
|
||||||
bool WildcardMatch(const char* psz, const char* mask)
|
bool WildcardMatch(const char* psz, const char* mask)
|
||||||
{
|
{
|
||||||
while (true)
|
const char* star = nullptr;
|
||||||
{
|
const char* retry = nullptr;
|
||||||
switch (*mask)
|
|
||||||
{
|
|
||||||
case '\0':
|
|
||||||
return (*psz == '\0');
|
|
||||||
case '*':
|
|
||||||
return WildcardMatch(psz, mask+1) || (*psz && WildcardMatch(psz+1, mask));
|
|
||||||
case '?':
|
|
||||||
if (*psz == '\0')
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (*psz != *mask)
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
psz++;
|
|
||||||
mask++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WildcardMatch(const string& str, const string& mask)
|
while (*psz != '\0') {
|
||||||
{
|
if (*mask == '?' || *mask == *psz) {
|
||||||
return WildcardMatch(str.c_str(), mask.c_str());
|
++psz;
|
||||||
|
++mask;
|
||||||
|
} else if (*mask == '*') {
|
||||||
|
star = mask++;
|
||||||
|
retry = psz;
|
||||||
|
} else if (star != nullptr) {
|
||||||
|
mask = star + 1;
|
||||||
|
psz = ++retry;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*mask == '*')
|
||||||
|
++mask;
|
||||||
|
return *mask == '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1145,7 +1147,20 @@ void ReadConfigFile(map<string, string>& mapSettingsRet,
|
|||||||
// - Section headers ([section])
|
// - Section headers ([section])
|
||||||
// If any of those become needed, the actual conf syntax in
|
// If any of those become needed, the actual conf syntax in
|
||||||
// contrib/triangles.conf.example should be extended first.
|
// contrib/triangles.conf.example should be extended first.
|
||||||
std::ifstream streamConfig(GetConfigFile());
|
const std::filesystem::path configPath = GetConfigFile();
|
||||||
|
#ifndef WIN32
|
||||||
|
struct stat configStat;
|
||||||
|
if (::lstat(configPath.string().c_str(), &configStat) == 0) {
|
||||||
|
if (!S_ISREG(configStat.st_mode) || configStat.st_uid != geteuid() ||
|
||||||
|
(configStat.st_mode & (S_IRWXG | S_IRWXO)) != 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Refusing to read insecure configuration file " + configPath.string() +
|
||||||
|
"; it must be a regular file owned by the current user with no group or other access");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::ifstream streamConfig(configPath);
|
||||||
if (!streamConfig.good())
|
if (!streamConfig.good())
|
||||||
return; // No triangles.conf file is OK
|
return; // No triangles.conf file is OK
|
||||||
|
|
||||||
|
|||||||
+425
-153
@@ -13,12 +13,15 @@
|
|||||||
#include "kernel.h"
|
#include "kernel.h"
|
||||||
#include "coincontrol.h"
|
#include "coincontrol.h"
|
||||||
#include "addressindex.h"
|
#include "addressindex.h"
|
||||||
|
#include "init.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <openssl/crypto.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
extern unsigned int nStakeMaxAge;
|
extern unsigned int nStakeMaxAge;
|
||||||
@@ -26,6 +29,29 @@ extern unsigned int nStakeMaxAge;
|
|||||||
unsigned int nStakeSplitAge = 1 * 3 * 60 * 60;
|
unsigned int nStakeSplitAge = 1 * 3 * 60 * 60;
|
||||||
int64_t nStakeCombineThreshold = 20 * COIN;
|
int64_t nStakeCombineThreshold = 20 * COIN;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void CleanseWalletString(std::string& value)
|
||||||
|
{
|
||||||
|
if (!value.empty())
|
||||||
|
OPENSSL_cleanse(value.data(), value.size());
|
||||||
|
value.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RewriteWalletDatabase(const std::string& walletFile, const char* skip = nullptr)
|
||||||
|
{
|
||||||
|
if (ResolveWalletDbKind() != WalletDbKind::SQLite)
|
||||||
|
return CDB::Rewrite(walletFile, skip);
|
||||||
|
|
||||||
|
try {
|
||||||
|
CWalletDB walletdb(walletFile);
|
||||||
|
return walletdb.RewriteDatabase(skip);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
printf("RewriteWalletDatabase: %s\n", e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// mapWallet
|
// mapWallet
|
||||||
@@ -132,32 +158,80 @@ CPubKey CWallet::GenerateNewKey()
|
|||||||
RandAddSeedPerfmon();
|
RandAddSeedPerfmon();
|
||||||
CKey key;
|
CKey key;
|
||||||
bool fUsedHD = false;
|
bool fUsedHD = false;
|
||||||
if (fHDEnabled && !hdMnemonic.empty()) {
|
if (fHDEnabled) {
|
||||||
if (DeriveHDKey(nHDChainIndex, key)) { fUsedHD = true; fCompressed = true; }
|
if (hdMnemonic.empty())
|
||||||
|
throw std::runtime_error("CWallet::GenerateNewKey() : HD seed is unavailable while wallet is locked");
|
||||||
|
if (!DeriveHDKey(nHDChainIndex, key))
|
||||||
|
throw std::runtime_error("CWallet::GenerateNewKey() : HD key derivation failed");
|
||||||
|
fUsedHD = true;
|
||||||
|
fCompressed = true;
|
||||||
}
|
}
|
||||||
if (!fUsedHD)
|
if (!fUsedHD)
|
||||||
key.MakeNewKey(fCompressed);
|
key.MakeNewKey(fCompressed);
|
||||||
|
|
||||||
// Compressed public keys were introduced in version 0.6.0
|
// Compressed public keys were introduced in version 0.6.0
|
||||||
if (fCompressed)
|
if (fCompressed && !SetMinVersion(WalletFeature::ComprPubKey))
|
||||||
SetMinVersion(WalletFeature::ComprPubKey);
|
throw std::runtime_error("CWallet::GenerateNewKey() : wallet version update failed");
|
||||||
|
|
||||||
CPubKey pubkey = key.GetPubKey();
|
CPubKey pubkey = key.GetPubKey();
|
||||||
|
|
||||||
// Create new metadata
|
const int64_t nCreationTime = GetTime();
|
||||||
int64_t nCreationTime = GetTime();
|
const CKeyMetadata metadata(nCreationTime);
|
||||||
mapKeyMetadata[pubkey.GetID()] = CKeyMetadata(nCreationTime);
|
|
||||||
|
if (fUsedHD && fFileBacked) {
|
||||||
|
const int64_t nextHDChainIndex = nHDChainIndex + 1;
|
||||||
|
std::vector<unsigned char> cryptedSecret;
|
||||||
|
if (IsCrypted()) {
|
||||||
|
if (IsLocked() || vMasterKey.size() != WALLET_CRYPTO_KEY_SIZE)
|
||||||
|
throw std::runtime_error("CWallet::GenerateNewKey() : encrypted HD wallet is locked");
|
||||||
|
bool compressed = false;
|
||||||
|
if (!EncryptSecret(vMasterKey, key.GetSecret(compressed),
|
||||||
|
pubkey.GetHash(), cryptedSecret))
|
||||||
|
throw std::runtime_error("CWallet::GenerateNewKey() : key encryption failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
CWalletDB walletdb(strWalletFile);
|
||||||
|
if (!walletdb.TxnBegin())
|
||||||
|
throw std::runtime_error("CWallet::GenerateNewKey() : database transaction failed");
|
||||||
|
bool wrote = IsCrypted()
|
||||||
|
? walletdb.WriteCryptedKey(pubkey, cryptedSecret, metadata)
|
||||||
|
: walletdb.WriteKey(pubkey, key.GetPrivKey(), metadata);
|
||||||
|
wrote = wrote && walletdb.WriteHDChain(nextHDChainIndex);
|
||||||
|
if (!wrote) {
|
||||||
|
walletdb.TxnAbort();
|
||||||
|
throw std::runtime_error("CWallet::GenerateNewKey() : atomic HD key write failed");
|
||||||
|
}
|
||||||
|
if (!walletdb.TxnCommit()) {
|
||||||
|
walletdb.TxnAbort();
|
||||||
|
throw std::runtime_error("CWallet::GenerateNewKey() : atomic HD key commit failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool added = IsCrypted()
|
||||||
|
? CCryptoKeyStore::AddCryptedKey(pubkey, cryptedSecret)
|
||||||
|
: CBasicKeyStore::AddKey(key);
|
||||||
|
if (!added) {
|
||||||
|
printf("GenerateNewKey: HD key persisted but could not be added in memory; shutting down\n");
|
||||||
|
MarkShutdownFailure();
|
||||||
|
StartShutdown();
|
||||||
|
throw std::runtime_error("CWallet::GenerateNewKey() : in-memory HD key commit failed");
|
||||||
|
}
|
||||||
|
mapKeyMetadata[pubkey.GetID()] = metadata;
|
||||||
|
if (!nTimeFirstKey || nCreationTime < nTimeFirstKey)
|
||||||
|
nTimeFirstKey = nCreationTime;
|
||||||
|
nHDChainIndex = nextHDChainIndex;
|
||||||
|
return pubkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapKeyMetadata[pubkey.GetID()] = metadata;
|
||||||
|
if (!AddKey(key)) {
|
||||||
|
mapKeyMetadata.erase(pubkey.GetID());
|
||||||
|
throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed");
|
||||||
|
}
|
||||||
if (!nTimeFirstKey || nCreationTime < nTimeFirstKey)
|
if (!nTimeFirstKey || nCreationTime < nTimeFirstKey)
|
||||||
nTimeFirstKey = nCreationTime;
|
nTimeFirstKey = nCreationTime;
|
||||||
|
if (fUsedHD)
|
||||||
if (!AddKey(key))
|
++nHDChainIndex;
|
||||||
throw std::runtime_error("CWallet::GenerateNewKey() : AddKey failed");
|
return pubkey;
|
||||||
if (fUsedHD) {
|
|
||||||
nHDChainIndex++;
|
|
||||||
if (fFileBacked)
|
|
||||||
CWalletDB(strWalletFile).WriteHDChain(nHDChainIndex);
|
|
||||||
}
|
|
||||||
return key.GetPubKey();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::AddKey(const CKey& key)
|
bool CWallet::AddKey(const CKey& key)
|
||||||
@@ -215,22 +289,17 @@ bool fWalletUnlockStakingOnly = false;
|
|||||||
|
|
||||||
bool CWallet::Lock()
|
bool CWallet::Lock()
|
||||||
{
|
{
|
||||||
|
if (IsCrypted()) {
|
||||||
|
CleanseWalletString(hdMnemonic);
|
||||||
|
CleanseWalletString(hdPassphrase);
|
||||||
|
}
|
||||||
|
|
||||||
if (IsLocked())
|
if (IsLocked())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (fDebug)
|
if (fDebug)
|
||||||
printf("Locking wallet.\n");
|
printf("Locking wallet.\n");
|
||||||
|
|
||||||
if (IsCrypted()) {
|
|
||||||
hdMnemonic.clear(); // keep only the encrypted copies while locked
|
|
||||||
hdPassphrase.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
LOCK(cs_wallet);
|
|
||||||
CWalletDB wdb(strWalletFile);
|
|
||||||
|
|
||||||
}
|
|
||||||
return LockKeyStore();
|
return LockKeyStore();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -242,38 +311,66 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase)
|
|||||||
CCrypter crypter;
|
CCrypter crypter;
|
||||||
CKeyingMaterial vMasterKey;
|
CKeyingMaterial vMasterKey;
|
||||||
|
|
||||||
|
bool unlocked = false;
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
for (const MasterKeyMap::value_type& pMasterKey : mapMasterKeys)
|
for (const MasterKeyMap::value_type& pMasterKey : mapMasterKeys)
|
||||||
{
|
{
|
||||||
if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
|
if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
|
||||||
return false;
|
continue;
|
||||||
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
|
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
|
||||||
return false;
|
continue;
|
||||||
if (CCryptoKeyStore::Unlock(vMasterKey)) {
|
if (CCryptoKeyStore::Unlock(vMasterKey)) {
|
||||||
if (fHDEnabled && hdMnemonic.empty() && !vchCryptedHDMnemonic.empty()) {
|
if (fHDEnabled) {
|
||||||
|
if (vchCryptedHDMnemonic.empty()) {
|
||||||
|
LockKeyStore();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
CSecret sec;
|
CSecret sec;
|
||||||
if (DecryptSecret(vMasterKey, vchCryptedHDMnemonic, hdMnemonicIV, sec))
|
if (!DecryptSecret(vMasterKey, vchCryptedHDMnemonic, hdMnemonicIV, sec)) {
|
||||||
hdMnemonic.assign(sec.begin(), sec.end());
|
LockKeyStore();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string mnemonic(sec.begin(), sec.end());
|
||||||
|
if (!hd::CheckMnemonic(mnemonic)) {
|
||||||
|
OPENSSL_cleanse(mnemonic.data(), mnemonic.size());
|
||||||
|
LockKeyStore();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string passphrase;
|
||||||
|
if (!vchCryptedHDPassphrase.empty()) {
|
||||||
|
CSecret psec;
|
||||||
|
if (!DecryptSecret(vMasterKey, vchCryptedHDPassphrase,
|
||||||
|
hdPassphraseIV, psec)) {
|
||||||
|
OPENSSL_cleanse(mnemonic.data(), mnemonic.size());
|
||||||
|
LockKeyStore();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
passphrase.assign(psec.begin(), psec.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
CleanseWalletString(hdMnemonic);
|
||||||
|
CleanseWalletString(hdPassphrase);
|
||||||
|
hdMnemonic = std::move(mnemonic);
|
||||||
|
hdPassphrase = std::move(passphrase);
|
||||||
}
|
}
|
||||||
if (fHDEnabled && hdPassphrase.empty() && !vchCryptedHDPassphrase.empty()) {
|
unlocked = true;
|
||||||
CSecret psec;
|
break;
|
||||||
if (DecryptSecret(vMasterKey, vchCryptedHDPassphrase, hdPassphraseIV, psec))
|
|
||||||
hdPassphrase.assign(psec.begin(), psec.end());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SecureMsgWalletUnlocked();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
if (!unlocked)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SecureMsgWalletUnlocked();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase)
|
bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase)
|
||||||
{
|
{
|
||||||
bool fWasLocked = IsLocked();
|
const bool fWasLocked = IsLocked();
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
@@ -284,39 +381,73 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
|
|||||||
for (MasterKeyMap::value_type& pMasterKey : mapMasterKeys)
|
for (MasterKeyMap::value_type& pMasterKey : mapMasterKeys)
|
||||||
{
|
{
|
||||||
if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
|
if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
|
||||||
return false;
|
continue;
|
||||||
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
|
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
|
||||||
return false;
|
continue;
|
||||||
if (CCryptoKeyStore::Unlock(vMasterKey))
|
if (CCryptoKeyStore::Unlock(vMasterKey))
|
||||||
{
|
{
|
||||||
|
CMasterKey updatedMasterKey = pMasterKey.second;
|
||||||
int64_t nStartTime = GetTimeMillis();
|
int64_t nStartTime = GetTimeMillis();
|
||||||
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
|
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase,
|
||||||
|
updatedMasterKey.vchSalt,
|
||||||
|
updatedMasterKey.nDeriveIterations,
|
||||||
|
updatedMasterKey.nDerivationMethod))
|
||||||
|
break;
|
||||||
int64_t nElapsed = std::max((int64_t)1, GetTimeMillis() - nStartTime);
|
int64_t nElapsed = std::max((int64_t)1, GetTimeMillis() - nStartTime);
|
||||||
pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)nElapsed));
|
updatedMasterKey.nDeriveIterations =
|
||||||
|
updatedMasterKey.nDeriveIterations * (100 / ((double)nElapsed));
|
||||||
|
|
||||||
nStartTime = GetTimeMillis();
|
nStartTime = GetTimeMillis();
|
||||||
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
|
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase,
|
||||||
|
updatedMasterKey.vchSalt,
|
||||||
|
updatedMasterKey.nDeriveIterations,
|
||||||
|
updatedMasterKey.nDerivationMethod))
|
||||||
|
break;
|
||||||
nElapsed = std::max((int64_t)1, GetTimeMillis() - nStartTime);
|
nElapsed = std::max((int64_t)1, GetTimeMillis() - nStartTime);
|
||||||
pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + pMasterKey.second.nDeriveIterations * 100 / ((double)nElapsed)) / 2;
|
updatedMasterKey.nDeriveIterations =
|
||||||
|
(updatedMasterKey.nDeriveIterations +
|
||||||
|
updatedMasterKey.nDeriveIterations * 100 / ((double)nElapsed)) / 2;
|
||||||
|
|
||||||
if (pMasterKey.second.nDeriveIterations < 25000)
|
if (updatedMasterKey.nDeriveIterations < 25000)
|
||||||
pMasterKey.second.nDeriveIterations = 25000;
|
updatedMasterKey.nDeriveIterations = 25000;
|
||||||
|
|
||||||
printf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations);
|
printf("Wallet passphrase changed to an nDeriveIterations of %i\n",
|
||||||
|
updatedMasterKey.nDeriveIterations);
|
||||||
|
|
||||||
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
|
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase,
|
||||||
return false;
|
updatedMasterKey.vchSalt,
|
||||||
if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey))
|
updatedMasterKey.nDeriveIterations,
|
||||||
return false;
|
updatedMasterKey.nDerivationMethod))
|
||||||
CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second);
|
break;
|
||||||
if (fWasLocked)
|
if (!crypter.Encrypt(vMasterKey, updatedMasterKey.vchCryptedKey))
|
||||||
Lock();
|
break;
|
||||||
return true;
|
|
||||||
|
bool persisted = true;
|
||||||
|
if (fFileBacked) {
|
||||||
|
try {
|
||||||
|
persisted = CWalletDB(strWalletFile).WriteMasterKey(
|
||||||
|
pMasterKey.first, updatedMasterKey);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
printf("ChangeWalletPassphrase: %s\n", e.what());
|
||||||
|
persisted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!persisted)
|
||||||
|
break;
|
||||||
|
|
||||||
|
pMasterKey.second = std::move(updatedMasterKey);
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
LockKeyStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
if (!changed)
|
||||||
|
return false;
|
||||||
|
if (!fWasLocked && !Unlock(strNewWalletPassphrase))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWallet::SetBestChain(const CBlockLocator& loc)
|
void CWallet::SetBestChain(const CBlockLocator& loc)
|
||||||
@@ -346,27 +477,44 @@ bool CWallet::SetMinVersion(WalletFeature nVersion, CWalletDB* pwalletdbIn, bool
|
|||||||
if (fExplicit && static_cast<int>(nVersion) > nWalletMaxVersion)
|
if (fExplicit && static_cast<int>(nVersion) > nWalletMaxVersion)
|
||||||
nVersion = WalletFeature::Latest;
|
nVersion = WalletFeature::Latest;
|
||||||
|
|
||||||
nWalletVersion = static_cast<int>(nVersion);
|
const int newWalletVersion = static_cast<int>(nVersion);
|
||||||
|
|
||||||
if (static_cast<int>(nVersion) > nWalletMaxVersion)
|
|
||||||
nWalletMaxVersion = static_cast<int>(nVersion);
|
|
||||||
|
|
||||||
if (fFileBacked)
|
if (fFileBacked)
|
||||||
{
|
{
|
||||||
CWalletDB* pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(strWalletFile);
|
std::unique_ptr<CWalletDB> ownedWalletDB;
|
||||||
if (nWalletVersion >= 40000)
|
CWalletDB* pwalletdb = pwalletdbIn;
|
||||||
|
if (!pwalletdb) {
|
||||||
|
ownedWalletDB = std::make_unique<CWalletDB>(strWalletFile);
|
||||||
|
pwalletdb = ownedWalletDB.get();
|
||||||
|
if (!pwalletdb->TxnBegin())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wrote = true;
|
||||||
|
if (newWalletVersion >= 40000)
|
||||||
{
|
{
|
||||||
// Versions prior to 0.4.0 did not support the "minversion" record.
|
// Versions prior to 0.4.0 did not support the "minversion" record.
|
||||||
// Use a CCorruptAddress to make them crash instead.
|
// Use a CCorruptAddress to make them crash instead.
|
||||||
CCorruptAddress corruptAddress;
|
CCorruptAddress corruptAddress;
|
||||||
pwalletdb->WriteSetting("addrIncoming", corruptAddress);
|
wrote = pwalletdb->WriteSetting("addrIncoming", corruptAddress);
|
||||||
|
}
|
||||||
|
if (wrote && newWalletVersion > 40000)
|
||||||
|
wrote = pwalletdb->WriteMinVersion(newWalletVersion);
|
||||||
|
|
||||||
|
if (!wrote) {
|
||||||
|
if (ownedWalletDB)
|
||||||
|
pwalletdb->TxnAbort();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ownedWalletDB && !pwalletdb->TxnCommit()) {
|
||||||
|
pwalletdb->TxnAbort();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (nWalletVersion > 40000)
|
|
||||||
pwalletdb->WriteMinVersion(nWalletVersion);
|
|
||||||
if (!pwalletdbIn)
|
|
||||||
delete pwalletdb;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nWalletVersion = newWalletVersion;
|
||||||
|
if (newWalletVersion > nWalletMaxVersion)
|
||||||
|
nWalletMaxVersion = newWalletVersion;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,27 +533,38 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
|||||||
{
|
{
|
||||||
if (IsCrypted())
|
if (IsCrypted())
|
||||||
return false;
|
return false;
|
||||||
|
if (fHDEnabled && hdMnemonic.empty())
|
||||||
|
return false;
|
||||||
|
if (nMasterKeyMaxID == std::numeric_limits<unsigned int>::max())
|
||||||
|
return false;
|
||||||
|
|
||||||
CKeyingMaterial vMasterKey;
|
CKeyingMaterial vMasterKey;
|
||||||
RandAddSeedPerfmon();
|
RandAddSeedPerfmon();
|
||||||
|
|
||||||
vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
|
vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
|
||||||
RAND_bytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
|
if (RAND_bytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE) != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
CMasterKey kMasterKey(nDerivationMethodIndex);
|
CMasterKey kMasterKey(nDerivationMethodIndex);
|
||||||
|
|
||||||
RandAddSeedPerfmon();
|
RandAddSeedPerfmon();
|
||||||
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
|
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
|
||||||
RAND_bytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
|
if (RAND_bytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE) != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
CCrypter crypter;
|
CCrypter crypter;
|
||||||
int64_t nStartTime = GetTimeMillis();
|
int64_t nStartTime = GetTimeMillis();
|
||||||
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod);
|
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt,
|
||||||
|
25000, kMasterKey.nDerivationMethod))
|
||||||
|
return false;
|
||||||
int64_t nElapsed = std::max((int64_t)1, GetTimeMillis() - nStartTime);
|
int64_t nElapsed = std::max((int64_t)1, GetTimeMillis() - nStartTime);
|
||||||
kMasterKey.nDeriveIterations = 2500000 / ((double)nElapsed);
|
kMasterKey.nDeriveIterations = 2500000 / ((double)nElapsed);
|
||||||
|
|
||||||
nStartTime = GetTimeMillis();
|
nStartTime = GetTimeMillis();
|
||||||
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod);
|
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt,
|
||||||
|
kMasterKey.nDeriveIterations,
|
||||||
|
kMasterKey.nDerivationMethod))
|
||||||
|
return false;
|
||||||
nElapsed = std::max((int64_t)1, GetTimeMillis() - nStartTime);
|
nElapsed = std::max((int64_t)1, GetTimeMillis() - nStartTime);
|
||||||
kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)nElapsed)) / 2;
|
kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)nElapsed)) / 2;
|
||||||
|
|
||||||
@@ -421,51 +580,109 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
|||||||
|
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
|
CryptedKeyMap cryptedKeys;
|
||||||
|
if (!PrepareKeyEncryption(vMasterKey, cryptedKeys))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint256 newMnemonicIV;
|
||||||
|
uint256 newPassphraseIV;
|
||||||
|
std::vector<unsigned char> newCryptedMnemonic;
|
||||||
|
std::vector<unsigned char> newCryptedPassphrase;
|
||||||
|
if (fHDEnabled) {
|
||||||
|
CSecret mnemonicSecret(hdMnemonic.begin(), hdMnemonic.end());
|
||||||
|
newMnemonicIV = GetRandHash();
|
||||||
|
if (!EncryptSecret(vMasterKey, mnemonicSecret, newMnemonicIV,
|
||||||
|
newCryptedMnemonic))
|
||||||
|
return false;
|
||||||
|
if (!hdPassphrase.empty()) {
|
||||||
|
CSecret passphraseSecret(hdPassphrase.begin(), hdPassphrase.end());
|
||||||
|
newPassphraseIV = GetRandHash();
|
||||||
|
if (!EncryptSecret(vMasterKey, passphraseSecret, newPassphraseIV,
|
||||||
|
newCryptedPassphrase))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned int newMasterKeyID = nMasterKeyMaxID + 1;
|
||||||
|
const int oldWalletVersion = nWalletVersion;
|
||||||
|
const int oldWalletMaxVersion = nWalletMaxVersion;
|
||||||
if (fFileBacked)
|
if (fFileBacked)
|
||||||
{
|
{
|
||||||
std::unique_ptr<CWalletDB> dbEnc(new CWalletDB(strWalletFile));
|
std::unique_ptr<CWalletDB> dbEnc = std::make_unique<CWalletDB>(strWalletFile);
|
||||||
if (!dbEnc->TxnBegin())
|
if (!dbEnc->TxnBegin())
|
||||||
return false;
|
return false;
|
||||||
dbEnc->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
|
bool wrote = dbEnc->WriteMasterKey(newMasterKeyID, kMasterKey);
|
||||||
|
for (const auto& item : cryptedKeys) {
|
||||||
|
if (!wrote)
|
||||||
|
break;
|
||||||
|
CKeyMetadata metadata;
|
||||||
|
auto metadataIt = mapKeyMetadata.find(item.first);
|
||||||
|
if (metadataIt != mapKeyMetadata.end())
|
||||||
|
metadata = metadataIt->second;
|
||||||
|
wrote = dbEnc->WriteCryptedKey(item.second.first,
|
||||||
|
item.second.second, metadata);
|
||||||
|
}
|
||||||
|
if (wrote && fHDEnabled) {
|
||||||
|
wrote = dbEnc->WriteHDCryptedMnemonic(newMnemonicIV,
|
||||||
|
newCryptedMnemonic);
|
||||||
|
if (wrote) {
|
||||||
|
wrote = hdPassphrase.empty()
|
||||||
|
? dbEnc->EraseHDPassphrase()
|
||||||
|
: dbEnc->WriteHDCryptedPassphrase(newPassphraseIV,
|
||||||
|
newCryptedPassphrase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wrote)
|
||||||
|
wrote = SetMinVersion(WalletFeature::WalletCrypt, dbEnc.get(), true);
|
||||||
|
|
||||||
if (!EncryptKeys(vMasterKey))
|
if (!wrote) {
|
||||||
{
|
|
||||||
dbEnc->TxnAbort();
|
dbEnc->TxnAbort();
|
||||||
|
nWalletVersion = oldWalletVersion;
|
||||||
|
nWalletMaxVersion = oldWalletMaxVersion;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!dbEnc->TxnCommit()) {
|
||||||
if (fHDEnabled && !hdMnemonic.empty()) {
|
dbEnc->TxnAbort();
|
||||||
CSecret sec(hdMnemonic.begin(), hdMnemonic.end());
|
nWalletVersion = oldWalletVersion;
|
||||||
uint256 iv = GetRandHash();
|
nWalletMaxVersion = oldWalletMaxVersion;
|
||||||
std::vector<unsigned char> cipher;
|
|
||||||
if (!EncryptSecret(vMasterKey, sec, iv, cipher)) { dbEnc->TxnAbort(); return false; }
|
|
||||||
hdMnemonicIV = iv; vchCryptedHDMnemonic = cipher;
|
|
||||||
dbEnc->WriteHDCryptedMnemonic(iv, cipher);
|
|
||||||
}
|
|
||||||
if (fHDEnabled && !hdPassphrase.empty()) {
|
|
||||||
CSecret psec(hdPassphrase.begin(), hdPassphrase.end());
|
|
||||||
uint256 piv = GetRandHash();
|
|
||||||
std::vector<unsigned char> pcipher;
|
|
||||||
if (!EncryptSecret(vMasterKey, psec, piv, pcipher)) { dbEnc->TxnAbort(); return false; }
|
|
||||||
hdPassphraseIV = piv; vchCryptedHDPassphrase = pcipher;
|
|
||||||
dbEnc->WriteHDCryptedPassphrase(piv, pcipher);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetMinVersion(WalletFeature::WalletCrypt, dbEnc.get(), true);
|
|
||||||
|
|
||||||
if (!dbEnc->TxnCommit())
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!SetMinVersion(WalletFeature::WalletCrypt, nullptr, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CommitKeyEncryption(std::move(cryptedKeys))) {
|
||||||
|
printf("EncryptWallet: disk was updated but in-memory key encryption could not be committed; shutting down\n");
|
||||||
|
MarkShutdownFailure();
|
||||||
|
StartShutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
nMasterKeyMaxID = newMasterKeyID;
|
||||||
|
mapMasterKeys[newMasterKeyID] = kMasterKey;
|
||||||
|
if (fHDEnabled) {
|
||||||
|
hdMnemonicIV = newMnemonicIV;
|
||||||
|
hdPassphraseIV = newPassphraseIV;
|
||||||
|
vchCryptedHDMnemonic = std::move(newCryptedMnemonic);
|
||||||
|
vchCryptedHDPassphrase = std::move(newCryptedPassphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lock();
|
Lock();
|
||||||
Unlock(strWalletPassphrase);
|
if (!Unlock(strWalletPassphrase)) {
|
||||||
NewKeyPool();
|
printf("EncryptWallet: encrypted wallet could not be verified; shutting down\n");
|
||||||
|
MarkShutdownFailure();
|
||||||
|
StartShutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!NewKeyPool())
|
||||||
|
printf("EncryptWallet: wallet encrypted, but keypool regeneration failed\n");
|
||||||
Lock();
|
Lock();
|
||||||
|
|
||||||
// Need to completely rewrite the wallet file; if we don't, bdb might keep
|
if (fFileBacked && !RewriteWalletDatabase(strWalletFile)) {
|
||||||
// bits of the unencrypted private key in slack space in the database file.
|
printf("EncryptWallet: secure wallet database rewrite failed; shutting down\n");
|
||||||
CDB::Rewrite(strWalletFile);
|
MarkShutdownFailure();
|
||||||
|
StartShutdown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
NotifyStatusChanged(this);
|
NotifyStatusChanged(this);
|
||||||
@@ -793,7 +1010,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
|
|||||||
// the inputs (vfSpent was tracked on the wtx) and resolves the conflict.
|
// the inputs (vfSpent was tracked on the wtx) and resolves the conflict.
|
||||||
bool fErased = EraseFromWallet(hashTx);
|
bool fErased = EraseFromWallet(hashTx);
|
||||||
|
|
||||||
LogPrintf("CWallet::AbandonTransaction: %s abandoned (%u descendant(s) noted)\n",
|
LogPrintf("CWallet::AbandonTransaction: %s abandoned (%" PRIszu " descendant(s) noted)\n",
|
||||||
hashTx.ToString().c_str(), sDescendants.size());
|
hashTx.ToString().c_str(), sDescendants.size());
|
||||||
return fErased;
|
return fErased;
|
||||||
}
|
}
|
||||||
@@ -2115,10 +2332,12 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int
|
|||||||
break; // unable to find corresponding public key
|
break; // unable to find corresponding public key
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.GetPubKey() != vchPubKey)
|
if (key.GetPubKey() != vchPubKey)
|
||||||
{
|
{
|
||||||
if (fDebug && GetBoolArg("-printcoinstake"))
|
if (fDebug && GetBoolArg("-printcoinstake")) {
|
||||||
printf("CreateCoinStake : invalid key for kernel type=%d\n", whichType);
|
printf("CreateCoinStake : invalid key for kernel type=%d\n",
|
||||||
|
static_cast<int>(whichType));
|
||||||
|
}
|
||||||
break; // keys mismatch
|
break; // keys mismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2134,7 +2353,7 @@ bool CWallet::CreateCoinStake(const CKeyStore& keystore, unsigned int nBits, int
|
|||||||
if (GetWeight(block.GetBlockTime(), (int64_t)txNew.nTime) < nStakeSplitAge)
|
if (GetWeight(block.GetBlockTime(), (int64_t)txNew.nTime) < nStakeSplitAge)
|
||||||
txNew.vout.push_back(CTxOut(0, scriptPubKeyOut)); //split stake
|
txNew.vout.push_back(CTxOut(0, scriptPubKeyOut)); //split stake
|
||||||
if (fDebug && GetBoolArg("-printcoinstake"))
|
if (fDebug && GetBoolArg("-printcoinstake"))
|
||||||
printf("CreateCoinStake : added kernel type=%d\n", whichType);
|
printf("CreateCoinStake : added kernel type=%d\n", static_cast<int>(whichType));
|
||||||
fKernelFound = true;
|
fKernelFound = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2351,7 +2570,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
|
|||||||
DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this);
|
DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this);
|
||||||
if (nLoadWalletRet == DB_NEED_REWRITE)
|
if (nLoadWalletRet == DB_NEED_REWRITE)
|
||||||
{
|
{
|
||||||
if (CDB::Rewrite(strWalletFile, "\x04pool"))
|
if (RewriteWalletDatabase(strWalletFile, "\x04pool"))
|
||||||
{
|
{
|
||||||
setKeyPool.clear();
|
setKeyPool.clear();
|
||||||
// Note: can't top-up keypool here, because wallet is locked.
|
// Note: can't top-up keypool here, because wallet is locked.
|
||||||
@@ -2478,9 +2697,13 @@ bool CWallet::NewKeyPool()
|
|||||||
{
|
{
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
CWalletDB walletdb(strWalletFile);
|
std::unique_ptr<CWalletDB> walletdb;
|
||||||
for (int64_t nIndex : setKeyPool)
|
if (fFileBacked)
|
||||||
walletdb.ErasePool(nIndex);
|
walletdb = std::make_unique<CWalletDB>(strWalletFile);
|
||||||
|
for (int64_t nIndex : setKeyPool) {
|
||||||
|
if (walletdb && !walletdb->ErasePool(nIndex))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
setKeyPool.clear();
|
setKeyPool.clear();
|
||||||
|
|
||||||
if (IsLocked())
|
if (IsLocked())
|
||||||
@@ -2490,7 +2713,9 @@ bool CWallet::NewKeyPool()
|
|||||||
for (int i = 0; i < nKeys; i++)
|
for (int i = 0; i < nKeys; i++)
|
||||||
{
|
{
|
||||||
int64_t nIndex = i+1;
|
int64_t nIndex = i+1;
|
||||||
walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey()));
|
CKeyPool keypool(GenerateNewKey());
|
||||||
|
if (walletdb && !walletdb->WritePool(nIndex, keypool))
|
||||||
|
return false;
|
||||||
setKeyPool.insert(nIndex);
|
setKeyPool.insert(nIndex);
|
||||||
}
|
}
|
||||||
printf("CWallet::NewKeyPool wrote %"PRId64" new keys\n", nKeys);
|
printf("CWallet::NewKeyPool wrote %"PRId64" new keys\n", nKeys);
|
||||||
@@ -2506,7 +2731,9 @@ bool CWallet::TopUpKeyPool(unsigned int nSize)
|
|||||||
if (IsLocked())
|
if (IsLocked())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
CWalletDB walletdb(strWalletFile);
|
std::unique_ptr<CWalletDB> walletdb;
|
||||||
|
if (fFileBacked)
|
||||||
|
walletdb = std::make_unique<CWalletDB>(strWalletFile);
|
||||||
|
|
||||||
// Top up key pool
|
// Top up key pool
|
||||||
unsigned int nTargetSize;
|
unsigned int nTargetSize;
|
||||||
@@ -2520,7 +2747,8 @@ bool CWallet::TopUpKeyPool(unsigned int nSize)
|
|||||||
int64_t nEnd = 1;
|
int64_t nEnd = 1;
|
||||||
if (!setKeyPool.empty())
|
if (!setKeyPool.empty())
|
||||||
nEnd = *(--setKeyPool.end()) + 1;
|
nEnd = *(--setKeyPool.end()) + 1;
|
||||||
if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey())))
|
CKeyPool keypool(GenerateNewKey());
|
||||||
|
if (walletdb && !walletdb->WritePool(nEnd, keypool))
|
||||||
throw runtime_error("TopUpKeyPool() : writing generated key failed");
|
throw runtime_error("TopUpKeyPool() : writing generated key failed");
|
||||||
setKeyPool.insert(nEnd);
|
setKeyPool.insert(nEnd);
|
||||||
printf("keypool added key %"PRId64", size=%"PRIszu"\n", nEnd, setKeyPool.size());
|
printf("keypool added key %"PRId64", size=%"PRIszu"\n", nEnd, setKeyPool.size());
|
||||||
@@ -2946,16 +3174,18 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const {
|
|||||||
// ---- HD wallet (BIP39/BIP32) implementation ----
|
// ---- HD wallet (BIP39/BIP32) implementation ----
|
||||||
bool CWallet::DeriveHDKey(int64_t index, CKey& keyOut) const
|
bool CWallet::DeriveHDKey(int64_t index, CKey& keyOut) const
|
||||||
{
|
{
|
||||||
if (hdMnemonic.empty())
|
if (hdMnemonic.empty() || index < 0 ||
|
||||||
|
static_cast<uint64_t>(index) > std::numeric_limits<uint32_t>::max())
|
||||||
return false;
|
return false;
|
||||||
// If a BIP39 passphrase ("25th word") was set with the seed, it MUST be
|
// If a BIP39 passphrase ("25th word") was set with the seed, it MUST be
|
||||||
// part of every derivation — otherwise restored wallets derive different
|
// part of every derivation — otherwise restored wallets derive different
|
||||||
// addresses than the originals. Empty string = no passphrase (legacy).
|
// addresses than the originals. Empty string = no passphrase (legacy).
|
||||||
unsigned char priv[32];
|
unsigned char priv[32];
|
||||||
if (!hd::DeriveTriangles(hdMnemonic, hdPassphrase, 0, 0, (uint32_t)index, priv))
|
if (!hd::DeriveTriangles(hdMnemonic, hdPassphrase, 0, 0,
|
||||||
|
static_cast<uint32_t>(index), priv))
|
||||||
return false;
|
return false;
|
||||||
CSecret secret(priv, priv + 32);
|
CSecret secret(priv, priv + 32);
|
||||||
memset(priv, 0, sizeof(priv));
|
OPENSSL_cleanse(priv, sizeof(priv));
|
||||||
keyOut.SetSecret(secret, true); // HD keys are compressed
|
keyOut.SetSecret(secret, true); // HD keys are compressed
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2972,6 +3202,7 @@ bool CWallet::SetHDSeed(const std::string& mnemonicIn, const std::string& passph
|
|||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
if (IsLocked()) { strError = "Wallet is locked; unlock it before setting an HD seed."; return false; }
|
if (IsLocked()) { strError = "Wallet is locked; unlock it before setting an HD seed."; return false; }
|
||||||
|
if (fHDEnabled) { strError = "Wallet already has an HD seed; refusing to replace it."; return false; }
|
||||||
|
|
||||||
std::string m = mnemonicIn;
|
std::string m = mnemonicIn;
|
||||||
if (m.empty()) {
|
if (m.empty()) {
|
||||||
@@ -2983,45 +3214,86 @@ bool CWallet::SetHDSeed(const std::string& mnemonicIn, const std::string& passph
|
|||||||
|
|
||||||
unsigned char priv[32];
|
unsigned char priv[32];
|
||||||
if (!hd::DeriveTriangles(m, passphrase, 0, 0, 0, priv)) { strError = "Key derivation failed."; return false; }
|
if (!hd::DeriveTriangles(m, passphrase, 0, 0, 0, priv)) { strError = "Key derivation failed."; return false; }
|
||||||
memset(priv, 0, sizeof(priv));
|
OPENSSL_cleanse(priv, sizeof(priv));
|
||||||
|
|
||||||
hdMnemonic = m;
|
uint256 newMnemonicIV;
|
||||||
hdPassphrase = passphrase;
|
uint256 newPassphraseIV;
|
||||||
fHDEnabled = true;
|
std::vector<unsigned char> newCryptedMnemonic;
|
||||||
nHDChainIndex = 0;
|
std::vector<unsigned char> newCryptedPassphrase;
|
||||||
|
|
||||||
|
if (IsCrypted()) {
|
||||||
|
if (vMasterKey.size() != WALLET_CRYPTO_KEY_SIZE) {
|
||||||
|
strError = "Wallet master key is unavailable.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CSecret sec(m.begin(), m.end());
|
||||||
|
newMnemonicIV = GetRandHash();
|
||||||
|
if (!EncryptSecret(vMasterKey, sec, newMnemonicIV, newCryptedMnemonic)) {
|
||||||
|
strError = "Failed to encrypt seed.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!passphrase.empty()) {
|
||||||
|
CSecret psec(passphrase.begin(), passphrase.end());
|
||||||
|
newPassphraseIV = GetRandHash();
|
||||||
|
if (!EncryptSecret(vMasterKey, psec, newPassphraseIV,
|
||||||
|
newCryptedPassphrase)) {
|
||||||
|
strError = "Failed to encrypt passphrase.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (fFileBacked) {
|
if (fFileBacked) {
|
||||||
CWalletDB wdb(strWalletFile);
|
CWalletDB wdb(strWalletFile);
|
||||||
if (IsCrypted()) {
|
if (!wdb.TxnBegin()) {
|
||||||
CSecret sec(m.begin(), m.end());
|
strError = "Failed to start wallet database transaction.";
|
||||||
uint256 iv = GetRandHash();
|
return false;
|
||||||
std::vector<unsigned char> cipher;
|
}
|
||||||
if (!EncryptSecret(vMasterKey, sec, iv, cipher)) { strError = "Failed to encrypt seed."; return false; }
|
|
||||||
hdMnemonicIV = iv; vchCryptedHDMnemonic = cipher;
|
bool wrote = false;
|
||||||
wdb.WriteHDCryptedMnemonic(iv, cipher);
|
if (IsCrypted()) {
|
||||||
if (!passphrase.empty()) {
|
wrote = wdb.WriteHDCryptedMnemonic(newMnemonicIV, newCryptedMnemonic) &&
|
||||||
CSecret psec(passphrase.begin(), passphrase.end());
|
(passphrase.empty()
|
||||||
uint256 piv = GetRandHash();
|
? wdb.EraseHDPassphrase()
|
||||||
std::vector<unsigned char> pcipher;
|
: wdb.WriteHDCryptedPassphrase(newPassphraseIV,
|
||||||
if (!EncryptSecret(vMasterKey, psec, piv, pcipher)) { strError = "Failed to encrypt passphrase."; return false; }
|
newCryptedPassphrase));
|
||||||
hdPassphraseIV = piv; vchCryptedHDPassphrase = pcipher;
|
} else {
|
||||||
wdb.WriteHDCryptedPassphrase(piv, pcipher);
|
wrote = wdb.WriteHDMnemonic(m) &&
|
||||||
} else {
|
(passphrase.empty()
|
||||||
vchCryptedHDPassphrase.clear();
|
? wdb.EraseHDPassphrase()
|
||||||
wdb.EraseHDPassphrase(); // re-seed without passphrase: drop any old record
|
: wdb.WriteHDPassphrase(passphrase));
|
||||||
}
|
}
|
||||||
} else {
|
wrote = wrote && wdb.WriteHDChain(0);
|
||||||
wdb.WriteHDMnemonic(m);
|
|
||||||
if (!passphrase.empty())
|
if (!wrote) {
|
||||||
wdb.WriteHDPassphrase(passphrase);
|
wdb.TxnAbort();
|
||||||
else
|
strError = "Failed to persist HD seed; wallet database transaction was rolled back.";
|
||||||
wdb.EraseHDPassphrase();
|
return false;
|
||||||
|
}
|
||||||
|
if (!wdb.TxnCommit()) {
|
||||||
|
wdb.TxnAbort();
|
||||||
|
strError = "Failed to commit HD seed to the wallet database.";
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
wdb.WriteHDChain(nHDChainIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CleanseWalletString(hdMnemonic);
|
||||||
|
CleanseWalletString(hdPassphrase);
|
||||||
|
hdMnemonic = m;
|
||||||
|
hdPassphrase = passphrase;
|
||||||
|
hdMnemonicIV = newMnemonicIV;
|
||||||
|
hdPassphraseIV = newPassphraseIV;
|
||||||
|
vchCryptedHDMnemonic = std::move(newCryptedMnemonic);
|
||||||
|
vchCryptedHDPassphrase = std::move(newCryptedPassphrase);
|
||||||
|
fHDEnabled = true;
|
||||||
|
nHDChainIndex = 0;
|
||||||
|
|
||||||
// Replace any pre-existing (random) keypool with HD-derived keys so that
|
// Replace any pre-existing (random) keypool with HD-derived keys so that
|
||||||
// getnewaddress immediately hands out deterministic m/44'/2222'/0'/0/i keys.
|
// getnewaddress immediately hands out deterministic m/44'/2222'/0'/0/i keys.
|
||||||
NewKeyPool();
|
if (!NewKeyPool()) {
|
||||||
|
strError = "HD seed was stored, but rebuilding the keypool failed.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
mnemonicOut = m;
|
mnemonicOut = m;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -76,8 +76,6 @@ private:
|
|||||||
bool SelectCoinsSimple(int64_t nTargetValue, unsigned int nSpendTime, int nMinConf, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet) const;
|
bool SelectCoinsSimple(int64_t nTargetValue, unsigned int nSpendTime, int nMinConf, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet) const;
|
||||||
bool SelectCoins(int64_t nTargetValue, unsigned int nSpendTime, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet, const CCoinControl *coinControl=nullptr) const;
|
bool SelectCoins(int64_t nTargetValue, unsigned int nSpendTime, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet, const CCoinControl *coinControl=nullptr) const;
|
||||||
|
|
||||||
CWalletDB *pwalletdbEncryption;
|
|
||||||
|
|
||||||
// the current wallet version: clients below this version are not able to load the wallet
|
// the current wallet version: clients below this version are not able to load the wallet
|
||||||
int nWalletVersion;
|
int nWalletVersion;
|
||||||
|
|
||||||
@@ -104,9 +102,9 @@ public:
|
|||||||
nWalletMaxVersion = static_cast<int>(WalletFeature::Base);
|
nWalletMaxVersion = static_cast<int>(WalletFeature::Base);
|
||||||
fFileBacked = false;
|
fFileBacked = false;
|
||||||
nMasterKeyMaxID = 0;
|
nMasterKeyMaxID = 0;
|
||||||
pwalletdbEncryption = nullptr;
|
|
||||||
fHDEnabled = false;
|
fHDEnabled = false;
|
||||||
nHDChainIndex = 0;
|
nHDChainIndex = 0;
|
||||||
|
nTimeFirstKey = 0;
|
||||||
nOrderPosNext = 0;
|
nOrderPosNext = 0;
|
||||||
nCachedStakeWeight = 0;
|
nCachedStakeWeight = 0;
|
||||||
nCachedStakeWeightTime = 0;
|
nCachedStakeWeightTime = 0;
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ bool SQLiteDatabase::Open(std::string& strError)
|
|||||||
// Durability + integrity pragmas. FULL fsync on commit — a wallet must not
|
// Durability + integrity pragmas. FULL fsync on commit — a wallet must not
|
||||||
// lose a freshly-written key on power loss.
|
// lose a freshly-written key on power loss.
|
||||||
if (!ExecOrError("PRAGMA synchronous = FULL;", strError)) return false;
|
if (!ExecOrError("PRAGMA synchronous = FULL;", strError)) return false;
|
||||||
|
if (!ExecOrError("PRAGMA journal_mode = DELETE;", strError)) return false;
|
||||||
|
if (!ExecOrError("PRAGMA secure_delete = ON;", strError)) return false;
|
||||||
|
if (!ExecOrError("PRAGMA temp_store = MEMORY;", strError)) return false;
|
||||||
if (!ExecOrError("PRAGMA foreign_keys = ON;", strError)) return false;
|
if (!ExecOrError("PRAGMA foreign_keys = ON;", strError)) return false;
|
||||||
// Fail loudly instead of silently truncating an over-long blob.
|
// Fail loudly instead of silently truncating an over-long blob.
|
||||||
if (!ExecOrError("PRAGMA cell_size_check = ON;", strError)) return false;
|
if (!ExecOrError("PRAGMA cell_size_check = ON;", strError)) return false;
|
||||||
@@ -156,6 +159,20 @@ bool SQLiteDatabase::Backup(const std::string& strDest) const
|
|||||||
if (pDest) sqlite3_close(pDest);
|
if (pDest) sqlite3_close(pDest);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#ifndef WIN32
|
||||||
|
{
|
||||||
|
std::error_code ec;
|
||||||
|
fs::permissions(strDest,
|
||||||
|
fs::perms::owner_read | fs::perms::owner_write,
|
||||||
|
fs::perm_options::replace, ec);
|
||||||
|
if (ec) {
|
||||||
|
printf("SQLiteDatabase::Backup cannot restrict destination permissions: %s\n",
|
||||||
|
ec.message().c_str());
|
||||||
|
sqlite3_close(pDest);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
sqlite3_backup* bk = sqlite3_backup_init(pDest, "main", m_db, "main");
|
sqlite3_backup* bk = sqlite3_backup_init(pDest, "main", m_db, "main");
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
|
|||||||
+111
-6
@@ -42,6 +42,20 @@ namespace fs = std::filesystem;
|
|||||||
static uint64_t nAccountingEntryNumber = 0;
|
static uint64_t nAccountingEntryNumber = 0;
|
||||||
extern bool fWalletUnlockStakingOnly;
|
extern bool fWalletUnlockStakingOnly;
|
||||||
|
|
||||||
|
static bool RestrictWalletFilePermissions(const fs::path& path)
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
(void)path;
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
std::error_code ec;
|
||||||
|
fs::permissions(path,
|
||||||
|
fs::perms::owner_read | fs::perms::owner_write,
|
||||||
|
fs::perm_options::replace, ec);
|
||||||
|
return !ec;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Auto-backup wallet before flush/rewrite operations.
|
// Auto-backup wallet before flush/rewrite operations.
|
||||||
// Copies wallet.dat to wallet.dat.auto.bak if the backup is older than the wallet.
|
// Copies wallet.dat to wallet.dat.auto.bak if the backup is older than the wallet.
|
||||||
@@ -61,10 +75,17 @@ bool AutoBackupWallet(const fs::path& walletPath)
|
|||||||
}
|
}
|
||||||
if (fs::exists(backupPath)) {
|
if (fs::exists(backupPath)) {
|
||||||
uintmax_t backupSize = fs::file_size(backupPath);
|
uintmax_t backupSize = fs::file_size(backupPath);
|
||||||
if (backupSize == walletSize)
|
if (backupSize == walletSize) {
|
||||||
|
if (!RestrictWalletFilePermissions(backupPath))
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fs::copy_file(walletPath, backupPath, fs::copy_options::overwrite_existing);
|
fs::copy_file(walletPath, backupPath, fs::copy_options::overwrite_existing);
|
||||||
|
if (!RestrictWalletFilePermissions(backupPath)) {
|
||||||
|
printf("AutoBackupWallet: could not restrict backup file permissions\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
printf("AutoBackupWallet: backed up wallet.dat (%llu bytes) to wallet.dat.auto.bak\n",
|
printf("AutoBackupWallet: backed up wallet.dat (%llu bytes) to wallet.dat.auto.bak\n",
|
||||||
(unsigned long long)walletSize);
|
(unsigned long long)walletSize);
|
||||||
return true;
|
return true;
|
||||||
@@ -257,6 +278,10 @@ public:
|
|||||||
unsigned int nKeyMeta;
|
unsigned int nKeyMeta;
|
||||||
bool fIsEncrypted;
|
bool fIsEncrypted;
|
||||||
bool fAnyUnordered;
|
bool fAnyUnordered;
|
||||||
|
bool fHDPlainMnemonic;
|
||||||
|
bool fHDCryptedMnemonic;
|
||||||
|
bool fHDPlainPassphrase;
|
||||||
|
bool fHDCryptedPassphrase;
|
||||||
int nFileVersion;
|
int nFileVersion;
|
||||||
std::vector<uint256> vWalletUpgrade;
|
std::vector<uint256> vWalletUpgrade;
|
||||||
|
|
||||||
@@ -264,6 +289,10 @@ public:
|
|||||||
nKeys = nCKeys = nKeyMeta = 0;
|
nKeys = nCKeys = nKeyMeta = 0;
|
||||||
fIsEncrypted = false;
|
fIsEncrypted = false;
|
||||||
fAnyUnordered = false;
|
fAnyUnordered = false;
|
||||||
|
fHDPlainMnemonic = false;
|
||||||
|
fHDCryptedMnemonic = false;
|
||||||
|
fHDPlainPassphrase = false;
|
||||||
|
fHDCryptedPassphrase = false;
|
||||||
nFileVersion = 0;
|
nFileVersion = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -380,6 +409,7 @@ static bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssVa
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
pwallet->mapMasterKeys[nID] = kMasterKey;
|
pwallet->mapMasterKeys[nID] = kMasterKey;
|
||||||
|
wss.fIsEncrypted = true;
|
||||||
if (pwallet->nMasterKeyMaxID < nID)
|
if (pwallet->nMasterKeyMaxID < nID)
|
||||||
pwallet->nMasterKeyMaxID = nID;
|
pwallet->nMasterKeyMaxID = nID;
|
||||||
} else if (strType == "ckey") {
|
} else if (strType == "ckey") {
|
||||||
@@ -417,18 +447,22 @@ static bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssVa
|
|||||||
} else if (strType == "hdmnemonic") {
|
} else if (strType == "hdmnemonic") {
|
||||||
std::string m;
|
std::string m;
|
||||||
ssValue >> m;
|
ssValue >> m;
|
||||||
|
wss.fHDPlainMnemonic = true;
|
||||||
pwallet->LoadHDMnemonic(m);
|
pwallet->LoadHDMnemonic(m);
|
||||||
} else if (strType == "hdcmnemonic") {
|
} else if (strType == "hdcmnemonic") {
|
||||||
std::pair<uint256, std::vector<unsigned char>> cm;
|
std::pair<uint256, std::vector<unsigned char>> cm;
|
||||||
ssValue >> cm;
|
ssValue >> cm;
|
||||||
|
wss.fHDCryptedMnemonic = true;
|
||||||
pwallet->LoadCryptedHDMnemonic(cm.first, cm.second);
|
pwallet->LoadCryptedHDMnemonic(cm.first, cm.second);
|
||||||
} else if (strType == "hdpassphrase") {
|
} else if (strType == "hdpassphrase") {
|
||||||
std::string p;
|
std::string p;
|
||||||
ssValue >> p;
|
ssValue >> p;
|
||||||
|
wss.fHDPlainPassphrase = true;
|
||||||
pwallet->LoadHDPassphrase(p);
|
pwallet->LoadHDPassphrase(p);
|
||||||
} else if (strType == "hdcpassphrase") {
|
} else if (strType == "hdcpassphrase") {
|
||||||
std::pair<uint256, std::vector<unsigned char>> cp;
|
std::pair<uint256, std::vector<unsigned char>> cp;
|
||||||
ssValue >> cp;
|
ssValue >> cp;
|
||||||
|
wss.fHDCryptedPassphrase = true;
|
||||||
pwallet->LoadCryptedHDPassphrase(cp.first, cp.second);
|
pwallet->LoadCryptedHDPassphrase(cp.first, cp.second);
|
||||||
} else if (strType == "hdchain") {
|
} else if (strType == "hdchain") {
|
||||||
int64_t n;
|
int64_t n;
|
||||||
@@ -513,6 +547,35 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
|
|||||||
if (fNoncriticalErrors && result == DB_LOAD_OK)
|
if (fNoncriticalErrors && result == DB_LOAD_OK)
|
||||||
result = DB_NONCRITICAL_ERROR;
|
result = DB_NONCRITICAL_ERROR;
|
||||||
|
|
||||||
|
if (result != DB_LOAD_OK && result != DB_NONCRITICAL_ERROR)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
const bool conflictingMnemonicRecords =
|
||||||
|
wss.fHDPlainMnemonic && wss.fHDCryptedMnemonic;
|
||||||
|
const bool conflictingPassphraseRecords =
|
||||||
|
wss.fHDPlainPassphrase && wss.fHDCryptedPassphrase;
|
||||||
|
const bool missingMnemonic =
|
||||||
|
(wss.fHDPlainPassphrase || wss.fHDCryptedPassphrase) &&
|
||||||
|
!(wss.fHDPlainMnemonic || wss.fHDCryptedMnemonic);
|
||||||
|
const bool mixedHDProtection =
|
||||||
|
(wss.fHDPlainMnemonic && wss.fHDCryptedPassphrase) ||
|
||||||
|
(wss.fHDCryptedMnemonic && wss.fHDPlainPassphrase);
|
||||||
|
const bool plaintextInEncryptedWallet =
|
||||||
|
wss.fIsEncrypted &&
|
||||||
|
(wss.nKeys != 0 || wss.fHDPlainMnemonic || wss.fHDPlainPassphrase);
|
||||||
|
const bool encryptedHDInPlainWallet =
|
||||||
|
!wss.fIsEncrypted &&
|
||||||
|
(wss.fHDCryptedMnemonic || wss.fHDCryptedPassphrase);
|
||||||
|
const bool encryptedKeysWithoutMasterKey =
|
||||||
|
wss.nCKeys != 0 && pwallet->mapMasterKeys.empty();
|
||||||
|
|
||||||
|
if (conflictingMnemonicRecords || conflictingPassphraseRecords ||
|
||||||
|
missingMnemonic || mixedHDProtection || plaintextInEncryptedWallet ||
|
||||||
|
encryptedHDInPlainWallet || encryptedKeysWithoutMasterKey) {
|
||||||
|
printf("Error reading wallet database: inconsistent encryption or HD seed records\n");
|
||||||
|
return DB_CORRUPT;
|
||||||
|
}
|
||||||
|
|
||||||
if (result != DB_LOAD_OK)
|
if (result != DB_LOAD_OK)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
@@ -617,18 +680,55 @@ bool BackupWallet(const CWallet& wallet, const std::string& strDest)
|
|||||||
if (!wallet.fFileBacked)
|
if (!wallet.fFileBacked)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// For the SQLite backend, the database is a single file — copy directly
|
// SQLite's online backup API takes a consistent snapshot while the daemon
|
||||||
// (after a checkpoint flush to fold any -wal into the main file).
|
// is running; copying the live database file directly can produce a torn
|
||||||
|
// backup if a transaction commits during the copy.
|
||||||
if (ResolveWalletDbKind() == WalletDbKind::SQLite) {
|
if (ResolveWalletDbKind() == WalletDbKind::SQLite) {
|
||||||
fs::path pathSrc = GetDataDir() / wallet.strWalletFile;
|
fs::path pathSrc = GetDataDir() / wallet.strWalletFile;
|
||||||
fs::path pathDest(strDest);
|
fs::path pathDest(strDest);
|
||||||
if (fs::is_directory(pathDest))
|
if (fs::is_directory(pathDest))
|
||||||
pathDest /= wallet.strWalletFile;
|
pathDest /= wallet.strWalletFile;
|
||||||
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
fs::copy_file(pathSrc, pathDest, fs::copy_options::overwrite_existing, ec);
|
if (fs::exists(pathDest, ec) && fs::equivalent(pathSrc, pathDest, ec)) {
|
||||||
|
printf("refusing to back up wallet.dat onto itself\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ec.clear();
|
||||||
|
|
||||||
|
const fs::path pathTemp = pathDest.string() +
|
||||||
|
strprintf(".tmp.%" PRId64, GetTimeMillis());
|
||||||
|
try {
|
||||||
|
CWalletDB walletdb(wallet.strWalletFile);
|
||||||
|
if (!walletdb.BackupDatabase(pathTemp.string())) {
|
||||||
|
fs::remove(pathTemp, ec);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
printf("error backing up wallet.dat to %s - %s\n",
|
||||||
|
pathDest.string().c_str(), e.what());
|
||||||
|
fs::remove(pathTemp, ec);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!RestrictWalletFilePermissions(pathTemp)) {
|
||||||
|
printf("error restricting wallet backup permissions: %s\n",
|
||||||
|
pathDest.string().c_str());
|
||||||
|
fs::remove(pathTemp, ec);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fs::rename(pathTemp, pathDest, ec);
|
||||||
|
#ifdef WIN32
|
||||||
if (ec) {
|
if (ec) {
|
||||||
printf("error copying wallet.dat to %s - %s\n",
|
ec.clear();
|
||||||
|
fs::remove(pathDest, ec);
|
||||||
|
ec.clear();
|
||||||
|
fs::rename(pathTemp, pathDest, ec);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (ec) {
|
||||||
|
printf("error finalizing wallet backup %s - %s\n",
|
||||||
pathDest.string().c_str(), ec.message().c_str());
|
pathDest.string().c_str(), ec.message().c_str());
|
||||||
|
fs::remove(pathTemp, ec);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
printf("copied wallet.dat to %s\n", pathDest.string().c_str());
|
printf("copied wallet.dat to %s\n", pathDest.string().c_str());
|
||||||
@@ -653,6 +753,11 @@ bool BackupWallet(const CWallet& wallet, const std::string& strDest)
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
fs::copy_file(pathSrc, pathDest, fs::copy_options::overwrite_existing);
|
fs::copy_file(pathSrc, pathDest, fs::copy_options::overwrite_existing);
|
||||||
|
if (!RestrictWalletFilePermissions(pathDest)) {
|
||||||
|
printf("error restricting wallet backup permissions: %s\n",
|
||||||
|
pathDest.string().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
printf("copied wallet.dat to %s\n", pathDest.string().c_str());
|
printf("copied wallet.dat to %s\n", pathDest.string().c_str());
|
||||||
return true;
|
return true;
|
||||||
} catch (const fs::filesystem_error& e) {
|
} catch (const fs::filesystem_error& e) {
|
||||||
@@ -665,4 +770,4 @@ bool BackupWallet(const CWallet& wallet, const std::string& strDest)
|
|||||||
MilliSleep(100);
|
MilliSleep(100);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-13
@@ -123,8 +123,10 @@ public:
|
|||||||
return false;
|
return false;
|
||||||
if (fEraseUnencryptedKey)
|
if (fEraseUnencryptedKey)
|
||||||
{
|
{
|
||||||
Erase(std::make_pair(std::string("key"), vchPubKey.Raw()));
|
if (!Erase(std::make_pair(std::string("key"), vchPubKey.Raw())))
|
||||||
Erase(std::make_pair(std::string("wkey"), vchPubKey.Raw()));
|
return false;
|
||||||
|
if (!Erase(std::make_pair(std::string("wkey"), vchPubKey.Raw())))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -166,13 +168,13 @@ public:
|
|||||||
|
|
||||||
bool WriteHDMnemonic(const std::string& mnemonic) {
|
bool WriteHDMnemonic(const std::string& mnemonic) {
|
||||||
nWalletDBUpdated++;
|
nWalletDBUpdated++;
|
||||||
Erase(std::string("hdcmnemonic"));
|
const bool erased = Erase(std::string("hdcmnemonic"));
|
||||||
return Write(std::string("hdmnemonic"), mnemonic);
|
return erased && Write(std::string("hdmnemonic"), mnemonic);
|
||||||
}
|
}
|
||||||
bool WriteHDCryptedMnemonic(const uint256& iv, const std::vector<unsigned char>& cipher) {
|
bool WriteHDCryptedMnemonic(const uint256& iv, const std::vector<unsigned char>& cipher) {
|
||||||
nWalletDBUpdated++;
|
nWalletDBUpdated++;
|
||||||
Erase(std::string("hdmnemonic"));
|
const bool erased = Erase(std::string("hdmnemonic"));
|
||||||
return Write(std::string("hdcmnemonic"), std::make_pair(iv, cipher));
|
return erased && Write(std::string("hdcmnemonic"), std::make_pair(iv, cipher));
|
||||||
}
|
}
|
||||||
bool WriteHDChain(int64_t nIndex) {
|
bool WriteHDChain(int64_t nIndex) {
|
||||||
nWalletDBUpdated++;
|
nWalletDBUpdated++;
|
||||||
@@ -183,19 +185,19 @@ public:
|
|||||||
// means no passphrase (legacy wallets and the common case).
|
// means no passphrase (legacy wallets and the common case).
|
||||||
bool WriteHDPassphrase(const std::string& passphrase) {
|
bool WriteHDPassphrase(const std::string& passphrase) {
|
||||||
nWalletDBUpdated++;
|
nWalletDBUpdated++;
|
||||||
Erase(std::string("hdcpassphrase"));
|
const bool erased = Erase(std::string("hdcpassphrase"));
|
||||||
return Write(std::string("hdpassphrase"), passphrase);
|
return erased && Write(std::string("hdpassphrase"), passphrase);
|
||||||
}
|
}
|
||||||
bool WriteHDCryptedPassphrase(const uint256& iv, const std::vector<unsigned char>& cipher) {
|
bool WriteHDCryptedPassphrase(const uint256& iv, const std::vector<unsigned char>& cipher) {
|
||||||
nWalletDBUpdated++;
|
nWalletDBUpdated++;
|
||||||
Erase(std::string("hdpassphrase"));
|
const bool erased = Erase(std::string("hdpassphrase"));
|
||||||
return Write(std::string("hdcpassphrase"), std::make_pair(iv, cipher));
|
return erased && Write(std::string("hdcpassphrase"), std::make_pair(iv, cipher));
|
||||||
}
|
}
|
||||||
bool EraseHDPassphrase() {
|
bool EraseHDPassphrase() {
|
||||||
nWalletDBUpdated++;
|
nWalletDBUpdated++;
|
||||||
Erase(std::string("hdpassphrase"));
|
const bool erasedPlain = Erase(std::string("hdpassphrase"));
|
||||||
Erase(std::string("hdcpassphrase"));
|
const bool erasedCrypted = Erase(std::string("hdcpassphrase"));
|
||||||
return true;
|
return erasedPlain && erasedCrypted;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReadPool(int64_t nPool, CKeyPool& keypool)
|
bool ReadPool(int64_t nPool, CKeyPool& keypool)
|
||||||
@@ -251,6 +253,16 @@ public:
|
|||||||
return Read(std::string("version"), nVersion);
|
return Read(std::string("version"), nVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool RewriteDatabase(const char* pszSkip = nullptr)
|
||||||
|
{
|
||||||
|
return m_database && m_database->Rewrite(pszSkip);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BackupDatabase(const std::string& destination) const
|
||||||
|
{
|
||||||
|
return m_database && m_database->Backup(destination);
|
||||||
|
}
|
||||||
|
|
||||||
bool ReadAccount(const std::string& strAccount, CAccount& account);
|
bool ReadAccount(const std::string& strAccount, CAccount& account);
|
||||||
bool WriteAccount(const std::string& strAccount, const CAccount& account);
|
bool WriteAccount(const std::string& strAccount, const CAccount& account);
|
||||||
private:
|
private:
|
||||||
|
|||||||
Reference in New Issue
Block a user