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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/impl/dgb/coin/dgb_arith256.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ struct u256 {

static u256 from_u64(uint64_t v) { u256 r; r.limb[0] = v; return r; }

// Construct from a 32-byte little-endian digest (the scrypt_1024_1_1_256
// PoW output): limb[0] holds the least-significant 8 bytes. Mirrors bitcoin
// UintToArith256 -- the Scrypt PoW hash is read little-endian for the
// hash <= target comparison, so the digest the embedded DigiByte Core port
// produces drops straight into HeaderSample::pow_hash at the ingest boundary
// in the SAME byte order the 256-bit satisfaction gate already compares.
// Depends only on <cstdint> / builtin unsigned char, so the standalone
// header guard keeps linking with NO btclibs scrypt dependency; the scrypt
// CALL itself lands at the ingest boundary in a following slice.
static u256 from_le_bytes(const unsigned char b[32]) {
u256 r;
for (int i = 0; i < 4; ++i) {
uint64_t v = 0;
for (int j = 0; j < 8; ++j)
v |= (uint64_t)b[i * 8 + j] << (8 * j);
r.limb[i] = v;
}
return r;
}

bool fits_u64() const { return limb[1] == 0 && limb[2] == 0 && limb[3] == 0; }
bool is_zero() const { return limb[0] == 0 && limb[1] == 0 && limb[2] == 0 && limb[3] == 0; }
uint64_t low64() const { return limb[0]; }
Expand Down
24 changes: 23 additions & 1 deletion src/impl/dgb/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,32 @@ if (BUILD_TESTING AND GTest_FOUND)
# transport rpc.cpp wiring is deferred (Option-B scope). Each MUST appear
# in BOTH this ctest registration AND the build.yml --target allowlist,
# or it becomes a #143-style NOT_BUILT sentinel that reds master.
foreach(dgb_guard rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test)
foreach(dgb_guard rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test)
add_executable(${dgb_guard} ${dgb_guard}.cpp)
target_link_libraries(${dgb_guard} PRIVATE
GTest::gtest_main GTest::gtest nlohmann_json::nlohmann_json)
gtest_add_tests(${dgb_guard} "" AUTO)
endforeach()

# header_chain_test compiles the btclibs Scrypt PoW TU set DIRECTLY (the
# same crypto/*.cpp sources btclibs builds, with the same default flags) so
# the M3 7b digest conversion is exercised against the REAL
# scrypt_1024_1_1_256 hash, not a synthetic digest. Only the scrypt + SHA256
# family is pulled in -- NOT the full btclibs archive (its siphash.o needs
# the removed base_uint<256> symbol) and NOT core/the dgb OBJECT lib. This
# is linking against an existing scrypt.cpp, no shared source modified: the
# single DGB smoke gate holds, no shared-base flag owed. Same target name,
# so it stays in BOTH build.yml --target allowlists unchanged.
set(_dgb_scrypt_tus
${CMAKE_SOURCE_DIR}/src/btclibs/crypto/scrypt.cpp
${CMAKE_SOURCE_DIR}/src/btclibs/crypto/sha256.cpp
${CMAKE_SOURCE_DIR}/src/btclibs/crypto/sha256_sse4.cpp
${CMAKE_SOURCE_DIR}/src/btclibs/crypto/sha256_sse41.cpp
${CMAKE_SOURCE_DIR}/src/btclibs/crypto/sha256_avx2.cpp
${CMAKE_SOURCE_DIR}/src/btclibs/crypto/sha256_shani.cpp)
add_executable(header_chain_test header_chain_test.cpp ${_dgb_scrypt_tus})
target_include_directories(header_chain_test PRIVATE ${CMAKE_SOURCE_DIR}/src/btclibs)
target_link_libraries(header_chain_test PRIVATE
GTest::gtest_main GTest::gtest nlohmann_json::nlohmann_json)
gtest_add_tests(header_chain_test "" AUTO)
endif()
112 changes: 109 additions & 3 deletions src/impl/dgb/test/header_chain_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@
// reaches avg_target nor widens actual_timespan. A naive all-headers
// window would corrupt both; this proves it can't.
//
// Header-only guard: links the header-only chain helpers + gtest, no dgb
// OBJECT lib / transport. MUST appear in BOTH the test/CMakeLists foreach AND
// both build.yml --target allowlists, or it becomes a #143 NOT_BUILT sentinel.
// Links the header-only chain helpers + gtest, plus the btclibs Scrypt PoW TU
// set (scrypt.cpp + the SHA256 family) so the M3 7b digest conversion runs
// against the REAL scrypt_1024_1_1_256 hash -- NOT the full btclibs archive,
// NOT the dgb OBJECT lib / transport. MUST appear in BOTH the test/CMakeLists
// dedicated target block AND both build.yml --target allowlists, or it becomes
// a #143 NOT_BUILT sentinel.
// ---------------------------------------------------------------------------

#include <cstdint>
#include <cstring>

#include <gtest/gtest.h>

#include <impl/dgb/coin/header_chain.hpp>
#include <btclibs/crypto/scrypt.h>

using namespace c2pool::dgb;
using dgb::coin::DGB_BLOCK_VERSION_SCRYPT;
Expand Down Expand Up @@ -440,3 +445,104 @@ TEST(HeaderChainValidate, IngestPoWSatisfactionRunsAtFullWidth)
EXPECT_EQ(hc.validate_and_append(weak), IngestResult::REJECTED);
EXPECT_EQ(hc.size(), 1u); // unchanged
}

