From 394d0b788db7a148e0e83c4baf671bf01f684f96 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sat, 21 Jul 2012 15:47:35 +0200 Subject: [PATCH] Add Wallet.getBloomFilter and a test-case for it. --- .../java/com/google/bitcoin/core/Wallet.java | 67 +++++++++++++++++++ .../google/bitcoin/core/BloomFilterTest.java | 28 ++++++++ 2 files changed, 95 insertions(+) diff --git a/core/src/main/java/com/google/bitcoin/core/Wallet.java b/core/src/main/java/com/google/bitcoin/core/Wallet.java index 95d81acb..b93df4e1 100644 --- a/core/src/main/java/com/google/bitcoin/core/Wallet.java +++ b/core/src/main/java/com/google/bitcoin/core/Wallet.java @@ -238,6 +238,13 @@ public class Wallet implements Serializable, BlockChainListener { public synchronized Iterable getKeys() { return new ArrayList(keychain); } + + /** + * Returns the number of keys in the keychain. + */ + public synchronized int getKeychainSize() { + return keychain.size(); + } private synchronized void saveToFile(File temp, File destFile) throws IOException { // This odd construction exists to allow Android apps to control file permissions on the newly saved files @@ -2204,4 +2211,64 @@ public class Wallet implements Serializable, BlockChainListener { public void setLastBlockSeenHash(Sha256Hash lastBlockSeenHash) { this.lastBlockSeenHash = lastBlockSeenHash; } + + /** + * Gets the number of elements that will be added to a bloom filter returned by getBloomFilter + */ + public int getBloomFilterElementCount() { + int size = getKeychainSize() * 2; + for (Transaction tx : getTransactions(false, true)) { + for (TransactionOutput out : tx.getOutputs()) { + try { + if (out.isMine(this) && out.getScriptPubKey().isSentToRawPubKey()) + size++; + } catch (ScriptException e) { + throw new RuntimeException(e); // If it is ours, we parsed the script corectly, so this shouldn't happen + } + } + } + return size; + } + + /** + * Gets a bloom filter that contains all of the public keys from this wallet, + * and which will provide the given false-positive rate. + * + * See the docs for {@link BloomFilter#BloomFilter(int, double)} for a brief explanation of anonymity when using bloom filters. + */ + public BloomFilter getBloomFilter(double falsePositiveRate) { + return getBloomFilter(getBloomFilterElementCount(), falsePositiveRate, new Random().nextLong()); + } + + /** + * Gets a bloom filter that contains all of the public keys from this wallet, + * and which will provide the given false-positive rate if it has size elements. + * Keep in mind that you will get 2 elements in the bloom filter for each key in the wallet. + * + * This is used to generate a BloomFilter which can be #{link BloomFilter.merge}d with another. + * It could also be used if you have a specific target for the filter's size. + * + * See the docs for {@link BloomFilter#BloomFilter(int, double)} for a brief explanation of anonymity when using bloom filters. + */ + public synchronized BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak) { + BloomFilter filter = new BloomFilter(size, falsePositiveRate, nTweak); + for (ECKey key : keychain) { + filter.insert(key.getPubKey()); + filter.insert(key.getPubKeyHash()); + } + for (Transaction tx : getTransactions(false, true)) { + for (int i = 0; i < tx.getOutputs().size(); i++) { + TransactionOutput out = tx.getOutputs().get(i); + try { + if (out.isMine(this) && out.getScriptPubKey().isSentToRawPubKey()) { + TransactionOutPoint outPoint = new TransactionOutPoint(params, i, tx); + filter.insert(outPoint.bitcoinSerialize()); + } + } catch (ScriptException e) { + throw new RuntimeException(e); // If it is ours, we parsed the script corectly, so this shouldn't happen + } + } + } + return filter; + } } diff --git a/core/src/test/java/com/google/bitcoin/core/BloomFilterTest.java b/core/src/test/java/com/google/bitcoin/core/BloomFilterTest.java index 9437e377..f946bf29 100644 --- a/core/src/test/java/com/google/bitcoin/core/BloomFilterTest.java +++ b/core/src/test/java/com/google/bitcoin/core/BloomFilterTest.java @@ -44,4 +44,32 @@ public class BloomFilterTest { // Value generated by the reference client assertTrue(Arrays.equals(Hex.decode("03ce42990500000001000080"), filter.bitcoinSerialize())); } + + @Test + public void walletTest() throws Exception { + NetworkParameters params = NetworkParameters.prodNet(); + + DumpedPrivateKey privKey = new DumpedPrivateKey(params, "5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C"); + + Address addr = privKey.getKey().toAddress(params); + assertTrue(addr.toString().equals("17Wx1GQfyPTNWpQMHrTwRSMTCAonSiZx9e")); + + Wallet wallet = new Wallet(params); + // Check that the wallet was created with no keys + // If wallets ever get created with keys, this test needs redone. + for (ECKey key : wallet.getKeys()) + fail(); + wallet.addKey(privKey.getKey()); + // Add a random key which happens to have been used in a recent generation + wallet.addKey(new ECKey(null, Hex.decode("03cb219f69f1b49468bd563239a86667e74a06fcba69ac50a08a5cbc42a5808e99"))); + wallet.commitTx(new Transaction(params, Hex.decode("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d038754030114062f503253482fffffffff01c05e559500000000232103cb219f69f1b49468bd563239a86667e74a06fcba69ac50a08a5cbc42a5808e99ac00000000"))); + + // We should have 2 per pubkey, and one for the pay-2-pubkey output we have + assertTrue(wallet.getBloomFilterElementCount() == 5); + + BloomFilter filter = wallet.getBloomFilter(wallet.getBloomFilterElementCount(), 0.001, 0); + + // Value generated by the reference client + assertTrue(Arrays.equals(Hex.decode("082ae5edc8e51d4a030800000000000000"), filter.bitcoinSerialize())); + } }