Skip to content

Commit 314ae5f

Browse files
committed
dgb(#82 4b/4c): scrypt_pow.hpp digest CALL SSOT + satisfaction-gate KAT
Land the foundational slice of embedded real work-gen: coin/scrypt_pow.hpp wraps btclibs scrypt_1024_1_1_256(80-byte header) and decodes the 32-byte digest via the existing u256::from_le_bytes (dgb_arith256.hpp), closing the documented "the scrypt CALL itself lands at the ingest boundary in a following slice" TODO. Single SSOT so the nonce grinder (next slice) and the header_chain ingest boundary can never disagree on PoW byte order. V36 Scrypt-only: this is the ONLY PoW digest DGB validates; the other four algos are accept-by-continuity and never reach here. dgb_scrypt_pow_test (5/5): pins the digest bytes over a fixed header (vector captured from btclibs scrypt itself, the DGB-Scrypt algo SSOT) and proves the little-endian decode compares correctly against SetCompact-shaped targets the way header_chain.hpp satisfaction gate does (pow_hash <= target, MSB-first). Reuses header_chain_test scrypt TU set (real scrypt, header-only u256, no core / no OBJECT lib). Registered in CMake + both build.yml --target allowlists. End-to-end agreement with DigiByte Core is proven by the live node-B ACCEPT integration test (4b/4c tip-extension bar), not this unit KAT.
1 parent 68f1028 commit 314ae5f

4 files changed

Lines changed: 143 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ jobs:
8484
dgb_gentx_coinbase_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
8585
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_coinbase_value_parity_test \
8686
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
87-
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test \
87+
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test \
8888
v37_test \
8989
-j$(nproc)
9090
@@ -217,7 +217,7 @@ jobs:
217217
dgb_gentx_coinbase_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test dgb_gentx_share_path_test dgb_other_tx_resolver_test \
218218
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_coinbase_value_parity_test \
219219
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
220-
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test \
220+
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test \
221221
test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
222222
v37_test \
223223
-j$(nproc)

src/impl/dgb/coin/scrypt_pow.hpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#pragma once
2+
// ---------------------------------------------------------------------------
3+
// DGB-Scrypt proof-of-work digest CALL (M3 §7b / Stage 4b-4c work-gen).
4+
//
5+
// The satisfaction gate in coin/header_chain.hpp compares HeaderSample::pow_hash
6+
// (a coin/dgb_arith256.hpp u256) against the SetCompact target -- hash <= target.
7+
// dgb_arith256.hpp::u256::from_le_bytes already documents the decode convention
8+
// (the scrypt output is read little-endian, mirroring bitcoin UintToArith256),
9+
// and ends: "the scrypt CALL itself lands at the ingest boundary in a following
10+
// slice." THIS header is that call: the single place the DGB-Scrypt algo hash is
11+
// computed, so the embedded work-gen (nonce grinder) and the ingest boundary
12+
// share ONE digest SSOT and can never disagree on byte order.
13+
//
14+
// V36 is Scrypt-ONLY (project_v36_dgb_scrypt_only): this is the ONLY PoW digest
15+
// DGB validates. The other four DGB algos (SHA256d/Skein/Qubit/Odocrypt) are
16+
// accept-by-continuity and never reach this function.
17+
//
18+
// scrypt_1024_1_1_256 is the canonical pooler/ArtForz Scrypt(N=1024,r=1,p=1)
19+
// from btclibs (src/btclibs/crypto/scrypt.*), the SAME routine DigiByte Core /
20+
// Litecoin Core use for the Scrypt algo. It links transitively here via
21+
// core -> btclibs (target_link_libraries(core PUBLIC ... btclibs)).
22+
// ---------------------------------------------------------------------------
23+
24+
#include <array>
25+
#include <cstdint>
26+
27+
#include <btclibs/crypto/scrypt.h> // scrypt_1024_1_1_256
28+
#include <impl/dgb/coin/dgb_arith256.hpp> // dgb::coin::u256
29+
30+
namespace dgb::coin {
31+
32+
// scrypt_1024_1_1_256 over the 80-byte serialized block header -> pow_hash u256.
33+
// The 32-byte digest is decoded little-endian (from_le_bytes) so the result
34+
// drops straight into the header_chain satisfaction gate with no reshape.
35+
inline u256 scrypt_pow_hash(const unsigned char header80[80]) {
36+
char digest[32];
37+
scrypt_1024_1_1_256(reinterpret_cast<const char*>(header80), digest);
38+
return u256::from_le_bytes(reinterpret_cast<const unsigned char*>(digest));
39+
}
40+
41+
// std::array convenience overload (the form the reconstructed-header builder and
42+
// the nonce grinder carry the 80 header bytes in).
43+
inline u256 scrypt_pow_hash(const std::array<unsigned char, 80>& header80) {
44+
return scrypt_pow_hash(header80.data());
45+
}
46+
47+
} // namespace dgb::coin

src/impl/dgb/test/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,4 +342,17 @@ if (BUILD_TESTING AND GTest_FOUND)
342342
target_link_libraries(header_chain_test PRIVATE
343343
GTest::gtest_main GTest::gtest nlohmann_json::nlohmann_json)
344344
gtest_add_tests(header_chain_test "" AUTO)
345+
346+
# --- Stage 4b/4c: DGB-Scrypt PoW digest CALL (coin/scrypt_pow.hpp) --------
347+
# Pins scrypt_pow_hash = scrypt_1024_1_1_256(80-byte header) decoded via
348+
# u256::from_le_bytes, plus the satisfaction-gate byte order (pow_hash <=
349+
# target) header_chain.hpp compares. Reuses the SAME _dgb_scrypt_tus set as
350+
# header_chain_test (real scrypt, not synthetic) -- header-only u256, no core
351+
# / no dgb OBJECT lib, the standalone-guard discipline dgb_arith256.hpp keeps.
352+
# Same target name, so it stays in BOTH build.yml --target allowlists.
353+
add_executable(dgb_scrypt_pow_test scrypt_pow_test.cpp ${_dgb_scrypt_tus})
354+
target_include_directories(dgb_scrypt_pow_test PRIVATE ${CMAKE_SOURCE_DIR}/src/btclibs)
355+
target_link_libraries(dgb_scrypt_pow_test PRIVATE
356+
GTest::gtest_main GTest::gtest)
357+
gtest_add_tests(dgb_scrypt_pow_test "" AUTO)
345358
endif()
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// DGB-Scrypt PoW digest CALL guard (coin/scrypt_pow.hpp) -- Stage 4b/4c.
2+
//
3+
// Pins (1) the scrypt_1024_1_1_256 digest bytes over a fixed 80-byte header and
4+
// (2) that the little-endian u256 decode compares correctly against SetCompact-
5+
// shaped targets the way the coin/header_chain.hpp satisfaction gate does
6+
// (pow_hash <= target == valid PoW). The byte order proven here is exactly what
7+
// the embedded nonce grinder must satisfy for node B to ACCEPT a reconstructed
8+
// block, so this is the unit floor under the live tip-extension test.
9+
//
10+
// The digest vector is pinned from btclibs scrypt_1024_1_1_256 itself (the DGB-
11+
// Scrypt algo SSOT, same routine as DigiByte Core); end-to-end agreement with
12+
// DigiByte Core is proven by the live node-B ACCEPT integration test, not here.
13+
14+
#include <array>
15+
#include <gtest/gtest.h>
16+
17+
#include <impl/dgb/coin/scrypt_pow.hpp>
18+
19+
using dgb::coin::u256;
20+
using dgb::coin::scrypt_pow_hash;
21+
22+
namespace {
23+
24+
// Deterministic 80-byte header: byte i = (i*7+1) mod 251. Arbitrary content but
25+
// fixed; the conversion + comparison proof does not depend on header semantics.
26+
std::array<unsigned char, 80> fixed_header() {
27+
std::array<unsigned char, 80> h{};
28+
for (int i = 0; i < 80; ++i) h[i] = static_cast<unsigned char>((i * 7 + 1) % 251);
29+
return h;
30+
}
31+
32+
// scrypt_1024_1_1_256(fixed_header) little-endian u256 limbs (limb[0] = LSB),
33+
// captured from btclibs. digest LE-in-memory:
34+
// 632b7c4db1da77a9683731a4a7a97761a2f600f66a1af420919a3aa0d4869a6c
35+
constexpr uint64_t L0 = 0xa977dab14d7c2b63ULL;
36+
constexpr uint64_t L1 = 0x6177a9a7a4313768ULL;
37+
constexpr uint64_t L2 = 0x20f41a6af600f6a2ULL;
38+
constexpr uint64_t L3 = 0x6c9a86d4a03a9a91ULL;
39+
40+
u256 expected_pow() {
41+
u256 r;
42+
r.limb[0] = L0; r.limb[1] = L1; r.limb[2] = L2; r.limb[3] = L3;
43+
return r;
44+
}
45+
46+
// --- KAT: the digest CALL is byte-exact ------------------------------------
47+
TEST(DgbScryptPowKAT, DigestMatchesPinnedVector) {
48+
EXPECT_TRUE(scrypt_pow_hash(fixed_header()) == expected_pow());
49+
}
50+
51+
// The pointer overload and the std::array overload are the same SSOT.
52+
TEST(DgbScryptPowKAT, PointerAndArrayOverloadsAgree) {
53+
auto h = fixed_header();
54+
EXPECT_TRUE(scrypt_pow_hash(h.data()) == scrypt_pow_hash(h));
55+
}
56+
57+
// --- satisfaction-gate byte order (header_chain.hpp: pow_hash <= target) ----
58+
// A target one ULP above the digest in the MOST-significant limb accepts;
59+
// one ULP below rejects. This is the exact comparison header_chain runs, so it
60+
// proves from_le_bytes feeds the gate in the right (MSB-first compare) order.
61+
TEST(DgbScryptPowKAT, SatisfiesTargetJustAbove) {
62+
u256 pow = scrypt_pow_hash(fixed_header());
63+
u256 target_above = expected_pow();
64+
target_above.limb[3] = L3 + 1; // strictly greater
65+
EXPECT_FALSE(pow > target_above); // pow <= target -> valid PoW
66+
}
67+
68+
TEST(DgbScryptPowKAT, FailsTargetJustBelow) {
69+
u256 pow = scrypt_pow_hash(fixed_header());
70+
u256 target_below = expected_pow();
71+
target_below.limb[3] = L3 - 1; // strictly less
72+
EXPECT_TRUE(pow > target_below); // pow > target -> PoW failed (high-hash)
73+
}
74+
75+
// Equal target is satisfied (hash <= target is inclusive).
76+
TEST(DgbScryptPowKAT, SatisfiesEqualTarget) {
77+
u256 pow = scrypt_pow_hash(fixed_header());
78+
EXPECT_FALSE(pow > expected_pow());
79+
}
80+
81+
} // namespace

0 commit comments

Comments
 (0)