// ---------------------------------------------------------------------------
// 256-BIT DIGEST INGEST (M3 7b) -- bytes -> field conversion. The embedded
// DigiByte Core port produces the Scrypt PoW hash as a 32-byte little-endian
// digest (scrypt_1024_1_1_256 output). u256::from_le_bytes is the bytes->field
// half of dropping that digest into HeaderSample::pow_hash; the scrypt CALL
// itself (btclibs) lands at the ingest boundary in a following slice. Pins the
// little-endian limb mapping (limb[0] = least-significant 8 bytes, matching
// bitcoin UintToArith256) and proves a converted digest feeds the existing
// full-width satisfaction gate with the SAME ordering -- no low64 proxy.
// ---------------------------------------------------------------------------
TEST(HeaderChainValidate, ScryptDigestLittleEndianConversion)
{
// Byte i = i+1, so each limb is a distinct, easily-checked LE word.
unsigned char d[32];
for (int i = 0; i < 32; ++i) d[i] = (unsigned char)(i + 1);
u256 h = u256::from_le_bytes(d);
EXPECT_EQ(h.limb[0], 0x0807060504030201ULL);
EXPECT_EQ(h.limb[1], 0x100f0e0d0c0b0a09ULL);
EXPECT_EQ(h.limb[2], 0x1817161514131211ULL);
EXPECT_EQ(h.limb[3], 0x201f1e1d1c1b1a19ULL);

// All-zero digest -> 0 (trivially satisfies any non-zero target).
unsigned char zero[32] = {0};
EXPECT_TRUE(u256::from_le_bytes(zero).is_zero());

// All-0xff digest -> MAX 256-bit value (every limb saturated), exceeding
// any real Scrypt target -> would NOT satisfy PoW.
unsigned char ones[32];
for (int i = 0; i < 32; ++i) ones[i] = 0xff;
u256 maxv = u256::from_le_bytes(ones);
for (int i = 0; i < 4; ++i) EXPECT_EQ(maxv.limb[i], 0xffffffffffffffffULL);

// Feed a converted digest through the live satisfaction gate: digest 9
// (low byte) vs target 2^192 + 5 -> 9 <= 2^192+5 at full width -> VALIDATED.
// Proves the converted value flows the SAME full-width path the field-shape
// swap widened, not a low64 proxy.
HeaderChain hc;
u256 target; target.limb[3] = 1; target.limb[0] = 5;
unsigned char nine[32] = {0}; nine[0] = 9;
HeaderSample s{SCRYPT, 1000};
s.target = target;
s.pow_hash = u256::from_le_bytes(nine);
EXPECT_EQ(hc.validate_and_append(s), IngestResult::VALIDATED_SCRYPT);
EXPECT_EQ(hc.size(), 1u);
}


// ---------------------------------------------------------------------------
// REAL SCRYPT DIGEST -> pow_hash, END-TO-END (M3 7b, slice (a)). The prior test
// pins the from_le_bytes limb mapping against SYNTHETIC bytes; this one closes
// the loop by running the ACTUAL btclibs scrypt_1024_1_1_256 PoW hash over an
// 80-byte header, converting the 32-byte little-endian digest via from_le_bytes,
// and feeding it through the live full-width satisfaction gate. Proves the
// conversion primitive is correct against a GENUINE Scrypt digest. It does NOT
// prove scrypt is wired into the production node.cpp ingest path -- that
// (b)-shaped call lands in embedded-daemon Phase A, scoped with an exe-smoke.
// ---------------------------------------------------------------------------
TEST(HeaderChainValidate, RealScryptDigestFeedsSatisfactionGate)
{
// Deterministic 80-byte header (content arbitrary for the conversion proof;
// fixed so the digest is a stable self-derived KAT).
char header[80];
for (int i = 0; i < 80; ++i) header[i] = (char)(i + 1);

unsigned char digest[32];
scrypt_1024_1_1_256(header, reinterpret_cast<char*>(digest));

// Deterministic: a second run yields the identical digest.
unsigned char digest2[32];
scrypt_1024_1_1_256(header, reinterpret_cast<char*>(digest2));
EXPECT_EQ(0, std::memcmp(digest, digest2, 32));

// Self-derived KAT: pin the actual scrypt PoW hash for this fixed header so
// any drift in scrypt OR the LE conversion is caught. limb[0] is the least-
// significant 8 bytes (from_le_bytes / UintToArith256 ordering).
u256 h = u256::from_le_bytes(digest);
EXPECT_EQ(h.limb[0], 0x3863b84a2bd8d4c7ULL);
EXPECT_EQ(h.limb[1], 0xdbc48fcba50b456bULL);
EXPECT_EQ(h.limb[2], 0x22185f7f7b983293ULL);
EXPECT_EQ(h.limb[3], 0x64d9ed52d29c3fbdULL);

// The real digest, converted, satisfies a max (pow_limit) target -> VALID.
HeaderChain hc;
u256 max_target; for (int i = 0; i < 4; ++i) max_target.limb[i] = 0xffffffffffffffffULL;
HeaderSample ok{SCRYPT, 1000};
ok.target = max_target;
ok.pow_hash = h;
EXPECT_EQ(hc.validate_and_append(ok), IngestResult::VALIDATED_SCRYPT);
EXPECT_EQ(hc.size(), 1u);

// Same real digest vs a tiny target (value 1): the high limbs are nonzero
// (0x64d9...) so hash > 1 at full width -> REJECT. A low64-only proxy could
// not see the high limbs -- this proves the converted digest flows the
// full-width satisfaction path, not a truncated comparison. No mutation.
HeaderSample weak{SCRYPT, 1075};
weak.target.limb[0] = 1; // value == 1, all high limbs zero
weak.pow_hash = h;
EXPECT_EQ(hc.validate_and_append(weak), IngestResult::REJECTED);
EXPECT_EQ(hc.size(), 1u); // unchanged
}