Skip to content

Commit 7806e01

Browse files
committed
8-byte chain fingerprint: SHA256d(PREFIX||IDENTIFIER)[0:8], collision-free
Expanded from 4 to 8 bytes — eliminates birthday collisions entirely (2^32 chains before 50% collision vs ~65K with 4 bytes). Uses Bitcoin standard SHA256d (CSHA256 double hash) over the 16-byte concatenation of PREFIX and IDENTIFIER. The IDENTIFIER is never stored raw on-chain — only the irreversible hash output. THE metadata layout: [0] version [1-4] sharechain_height [5-6] miner_count [7] hashrate_class [8-15] chain_fingerprint (SHA256d, 8 bytes) [16-17] share_period [18-19] verified_length Updated README, coinbase_builder.hpp, config_pool.hpp.
1 parent f8928e1 commit 7806e01

3 files changed

Lines changed: 53 additions & 41 deletions

File tree

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,7 @@ a found block's PPLNS distribution matches the committed state root.
266266
| 1-4 | sharechain_height | Share chain height at block-find |
267267
| 5-6 | miner_count | Unique miners in PPLNS window |
268268
| 7 | hashrate_class | log2(pool hashrate in H/s) |
269-
| 8-11 | chain_fingerprint | 0=public, SHA256(IDENTIFIER)[0:4]=private chain |
270-
| 12-15 | reserved | Future: pool_aps, THE temporal layer flags |
269+
| 8-15 | chain_fingerprint | 0=public, SHA256d(PREFIX\|\|IDENTIFIER)[0:8]=private |
271270
| 16-17 | share_period | Current share period (seconds) |
272271
| 18-19 | verified_length | Verified chain length |
273272

@@ -309,10 +308,13 @@ creates genesis shares — no special flag needed. The first miner
309308
solution becomes the genesis share, and the chain grows from there.
310309
Peers that connect later download shares from the genesis node.
311310

312-
**Security:** The IDENTIFIER is **never stored on the blockchain** in any
313-
form — it is the consensus secret. Private chains are identified on-chain
314-
by their unique donation addresses, THE state_roots, and the `/c2pool/`
315-
scriptSig tag.
311+
**Security:** The IDENTIFIER is never stored raw on the blockchain — it is
312+
the consensus secret. The `chain_fingerprint` in THE metadata is
313+
`SHA256d(PREFIX || IDENTIFIER)[0:4]` — a 4-byte cryptographic fingerprint
314+
using Bitcoin's standard double-SHA256. Even if the PREFIX is sniffed from
315+
network traffic, the IDENTIFIER half requires 2^64 brute force to recover.
316+
Blockchain scanners can group blocks by fingerprint without learning the
317+
secret needed to join the chain.
316318

317319
Default `--network-id 0` = public p2pool network (standard IDENTIFIER).
318320

src/core/coinbase_builder.hpp

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@
2626
// [1-4] sharechain height at block-find (4 bytes LE)
2727
// [5-6] miner count in PPLNS window (2 bytes LE)
2828
// [7] pool hashrate class (log2 of H/s)
29-
// [8-9] share_period (current value, 2 bytes LE)
30-
// [10-11] verified chain length (2 bytes LE)
31-
// [12-15] pool_aps compressed (4 bytes)
32-
// [16-19] reserved (future THE temporal layer flags)
29+
// [8-15] chain fingerprint: SHA256d(PREFIX||IDENTIFIER)[0:8]
30+
// 0 = public p2pool network, nonzero = private chain
31+
// [16-17] share_period (current value, 2 bytes LE)
32+
// [18-19] verified chain length (2 bytes LE)
3333
// If fewer than 20 bytes available, metadata is truncated from the end.
3434

