Skip to content

Commit 4286f43

Browse files
committed
Merge #8173: Use SipHash for node eviction (cont'd)
eebc232 test: Add more test vectors for siphash (Wladimir J. van der Laan) 8884830 Use C++11 thread-safe static initializers (Pieter Wuille) c31b24f Use 64-bit SipHash of netgroups in eviction (Pieter Wuille) 9bf156b Support SipHash with arbitrary byte writes (Pieter Wuille) 053930f Avoid recalculating vchKeyedNetGroup in eviction logic. (Patrick Strateman)
2 parents cd0c513 + eebc232 commit 4286f43

File tree

8 files changed

+152
-62
lines changed

8 files changed

+152
-62
lines changed

src/coins.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,7 @@ void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
5656
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
5757
CCoinsViewCursor *CCoinsViewBacked::Cursor() const { return base->Cursor(); }
5858

59-
SaltedTxidHasher::SaltedTxidHasher()
60-
{
61-
GetRandBytes((unsigned char*)&k0, sizeof(k0));
62-
GetRandBytes((unsigned char*)&k1, sizeof(k1));
63-
}
59+
SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {}
6460

6561
CCoinsViewCache::CCoinsViewCache(CCoinsView *baseIn) : CCoinsViewBacked(baseIn), hasModifier(false), cachedCoinsUsage(0) { }
6662

src/coins.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ class SaltedTxidHasher
269269
{
270270
private:
271271
/** Salt */
272-
uint64_t k0, k1;
272+
const uint64_t k0, k1;
273273

274274
public:
275275
SaltedTxidHasher();

src/hash.cpp

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,15 @@ CSipHasher::CSipHasher(uint64_t k0, uint64_t k1)
100100
v[2] = 0x6c7967656e657261ULL ^ k0;
101101
v[3] = 0x7465646279746573ULL ^ k1;
102102
count = 0;
103+
tmp = 0;
103104
}
104105

105106
CSipHasher& CSipHasher::Write(uint64_t data)
106107
{
107108
uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3];
108109

110+
assert(count % 8 == 0);
111+
109112
v3 ^= data;
110113
SIPROUND;
111114
SIPROUND;
@@ -116,18 +119,48 @@ CSipHasher& CSipHasher::Write(uint64_t data)
116119
v[2] = v2;
117120
v[3] = v3;
118121

119-
count++;
122+
count += 8;
123+
return *this;
124+
}
125+
126+
CSipHasher& CSipHasher::Write(const unsigned char* data, size_t size)
127+
{
128+
uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3];
129+
uint64_t t = tmp;
130+
int c = count;
131+
132+
while (size--) {
133+
t |= ((uint64_t)(*(data++))) << (8 * (c % 8));
134+
c++;
135+
if ((c & 7) == 0) {
136+
v3 ^= t;
137+
SIPROUND;
138+
SIPROUND;
139+
v0 ^= t;
140+
t = 0;
141+
}
142+
}
143+
144+
v[0] = v0;
145+
v[1] = v1;
146+
v[2] = v2;
147+
v[3] = v3;
148+
count = c;
149+
tmp = t;
150+
120151
return *this;
121152
}
122153

123154
uint64_t CSipHasher::Finalize() const
124155
{
125156
uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3];
126157

127-
v3 ^= ((uint64_t)count) << 59;
158+
uint64_t t = tmp | (((uint64_t)count) << 56);
159+
160+
v3 ^= t;
128161
SIPROUND;
129162
SIPROUND;
130-
v0 ^= ((uint64_t)count) << 59;
163+
v0 ^= t;
131164
v2 ^= 0xFF;
132165
SIPROUND;
133166
SIPROUND;

