Gavin Andresen
12 years ago
21 changed files with 1315 additions and 62 deletions
@ -0,0 +1,156 @@
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2012 The Bitcoin developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#include <math.h> |
||||
#include <stdlib.h> |
||||
|
||||
#include "bloom.h" |
||||
#include "main.h" |
||||
#include "script.h" |
||||
|
||||
#define LN2SQUARED 0.4804530139182014246671025263266649717305529515945455 |
||||
#define LN2 0.6931471805599453094172321214581765680755001343602552 |
||||
|
||||
using namespace std; |
||||
|
||||
static const unsigned char bit_mask[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; |
||||
|
||||
CBloomFilter::CBloomFilter(unsigned int nElements, double nFPRate, unsigned int nTweakIn, unsigned char nFlagsIn) : |
||||
// The ideal size for a bloom filter with a given number of elements and false positive rate is:
|
||||
// - nElements * log(fp rate) / ln(2)^2
|
||||
// We ignore filter parameters which will create a bloom filter larger than the protocol limits
|
||||
vData(min((unsigned int)(-1 / LN2SQUARED * nElements * log(nFPRate)), MAX_BLOOM_FILTER_SIZE * 8) / 8), |
||||
// The ideal number of hash functions is filter size * ln(2) / number of elements
|
||||
// Again, we ignore filter parameters which will create a bloom filter with more hash functions than the protocol limits
|
||||
// See http://en.wikipedia.org/wiki/Bloom_filter for an explanation of these formulas
|
||||
nHashFuncs(min((unsigned int)(vData.size() * 8 / nElements * LN2), MAX_HASH_FUNCS)), |
||||
nTweak(nTweakIn), |
||||
nFlags(nFlagsIn) |
||||
{ |
||||
} |
||||
|
||||
inline unsigned int CBloomFilter::Hash(unsigned int nHashNum, const std::vector<unsigned char>& vDataToHash) const |
||||
{ |
||||
// 0xFBA4C795 chosen as it guarantees a reasonable bit difference between nHashNum values.
|
||||
return MurmurHash3(nHashNum * 0xFBA4C795 + nTweak, vDataToHash) % (vData.size() * 8); |
||||
} |
||||
|
||||
void CBloomFilter::insert(const vector<unsigned char>& vKey) |
||||
{ |
||||
for (unsigned int i = 0; i < nHashFuncs; i++) |
||||
{ |
||||
unsigned int nIndex = Hash(i, vKey); |
||||
// Sets bit nIndex of vData
|
||||
vData[nIndex >> 3] |= bit_mask[7 & nIndex]; |
||||
} |
||||
} |
||||
|
||||
void CBloomFilter::insert(const COutPoint& outpoint) |
||||
{ |
||||
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); |
||||
stream << outpoint; |
||||
vector<unsigned char> data(stream.begin(), stream.end()); |
||||
insert(data); |
||||
} |
||||
|
||||
void CBloomFilter::insert(const uint256& hash) |
||||
{ |
||||
vector<unsigned char> data(hash.begin(), hash.end()); |
||||
insert(data); |
||||
} |
||||
|
||||
bool CBloomFilter::contains(const vector<unsigned char>& vKey) const |
||||
{ |
||||
for (unsigned int i = 0; i < nHashFuncs; i++) |
||||
{ |
||||
unsigned int nIndex = Hash(i, vKey); |
||||
// Checks bit nIndex of vData
|
||||
if (!(vData[nIndex >> 3] & bit_mask[7 & nIndex])) |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
bool CBloomFilter::contains(const COutPoint& outpoint) const |
||||
{ |
||||
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); |
||||
stream << outpoint; |
||||
vector<unsigned char> data(stream.begin(), stream.end()); |
||||
return contains(data); |
||||
} |
||||
|
||||
bool CBloomFilter::contains(const uint256& hash) const |
||||
{ |
||||
vector<unsigned char> data(hash.begin(), hash.end()); |
||||
return contains(data); |
||||
} |
||||
|
||||
bool CBloomFilter::IsWithinSizeConstraints() const |
||||
{ |
||||
return vData.size() <= MAX_BLOOM_FILTER_SIZE && nHashFuncs <= MAX_HASH_FUNCS; |
||||
} |
||||
|
||||
bool CBloomFilter::IsRelevantAndUpdate(const CTransaction& tx, const uint256& hash) |
||||
{ |
||||
bool fFound = false; |
||||
// Match if the filter contains the hash of tx
|
||||
// for finding tx when they appear in a block
|
||||
if (contains(hash)) |
||||
fFound = true; |
||||
|
||||
for (unsigned int i = 0; i < tx.vout.size(); i++) |
||||
{ |
||||
const CTxOut& txout = tx.vout[i]; |
||||
// Match if the filter contains any arbitrary script data element in any scriptPubKey in tx
|
||||
// If this matches, also add the specific output that was matched.
|
||||
// This means clients don't have to update the filter themselves when a new relevant tx
|
||||
// is discovered in order to find spending transactions, which avoids round-tripping and race conditions.
|
||||
CScript::const_iterator pc = txout.scriptPubKey.begin(); |
||||
vector<unsigned char> data; |
||||
while (pc < txout.scriptPubKey.end()) |
||||
{ |
||||
opcodetype opcode; |
||||
if (!txout.scriptPubKey.GetOp(pc, opcode, data)) |
||||
break; |
||||
if (data.size() != 0 && contains(data)) |
||||
{ |
||||
fFound = true; |
||||
if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_ALL) |
||||
insert(COutPoint(hash, i)); |
||||
else if ((nFlags & BLOOM_UPDATE_MASK) == BLOOM_UPDATE_P2PUBKEY_ONLY) |
||||
{ |
||||
txnouttype type; |
||||
vector<vector<unsigned char> > vSolutions; |
||||
if (Solver(txout.scriptPubKey, type, vSolutions) && |
||||
(type == TX_PUBKEY || type == TX_MULTISIG)) |
||||
insert(COutPoint(hash, i)); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (fFound) |
||||
return true; |
||||
|
||||
BOOST_FOREACH(const CTxIn& txin, tx.vin) |
||||
{ |
||||
// Match if the filter contains an outpoint tx spends
|
||||
if (contains(txin.prevout)) |
||||
return true; |
||||
|
||||
// Match if the filter contains any arbitrary script data element in any scriptSig in tx
|
||||
CScript::const_iterator pc = txin.scriptSig.begin(); |
||||
vector<unsigned char> data; |
||||
while (pc < txin.scriptSig.end()) |
||||
{ |
||||
opcodetype opcode; |
||||
if (!txin.scriptSig.GetOp(pc, opcode, data)) |
||||
break; |
||||
if (data.size() != 0 && contains(data)) |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
} |
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2012 The Bitcoin developers
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#ifndef BITCOIN_BLOOM_H |
||||
#define BITCOIN_BLOOM_H |
||||
|
||||
#include <vector> |
||||
|
||||
#include "uint256.h" |
||||
#include "serialize.h" |
||||
|
||||
class COutPoint; |
||||
class CTransaction; |
||||
|
||||
// 20,000 items with fp rate < 0.1% or 10,000 items and <0.0001%
|
||||
static const unsigned int MAX_BLOOM_FILTER_SIZE = 36000; // bytes
|
||||
static const unsigned int MAX_HASH_FUNCS = 50; |
||||
|
||||
// First two bits of nFlags control how much IsRelevantAndUpdate actually updates
|
||||
// The remaining bits are reserved
|
||||
enum bloomflags |
||||
{ |
||||
BLOOM_UPDATE_NONE = 0, |
||||
BLOOM_UPDATE_ALL = 1, |
||||
// Only adds outpoints to the filter if the output is a pay-to-pubkey/pay-to-multisig script
|
||||
BLOOM_UPDATE_P2PUBKEY_ONLY = 2, |
||||
BLOOM_UPDATE_MASK = 3, |
||||
}; |
||||
|
||||
/**
|
||||
* BloomFilter is a probabilistic filter which SPV clients provide |
||||
* so that we can filter the transactions we sends them. |
||||
* |
||||
* This allows for significantly more efficient transaction and block downloads. |
||||
* |
||||
* Because bloom filters are probabilistic, an SPV node can increase the false- |
||||
* positive rate, making us send them transactions which aren't actually theirs, |
||||
* allowing clients to trade more bandwidth for more privacy by obfuscating which |
||||
* keys are owned by them. |
||||
*/ |
||||
class CBloomFilter |
||||
{ |
||||
private: |
||||
std::vector<unsigned char> vData; |
||||
unsigned int nHashFuncs; |
||||
unsigned int nTweak; |
||||
unsigned char nFlags; |
||||
|
||||
unsigned int Hash(unsigned int nHashNum, const std::vector<unsigned char>& vDataToHash) const; |
||||
|
||||
public: |
||||
// Creates a new bloom filter which will provide the given fp rate when filled with the given number of elements
|
||||
// Note that if the given parameters will result in a filter outside the bounds of the protocol limits,
|
||||
// the filter created will be as close to the given parameters as possible within the protocol limits.
|
||||
// This will apply if nFPRate is very low or nElements is unreasonably high.
|
||||
// nTweak is a constant which is added to the seed value passed to the hash function
|
||||
// It should generally always be a random value (and is largely only exposed for unit testing)
|
||||
// nFlags should be one of the BLOOM_UPDATE_* enums (not _MASK)
|
||||
CBloomFilter(unsigned int nElements, double nFPRate, unsigned int nTweak, unsigned char nFlagsIn); |
||||
// Using a filter initialized with this results in undefined behavior
|
||||
// Should only be used for deserialization
|
||||
CBloomFilter() {} |
||||
|
||||
IMPLEMENT_SERIALIZE |
||||
( |
||||
READWRITE(vData); |
||||
READWRITE(nHashFuncs); |
||||
READWRITE(nTweak); |
||||
READWRITE(nFlags); |
||||
) |
||||
|
||||
void insert(const std::vector<unsigned char>& vKey); |
||||
void insert(const COutPoint& outpoint); |
||||
void insert(const uint256& hash); |
||||
|
||||
bool contains(const std::vector<unsigned char>& vKey) const; |
||||
bool contains(const COutPoint& outpoint) const; |
||||
bool contains(const uint256& hash) const; |
||||
|
||||
// True if the size is <= MAX_BLOOM_FILTER_SIZE and the number of hash functions is <= MAX_HASH_FUNCS
|
||||
// (catch a filter which was just deserialized which was too big)
|
||||
bool IsWithinSizeConstraints() const; |
||||
|
||||
// Also adds any outputs which match the filter to the filter (to match their spending txes)
|
||||
bool IsRelevantAndUpdate(const CTransaction& tx, const uint256& hash); |
||||
}; |
||||
|
||||
#endif /* BITCOIN_BLOOM_H */ |
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
#include "hash.h" |
||||
|
||||
inline uint32_t ROTL32 ( uint32_t x, int8_t r ) |
||||
{ |
||||
return (x << r) | (x >> (32 - r)); |
||||
} |
||||
|
||||
unsigned int MurmurHash3(unsigned int nHashSeed, const std::vector<unsigned char>& vDataToHash) |
||||
{ |
||||
// The following is MurmurHash3 (x86_32), see http://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp
|
||||
uint32_t h1 = nHashSeed; |
||||
const uint32_t c1 = 0xcc9e2d51; |
||||
const uint32_t c2 = 0x1b873593; |
||||
|
||||
const int nblocks = vDataToHash.size() / 4; |
||||
|
||||
//----------
|
||||
// body
|
||||
const uint32_t * blocks = (const uint32_t *)(&vDataToHash[0] + nblocks*4); |
||||
|
||||
for(int i = -nblocks; i; i++) |
||||
{ |
||||
uint32_t k1 = blocks[i]; |
||||
|
||||
k1 *= c1; |
||||
k1 = ROTL32(k1,15); |
||||
k1 *= c2; |
||||
|
||||
h1 ^= k1; |
||||
h1 = ROTL32(h1,13); |
||||
h1 = h1*5+0xe6546b64; |
||||
} |
||||
|
||||
//----------
|
||||
// tail
|
||||
const uint8_t * tail = (const uint8_t*)(&vDataToHash[0] + nblocks*4); |
||||
|
||||
uint32_t k1 = 0; |
||||
|
||||
switch(vDataToHash.size() & 3) |
||||
{ |
||||
case 3: k1 ^= tail[2] << 16; |
||||
case 2: k1 ^= tail[1] << 8; |
||||
case 1: k1 ^= tail[0]; |
||||
k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1; |
||||
}; |
||||
|
||||
//----------
|
||||
// finalization
|
||||
h1 ^= vDataToHash.size(); |
||||
h1 ^= h1 >> 16; |
||||
h1 *= 0x85ebca6b; |
||||
h1 ^= h1 >> 13; |
||||
h1 *= 0xc2b2ae35; |
||||
h1 ^= h1 >> 16; |
||||
|
||||
return h1; |
||||
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,98 @@
@@ -0,0 +1,98 @@
|
||||
#include <boost/test/unit_test.hpp> |
||||
|
||||
#include "uint256.h" |
||||
#include "main.h" |
||||
|
||||
using namespace std; |
||||
|
||||
class CPartialMerkleTreeTester : public CPartialMerkleTree |
||||
{ |
||||
public: |
||||
// flip one bit in one of the hashes - this should break the authentication
|
||||
void Damage() { |
||||
unsigned int n = rand() % vHash.size(); |
||||
int bit = rand() % 256; |
||||
uint256 &hash = vHash[n]; |
||||
hash ^= ((uint256)1 << bit); |
||||
} |
||||
}; |
||||
|
||||
BOOST_AUTO_TEST_SUITE(pmt_tests) |
||||
|
||||
BOOST_AUTO_TEST_CASE(pmt_test1) |
||||
{ |
||||
static const unsigned int nTxCounts[] = {1, 4, 7, 17, 56, 100, 127, 256, 312, 513, 1000, 4095}; |
||||
|
||||
for (int n = 0; n < 12; n++) { |
||||
unsigned int nTx = nTxCounts[n]; |
||||
|
||||
// build a block with some dummy transactions
|
||||
CBlock block; |
||||
for (unsigned int j=0; j<nTx; j++) { |
||||
CTransaction tx; |
||||
tx.nLockTime = rand(); // actual transaction data doesn't matter; just make the nLockTime's unique
|
||||
block.vtx.push_back(tx); |
||||
} |
||||
|
||||
// calculate actual merkle root and height
|
||||
uint256 merkleRoot1 = block.BuildMerkleTree(); |
||||
std::vector<uint256> vTxid(nTx, 0); |
||||
for (unsigned int j=0; j<nTx; j++) |
||||
vTxid[j] = block.vtx[j].GetHash(); |
||||
int nHeight = 1, nTx_ = nTx; |
||||
while (nTx_ > 1) { |
||||
nTx_ = (nTx_+1)/2; |
||||
nHeight++; |
||||
} |
||||
|
||||
// check with random subsets with inclusion chances 1, 1/2, 1/4, ..., 1/128
|
||||
for (int att = 1; att < 15; att++) { |
||||
// build random subset of txid's
|
||||
std::vector<bool> vMatch(nTx, false); |
||||
std::vector<uint256> vMatchTxid1; |
||||
for (unsigned int j=0; j<nTx; j++) { |
||||
bool fInclude = (rand() & ((1 << (att/2)) - 1)) == 0; |
||||
vMatch[j] = fInclude; |
||||
if (fInclude) |
||||
vMatchTxid1.push_back(vTxid[j]); |
||||
} |
||||
|
||||
// build the partial merkle tree
|
||||
CPartialMerkleTree pmt1(vTxid, vMatch); |
||||
|
||||
// serialize
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); |
||||
ss << pmt1; |
||||
|
||||
// verify CPartialMerkleTree's size guarantees
|
||||
unsigned int n = std::min<unsigned int>(nTx, 1 + vMatchTxid1.size()*nHeight); |
||||
BOOST_CHECK(ss.size() <= 10 + (258*n+7)/8); |
||||
|
||||
// deserialize into a tester copy
|
||||
CPartialMerkleTreeTester pmt2; |
||||
ss >> pmt2; |
||||
|
||||
// extract merkle root and matched txids from copy
|
||||
std::vector<uint256> vMatchTxid2; |
||||
uint256 merkleRoot2 = pmt2.ExtractMatches(vMatchTxid2); |
||||
|
||||
// check that it has the same merkle root as the original, and a valid one
|
||||
BOOST_CHECK(merkleRoot1 == merkleRoot2); |
||||
BOOST_CHECK(merkleRoot2 != 0); |
||||
|
||||
// check that it contains the matched transactions (in the same order!)
|
||||
BOOST_CHECK(vMatchTxid1 == vMatchTxid2); |
||||
|
||||
// check that random bit flips break the authentication
|
||||
for (int j=0; j<4; j++) { |
||||
CPartialMerkleTreeTester pmt3(pmt2); |
||||
pmt3.Damage(); |
||||
std::vector<uint256> vMatchTxid3; |
||||
uint256 merkleRoot3 = pmt3.ExtractMatches(vMatchTxid3); |
||||
BOOST_CHECK(merkleRoot3 != merkleRoot1); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
BOOST_AUTO_TEST_SUITE_END() |
Loading…
Reference in new issue