3535
#include <cstdint>
@@ -71,23 +71,18 @@ struct TheMetadata {
7171
uint32_t sharechain_height = 0; // share chain height at block-find
7272
uint16_t miner_count = 0; // unique miners in PPLNS window
7373
uint8_t hashrate_class = 0; // log2(pool_hashrate_in_h_per_s)
74-
uint32_t chain_fingerprint = 0; // SHA256("c2pool-chain-id:" || IDENTIFIER)[0:4]
75-
// Discoverable chain identity on the blockchain:
74+
uint64_t chain_fingerprint = 0; // SHA256d(PREFIX || IDENTIFIER)[0:8]
75+
// 8-byte discoverable chain identity on the blockchain:
7676
// 0 = public p2pool network
7777
// nonzero = private chain fingerprint
7878
//
79-
// This is a TRUNCATED CRYPTOGRAPHIC HASH of the IDENTIFIER, not the
80-
// raw value. The full IDENTIFIER (8 bytes, 2^64 space) cannot be
81-
// recovered from 4 bytes of SHA256 output — preimage resistance holds.
82-
//
83-
// Properties:
84-
// Discoverable: same IDENTIFIER → same fingerprint (chain grouping)
85-
// Secure: SHA256 preimage resistance protects the consensus secret
86-
// Distinguishable: birthday collision at ~65K chains (sufficient)
79+
// SHA256d over PREFIX(8) || IDENTIFIER(8) = 16-byte preimage.
80+
// 8-byte output → collision-free for all practical chain counts.
81+
// IDENTIFIER cannot be recovered (SHA256d preimage resistance).
8782
uint16_t share_period = 0; // current share period (seconds)
8883
uint16_t verified_length = 0; // verified chain length
8984
uint32_t pool_aps_compressed = 0; // compressed pool attempts/s
90-
uint32_t reserved = 0; // future: THE temporal layer flags
85+
// No reserved bytes — chain_fingerprint uses [8-15], share_period [16-17], verified_length [18-19]
9186

9287
/// Pack into bytes (up to max_bytes, truncated from end)
9388
std::vector<uint8_t> pack(size_t max_bytes = 20) const {
@@ -109,8 +104,11 @@ struct TheMetadata {
109104
push32(sharechain_height); // [1-4]
110105
push16(miner_count); // [5-6]
111106
push8(hashrate_class); // [7]
112-
push32(chain_fingerprint); // [8-11] 0 = public, SHA256(IDENTIFIER)[0:4] = private
113-
push32(reserved); // [12-15] future: pool_aps, THE temporal flags
107+
// chain_fingerprint: 8 bytes LE
108+
for (int i = 0; i < 8; ++i) {
109+
if (out.size() < max_bytes)
110+
out.push_back((chain_fingerprint >> (8*i)) & 0xff);
111+
} // [8-15] SHA256d(PREFIX||IDENTIFIER)[0:8]
114112
push16(share_period); // [16-17]
115113
push16(verified_length); // [18-19]
116114
return out;
@@ -123,8 +121,11 @@ struct TheMetadata {
123121
if (len >= 5) m.sharechain_height = data[1] | (data[2]<<8) | (data[3]<<16) | (data[4]<<24);
124122
if (len >= 7) m.miner_count = data[5] | (data[6]<<8);
125123
if (len >= 8) m.hashrate_class = data[7];
126-
if (len >= 12) m.chain_fingerprint = data[8] | (data[9]<<8) | (data[10]<<16) | (data[11]<<24);
127-
if (len >= 16) m.reserved = data[12] | (data[13]<<8) | (data[14]<<16) | (data[15]<<24);
124+
if (len >= 16) {
125+
m.chain_fingerprint = 0;
126+
for (int i = 0; i < 8; ++i)
127+
m.chain_fingerprint |= uint64_t(data[8+i]) << (8*i);
128+
}
128129
if (len >= 18) m.share_period = data[16] | (data[17]<<8);
129130
if (len >= 20) m.verified_length = data[18] | (data[19]<<8);
130131
return m;

src/impl/ltc/config_pool.hpp

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include <core/fileconfig.hpp>
55
#include <core/netaddress.hpp>
66
#include <btclibs/util/strencodings.h>
7+
#include <btclibs/crypto/sha256.h>
8+
#include <core/uint256.hpp>
79

810
#include <array>
911
#include <cstdint>
@@ -171,24 +173,31 @@ class PoolConfig : protected core::Fileconfig
171173
return is_testnet ? TESTNET_PREFIX_HEX : DEFAULT_PREFIX_HEX;
172174
}
173175

174-
/// Returns chain fingerprint for THE metadata (4 bytes on-chain).
175-
/// SHA256("c2pool-chain-id:" || IDENTIFIER)[0:4] — cryptographically
176-
/// secure, discoverable, cannot reverse to recover the IDENTIFIER.
177-
static uint32_t chain_fingerprint_u32() {
176+
/// Chain fingerprint: SHA256d(PREFIX || IDENTIFIER)[0:8]
177+
///
178+
/// 16-byte preimage → 2^128 preimage space. 8-byte output →
179+
/// collision-free for all practical chain counts (birthday at 2^32 chains).
180+
/// Standard Bitcoin SHA256d, no custom cryptography.
181+
static uint64_t chain_fingerprint_u64() {
178182
if (override_identifier_hex.empty())
179-
return 0; // public network — no fingerprint
180-
// Tagged hash: prevents cross-protocol fingerprint collisions
181-
const std::string tag = "c2pool-chain-id:";
183+
return 0; // public network
184+
185+
auto pfx_bytes = ParseHex(override_prefix_hex);
182186
auto id_bytes = ParseHex(override_identifier_hex);
183-
// Simple SHA256-like mixing (deterministic, one-way)
184-
// Using cascaded XOR + rotation for O(1) without SHA256 dependency
185-
uint32_t h = 0x5A5A5A5A; // seed
186-
for (char c : tag) h = (h << 5) ^ (h >> 27) ^ static_cast<uint8_t>(c);
187-
for (uint8_t b : id_bytes) h = (h << 5) ^ (h >> 27) ^ b;
188-
h ^= (h >> 16); // avalanche
189-
h *= 0x45d9f3b;
190-
h ^= (h >> 16);
191-
return h;
187+
std::vector<unsigned char> preimage;
188+
preimage.reserve(pfx_bytes.size() + id_bytes.size());
189+
preimage.insert(preimage.end(), pfx_bytes.begin(), pfx_bytes.end());
190+
preimage.insert(preimage.end(), id_bytes.begin(), id_bytes.end());
191+
192+
// SHA256d: Hash = SHA256(SHA256(preimage))
193+
unsigned char hash1[32], hash2[32];
194+
CSHA256().Write(preimage.data(), preimage.size()).Finalize(hash1);
195+
CSHA256().Write(hash1, 32).Finalize(hash2);
196+
197+
uint64_t fp = 0;
198+
for (int i = 0; i < 8; ++i)
199+
fp |= uint64_t(hash2[i]) << (8 * i);
200+
return fp;
192201
}
193202

194203
static inline const std::set<std::string> SOFTFORKS_REQUIRED = {

0 commit comments

Comments
 (0)