src/hash.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,19 +171,38 @@ unsigned int MurmurHash3(unsigned int nHashSeed, const std::vector<unsigned char
171171

172172
void BIP32Hash(const ChainCode &chainCode, unsigned int nChild, unsigned char header, const unsigned char data[32], unsigned char output[64]);
173173

174-
/** SipHash-2-4, using a uint64_t-based (rather than byte-based) interface */
174+
/** SipHash-2-4 */
175175
class CSipHasher
176176
{
177177
private:
178178
uint64_t v[4];
179+
uint64_t tmp;
179180
int count;
180181

181182
public:
183+
/** Construct a SipHash calculator initialized with 128-bit key (k0, k1) */
182184
CSipHasher(uint64_t k0, uint64_t k1);
185+
/** Hash a 64-bit integer worth of data
186+
* It is treated as if this was the little-endian interpretation of 8 bytes.
187+
* This function can only be used when a multiple of 8 bytes have been written so far.
188+
*/
183189
CSipHasher& Write(uint64_t data);
190+
/** Hash arbitrary bytes. */
191+
CSipHasher& Write(const unsigned char* data, size_t size);
192+
/** Compute the 64-bit SipHash-2-4 of the data written so far. The object remains untouched. */
184193
uint64_t Finalize() const;
185194
};
186195

196+
/** Optimized SipHash-2-4 implementation for uint256.
197+
*
198+
* It is identical to:
199+
* SipHasher(k0, k1)
200+
* .Write(val.GetUint64(0))
201+
* .Write(val.GetUint64(1))
202+
* .Write(val.GetUint64(2))
203+
* .Write(val.GetUint64(3))
204+
* .Finalize()
205+
*/
187206
uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val);
188207

189208
#endif // BITCOIN_HASH_H

src/main.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4783,11 +4783,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
47834783
LOCK(cs_vNodes);
47844784
// Use deterministic randomness to send to the same nodes for 24 hours
47854785
// at a time so the addrKnowns of the chosen nodes prevent repeats
4786-
static uint64_t salt0 = 0, salt1 = 0;
4787-
while (salt0 == 0 && salt1 == 0) {
4788-
GetRandBytes((unsigned char*)&salt0, sizeof(salt0));
4789-
GetRandBytes((unsigned char*)&salt1, sizeof(salt1));
4790-
}
4786+
static const uint64_t salt0 = GetRand(std::numeric_limits<uint64_t>::max());
4787+
static const uint64_t salt1 = GetRand(std::numeric_limits<uint64_t>::max());
47914788
uint64_t hashAddr = addr.GetHash();
47924789
multimap<uint64_t, CNode*> mapMix;
47934790
const CSipHasher hasher = CSipHasher(salt0, salt1).Write(hashAddr << 32).Write((GetTime() + hashAddr) / (24*60*60));

src/net.cpp

Lines changed: 26 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "clientversion.h"
1515
#include "consensus/consensus.h"
1616
#include "crypto/common.h"
17+
#include "crypto/sha256.h"
1718
#include "hash.h"
1819
#include "primitives/transaction.h"
1920
#include "scheduler.h"
@@ -838,6 +839,7 @@ struct NodeEvictionCandidate
838839
int64_t nTimeConnected;
839840
int64_t nMinPingUsecTime;
840841
CAddress addr;
842+
uint64_t nKeyedNetGroup;
841843
};
842844

843845
static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
@@ -850,36 +852,8 @@ static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a, cons
850852
return a.nTimeConnected > b.nTimeConnected;
851853
}
852854

853-
class CompareNetGroupKeyed
854-
{
855-
std::vector<unsigned char> vchSecretKey;
856-
public:
857-
CompareNetGroupKeyed()
858-
{
859-
vchSecretKey.resize(32, 0);
860-
GetRandBytes(vchSecretKey.data(), vchSecretKey.size());
861-
}
862-
863-
bool operator()(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b)
864-
{
865-
std::vector<unsigned char> vchGroupA, vchGroupB;
866-
CSHA256 hashA, hashB;
867-
std::vector<unsigned char> vchA(32), vchB(32);
868-
869-
vchGroupA = a.addr.GetGroup();
870-
vchGroupB = b.addr.GetGroup();
871-
872-
hashA.Write(begin_ptr(vchGroupA), vchGroupA.size());
873-
hashB.Write(begin_ptr(vchGroupB), vchGroupB.size());
874-
875-
hashA.Write(begin_ptr(vchSecretKey), vchSecretKey.size());
876-
hashB.Write(begin_ptr(vchSecretKey), vchSecretKey.size());
877-
878-
hashA.Finalize(begin_ptr(vchA));
879-
hashB.Finalize(begin_ptr(vchB));
880-
881-
return vchA < vchB;
882-
}
855+
static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a, const NodeEvictionCandidate &b) {
856+
return a.nKeyedNetGroup < b.nKeyedNetGroup;
883857
};
884858

