Gavin Andresen
12 years ago
21 changed files with 1315 additions and 62 deletions
@ -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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
#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 @@ |
|||||||
|
#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