885859
/** Try to find a connection to evict when the node is full.
@@ -902,7 +876,7 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) {
902876
continue;
903877
if (node->fDisconnect)
904878
continue;
905-
NodeEvictionCandidate candidate = {node->id, node->nTimeConnected, node->nMinPingUsecTime, node->addr};
879+
NodeEvictionCandidate candidate = {node->id, node->nTimeConnected, node->nMinPingUsecTime, node->addr, node->nKeyedNetGroup};
906880
vEvictionCandidates.push_back(candidate);
907881
}
908882
}
@@ -912,9 +886,8 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) {
912886
// Protect connections with certain characteristics
913887

914888
// Deterministically select 4 peers to protect by netgroup.
915-
// An attacker cannot predict which netgroups will be protected.
916-
static CompareNetGroupKeyed comparerNetGroupKeyed;
917-
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), comparerNetGroupKeyed);
889+
// An attacker cannot predict which netgroups will be protected
890+
std::sort(vEvictionCandidates.begin(), vEvictionCandidates.end(), CompareNetGroupKeyed);
918891
vEvictionCandidates.erase(vEvictionCandidates.end() - std::min(4, static_cast<int>(vEvictionCandidates.size())), vEvictionCandidates.end());
919892

920893
if (vEvictionCandidates.empty()) return false;
@@ -935,24 +908,24 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) {
935908

936909
// Identify the network group with the most connections and youngest member.
937910
// (vEvictionCandidates is already sorted by reverse connect time)
938-
std::vector<unsigned char> naMostConnections;
911+
uint64_t naMostConnections;
939912
unsigned int nMostConnections = 0;
940913
int64_t nMostConnectionsTime = 0;
941-
std::map<std::vector<unsigned char>, std::vector<NodeEvictionCandidate> > mapAddrCounts;
914+
std::map<uint64_t, std::vector<NodeEvictionCandidate> > mapAddrCounts;
942915
BOOST_FOREACH(const NodeEvictionCandidate &node, vEvictionCandidates) {
943-
mapAddrCounts[node.addr.GetGroup()].push_back(node);
944-
int64_t grouptime = mapAddrCounts[node.addr.GetGroup()][0].nTimeConnected;
945-
size_t groupsize = mapAddrCounts[node.addr.GetGroup()].size();
916+
mapAddrCounts[node.nKeyedNetGroup].push_back(node);
917+
int64_t grouptime = mapAddrCounts[node.nKeyedNetGroup][0].nTimeConnected;
918+
size_t groupsize = mapAddrCounts[node.nKeyedNetGroup].size();
946919

947920
if (groupsize > nMostConnections || (groupsize == nMostConnections && grouptime > nMostConnectionsTime)) {
948921
nMostConnections = groupsize;
949922
nMostConnectionsTime = grouptime;
950-
naMostConnections = node.addr.GetGroup();
923+
naMostConnections = node.nKeyedNetGroup;
951924
}
952925
}
953926

954927
// Reduce to the network group with the most connections
955-
vEvictionCandidates = mapAddrCounts[naMostConnections];
928+
vEvictionCandidates = std::move(mapAddrCounts[naMostConnections]);
956929

957930
// Do not disconnect peers if there is only one unprotected connection from their network group.
958931
// This step excessively favors netgroup diversity, and should be removed once more protective criteria are established.
@@ -2346,6 +2319,8 @@ unsigned int SendBufferSize() { return 1000*GetArg("-maxsendbuffer", DEFAULT_MAX
23462319

23472320
CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNameIn, bool fInboundIn) :
23482321
ssSend(SER_NETWORK, INIT_PROTO_VERSION),
2322+
addr(addrIn),
2323+
nKeyedNetGroup(CalculateKeyedNetGroup(addrIn)),
23492324
addrKnown(5000, 0.001),
23502325
filterInventoryKnown(50000, 0.000001)
23512326
{
@@ -2358,7 +2333,6 @@ CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNa
23582333
nRecvBytes = 0;
23592334
nTimeConnected = GetTime();
23602335
nTimeOffset = 0;
2361-
addr = addrIn;
23622336
addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn;
23632337
nVersion = 0;
23642338
strSubVer = "";
@@ -2625,3 +2599,13 @@ bool CBanDB::Read(banmap_t& banSet)
26252599
int64_t PoissonNextSend(int64_t nNow, int average_interval_seconds) {
26262600
return nNow + (int64_t)(log1p(GetRand(1ULL << 48) * -0.0000000000000035527136788 /* -1/2^48 */) * average_interval_seconds * -1000000.0 + 0.5);
26272601
}
2602+
2603+
/* static */ uint64_t CNode::CalculateKeyedNetGroup(const CAddress& ad)
2604+
{
2605+
static const uint64_t k0 = GetRand(std::numeric_limits<uint64_t>::max());
2606+
static const uint64_t k1 = GetRand(std::numeric_limits<uint64_t>::max());
2607+
2608+
std::vector<unsigned char> vchNetGroup(ad.GetGroup());
2609+
2610+
return CSipHasher(k0, k1).Write(&vchNetGroup[0], vchNetGroup.size()).Finalize();
2611+
}

src/net.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ class CNode
335335
int64_t nLastRecv;
336336
int64_t nTimeConnected;
337337
int64_t nTimeOffset;
338-
CAddress addr;
338+
const CAddress addr;
339339
std::string addrName;
340340
CService addrLocal;
341341
int nVersion;
@@ -362,6 +362,8 @@ class CNode
362362
CBloomFilter* pfilter;
363363
int nRefCount;
364364
NodeId id;
365+
366+
const uint64_t nKeyedNetGroup;
365367
protected:
366368

367369
// Denial-of-service detection/prevention
@@ -450,6 +452,8 @@ class CNode
450452
CNode(const CNode&);
451453
void operator=(const CNode&);
452454

455+
static uint64_t CalculateKeyedNetGroup(const CAddress& ad);
456+
453457
public:
454458

455459
NodeId GetId() const {

src/test/hash_tests.cpp

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,24 +47,81 @@ BOOST_AUTO_TEST_CASE(murmurhash3)
4747
#undef T
4848
}
4949

50+
/*
51+
SipHash-2-4 output with
52+
k = 00 01 02 ...
53+
and
54+
in = (empty string)
55+
in = 00 (1 byte)
56+
in = 00 01 (2 bytes)
57+
in = 00 01 02 (3 bytes)
58+
...
59+
in = 00 01 02 ... 3e (63 bytes)
60+
61+
from: https://131002.net/siphash/siphash24.c
62+
*/
63+
uint64_t siphash_4_2_testvec[] = {
64+
0x726fdb47dd0e0e31, 0x74f839c593dc67fd, 0x0d6c8009d9a94f5a, 0x85676696d7fb7e2d,
65+
0xcf2794e0277187b7, 0x18765564cd99a68d, 0xcbc9466e58fee3ce, 0xab0200f58b01d137,
66+
0x93f5f5799a932462, 0x9e0082df0ba9e4b0, 0x7a5dbbc594ddb9f3, 0xf4b32f46226bada7,
67+
0x751e8fbc860ee5fb, 0x14ea5627c0843d90, 0xf723ca908e7af2ee, 0xa129ca6149be45e5,
68+
0x3f2acc7f57c29bdb, 0x699ae9f52cbe4794, 0x4bc1b3f0968dd39c, 0xbb6dc91da77961bd,
69+
0xbed65cf21aa2ee98, 0xd0f2cbb02e3b67c7, 0x93536795e3a33e88, 0xa80c038ccd5ccec8,
70+
0xb8ad50c6f649af94, 0xbce192de8a85b8ea, 0x17d835b85bbb15f3, 0x2f2e6163076bcfad,
71+
0xde4daaaca71dc9a5, 0xa6a2506687956571, 0xad87a3535c49ef28, 0x32d892fad841c342,
72+
0x7127512f72f27cce, 0xa7f32346f95978e3, 0x12e0b01abb051238, 0x15e034d40fa197ae,
73+
0x314dffbe0815a3b4, 0x027990f029623981, 0xcadcd4e59ef40c4d, 0x9abfd8766a33735c,
74+
0x0e3ea96b5304a7d0, 0xad0c42d6fc585992, 0x187306c89bc215a9, 0xd4a60abcf3792b95,
75+
0xf935451de4f21df2, 0xa9538f0419755787, 0xdb9acddff56ca510, 0xd06c98cd5c0975eb,
76+
0xe612a3cb9ecba951, 0xc766e62cfcadaf96, 0xee64435a9752fe72, 0xa192d576b245165a,
77+
0x0a8787bf8ecb74b2, 0x81b3e73d20b49b6f, 0x7fa8220ba3b2ecea, 0x245731c13ca42499,
78+
0xb78dbfaf3a8d83bd, 0xea1ad565322a1a0b, 0x60e61c23a3795013, 0x6606d7e446282b93,
79+
0x6ca4ecb15c5f91e1, 0x9f626da15c9625f3, 0xe51b38608ef25f57, 0x958a324ceb064572
80+
};
81+
5082
BOOST_AUTO_TEST_CASE(siphash)
5183
{
5284
CSipHasher hasher(0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL);
5385
BOOST_CHECK_EQUAL(hasher.Finalize(), 0x726fdb47dd0e0e31ull);
54-
hasher.Write(0x0706050403020100ULL);
86+
static const unsigned char t0[1] = {0};
87+
hasher.Write(t0, 1);
88+
BOOST_CHECK_EQUAL(hasher.Finalize(), 0x74f839c593dc67fdull);
89+
static const unsigned char t1[7] = {1,2,3,4,5,6,7};
90+
hasher.Write(t1, 7);
5591
BOOST_CHECK_EQUAL(hasher.Finalize(), 0x93f5f5799a932462ull);
5692
hasher.Write(0x0F0E0D0C0B0A0908ULL);
5793
BOOST_CHECK_EQUAL(hasher.Finalize(), 0x3f2acc7f57c29bdbull);
58-
hasher.Write(0x1716151413121110ULL);
59-
BOOST_CHECK_EQUAL(hasher.Finalize(), 0xb8ad50c6f649af94ull);
60-
hasher.Write(0x1F1E1D1C1B1A1918ULL);
94+
static const unsigned char t2[2] = {16,17};
95+
hasher.Write(t2, 2);
96+
BOOST_CHECK_EQUAL(hasher.Finalize(), 0x4bc1b3f0968dd39cull);
97+
static const unsigned char t3[9] = {18,19,20,21,22,23,24,25,26};
98+
hasher.Write(t3, 9);
99+
BOOST_CHECK_EQUAL(hasher.Finalize(), 0x2f2e6163076bcfadull);
100+
static const unsigned char t4[5] = {27,28,29,30,31};
101+
hasher.Write(t4, 5);
61102
BOOST_CHECK_EQUAL(hasher.Finalize(), 0x7127512f72f27cceull);
62103
hasher.Write(0x2726252423222120ULL);
63104
BOOST_CHECK_EQUAL(hasher.Finalize(), 0x0e3ea96b5304a7d0ull);
64105
hasher.Write(0x2F2E2D2C2B2A2928ULL);
65106
BOOST_CHECK_EQUAL(hasher.Finalize(), 0xe612a3cb9ecba951ull);
66107

67108
BOOST_CHECK_EQUAL(SipHashUint256(0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL, uint256S("1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100")), 0x7127512f72f27cceull);
109+
110+
// Check test vectors from spec, one byte at a time
111+
CSipHasher hasher2(0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL);
112+
for (uint8_t x=0; x<ARRAYLEN(siphash_4_2_testvec); ++x)
113+
{
114+
BOOST_CHECK_EQUAL(hasher2.Finalize(), siphash_4_2_testvec[x]);
115+
hasher2.Write(&x, 1);
116+
}
117+
// Check test vectors from spec, eight bytes at a time
118+
CSipHasher hasher3(0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL);
119+
for (uint8_t x=0; x<ARRAYLEN(siphash_4_2_testvec); x+=8)
120+
{
121+
BOOST_CHECK_EQUAL(hasher3.Finalize(), siphash_4_2_testvec[x]);
122+
hasher3.Write(uint64_t(x)|(uint64_t(x+1)<<8)|(uint64_t(x+2)<<16)|(uint64_t(x+3)<<24)|
123+
(uint64_t(x+4)<<32)|(uint64_t(x+5)<<40)|(uint64_t(x+6)<<48)|(uint64_t(x+7)<<56));
124+
}
68125
}
69126

70127
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)