Merge remote-tracking branch 'upstream/master' into rebase

Conflicts:
	core/src/main/java/com/dogecoin/dogecoinj/core/Transaction.java
	core/src/main/java/com/dogecoin/dogecoinj/core/Wallet.java
	core/src/main/java/com/dogecoin/dogecoinj/wallet/DeterministicSeed.java
This commit is contained in:
langerhans
2014-10-30 22:32:59 +01:00
17 changed files with 419 additions and 243 deletions

View File

@@ -1117,23 +1117,36 @@ public class ECKey implements EncryptableItem, Serializable {
@Override
public String toString() {
return toString(false);
return toString(false, null);
}
/**
* Produce a string rendering of the ECKey INCLUDING the private key.
* Unless you absolutely need the private key it is better for security reasons to just use {@link #toString()}.
*/
public String toStringWithPrivate() {
return toString(true);
public String toStringWithPrivate(NetworkParameters params) {
return toString(true, params);
}
private String toString(boolean includePrivate) {
public String getPrivateKeyAsHex() {
return Utils.HEX.encode(getPrivKeyBytes());
}
public String getPublicKeyAsHex() {
return Utils.HEX.encode(pub.getEncoded());
}
public String getPrivateKeyAsWiF(NetworkParameters params) {
return getPrivateKeyEncoded(params).toString();
}
private String toString(boolean includePrivate, NetworkParameters params) {
final ToStringHelper helper = Objects.toStringHelper(this).omitNullValues();
helper.add("pub", Utils.HEX.encode(pub.getEncoded()));
helper.add("pub HEX", getPublicKeyAsHex());
if (includePrivate) {
try {
helper.add("priv", Utils.HEX.encode(getPrivKey().toByteArray()));
helper.add("priv HEX", getPrivateKeyAsHex());
helper.add("priv WIF", getPrivateKeyAsWiF(params));
} catch (IllegalStateException e) {
// TODO: Make hasPrivKey() work for deterministic keys and fix this.
}
@@ -1156,7 +1169,7 @@ public class ECKey implements EncryptableItem, Serializable {
builder.append("\n");
if (includePrivateKeys) {
builder.append(" ");
builder.append(toStringWithPrivate());
builder.append(toStringWithPrivate(params));
builder.append("\n");
}
}

View File

@@ -90,14 +90,14 @@ public class Transaction extends ChildMessage implements Serializable {
/**
* If fee is lower than this value (in satoshis), a default reference client will treat it as if there were no fee.
* Currently this is 10000 satoshis.
* Currently this is 1000 satoshis.
*/
public static final Coin REFERENCE_DEFAULT_MIN_TX_FEE = Coin.COIN; // 1 DOGE min fee
/**
* Any standard (ie pay-to-address) output smaller than this value (in satoshis) will most likely be rejected by the network.
* This is calculated by assuming a standard output will be 34 bytes, and then using the formula used in
* {@link TransactionOutput#getMinNonDustValue(Coin)}.
* {@link TransactionOutput#getMinNonDustValue(Coin)}. Currently it's 546 satoshis.
*/
public static final Coin MIN_NONDUST_OUTPUT = Coin.SATOSHI; //DOGE: We can send one "shibetoshi" but this will cost us extra fee!

View File

@@ -47,6 +47,7 @@ import com.google.protobuf.ByteString;
import org.bitcoin.protocols.payments.Protos.PaymentDetails;
import com.dogecoin.dogecoinj.wallet.Protos.Wallet.EncryptionType;
import org.bitcoinj.wallet.AllRandomKeysRotating;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
@@ -58,7 +59,10 @@ import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.google.common.base.Preconditions.*;
@@ -106,7 +110,10 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
private static final long serialVersionUID = 2L;
private static final int MINIMUM_BLOOM_DATA_LENGTH = 8;
// Ordering: lock > keychainLock. Keychain is protected separately to allow fast querying of current receive address
// even if the wallet itself is busy e.g. saving or processing a big reorg. Useful for reducing UI latency.
protected final ReentrantLock lock = Threading.lock("wallet");
protected final ReentrantReadWriteLock keychainLock = Threading.factory.newReentrantReadWriteLock("wallet-keychain");
// The various pools below give quick access to wallet-relevant transactions by the state they're in:
//
@@ -151,7 +158,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// The key chain group is not thread safe, and generally the whole hierarchy of objects should not be mutated
// outside the wallet lock. So don't expose this object directly via any accessors!
@GuardedBy("lock") protected KeyChainGroup keychain;
@GuardedBy("keychainLock") protected KeyChainGroup keychain;
// A list of scripts watched by this wallet.
private Set<Script> watchedScripts;
@@ -187,7 +194,6 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// UNIX time in seconds. Money controlled by keys created before this time will be automatically respent to a key
// that was created after it. Useful when you believe some keys have been compromised.
private volatile long vKeyRotationTimestamp;
private volatile boolean vKeyRotationEnabled;
protected transient CoinSelector coinSelector = new DefaultCoinSelector();
@@ -338,12 +344,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* a different key (for each purpose independently).
*/
public DeterministicKey currentKey(KeyChain.KeyPurpose purpose) {
lock.lock();
keychainLock.readLock().lock();
try {
maybeUpgradeToHD();
return keychain.currentKey(purpose);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -359,12 +365,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* Returns address for a {@link #currentKey(com.dogecoin.dogecoinj.wallet.KeyChain.KeyPurpose)}
*/
public Address currentAddress(KeyChain.KeyPurpose purpose) {
lock.lock();
keychainLock.readLock().lock();
try {
maybeUpgradeToHD();
return keychain.currentAddress(purpose);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -397,17 +403,18 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* to someone who wishes to send money.
*/
public List<DeterministicKey> freshKeys(KeyChain.KeyPurpose purpose, int numberOfKeys) {
lock.lock();
List<DeterministicKey> keys;
keychainLock.writeLock().lock();
try {
maybeUpgradeToHD();
List<DeterministicKey> keys = keychain.freshKeys(purpose, numberOfKeys);
// Do we really need an immediate hard save? Arguably all this is doing is saving the 'current' key
// and that's not quite so important, so we could coalesce for more performance.
saveNow();
return keys;
keys = keychain.freshKeys(purpose, numberOfKeys);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
// Do we really need an immediate hard save? Arguably all this is doing is saving the 'current' key
// and that's not quite so important, so we could coalesce for more performance.
saveNow();
return keys;
}
/**
@@ -422,14 +429,15 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* Returns address for a {@link #freshKey(com.dogecoin.dogecoinj.wallet.KeyChain.KeyPurpose)}
*/
public Address freshAddress(KeyChain.KeyPurpose purpose) {
lock.lock();
Address key;
keychainLock.writeLock().lock();
try {
Address key = keychain.freshAddress(purpose);
saveNow();
return key;
key = keychain.freshAddress(purpose);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
saveNow();
return key;
}
/**
@@ -449,11 +457,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* you automatically the first time a new key is requested (this happens when spending due to the change address).
*/
public void upgradeToDeterministic(@Nullable KeyParameter aesKey) throws DeterministicUpgradeRequiresPassword {
lock.lock();
keychainLock.writeLock().lock();
try {
keychain.upgradeToDeterministic(vKeyRotationEnabled ? vKeyRotationTimestamp : 0, aesKey);
keychain.upgradeToDeterministic(vKeyRotationTimestamp, aesKey);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
@@ -463,11 +471,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* that would require a new address or key.
*/
public boolean isDeterministicUpgradeRequired() {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.isDeterministicUpgradeRequired();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -476,7 +484,6 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
private void maybeUpgradeToHD(@Nullable KeyParameter aesKey) throws DeterministicUpgradeRequiresPassword {
checkState(lock.isHeldByCurrentThread());
if (keychain.isDeterministicUpgradeRequired()) {
log.info("Upgrade to HD wallets is required, attempting to do so.");
try {
@@ -493,11 +500,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* Returns a snapshot of the watched scripts. This view is not live.
*/
public List<Script> getWatchedScripts() {
lock.lock();
keychainLock.readLock().lock();
try {
return new ArrayList<Script>(watchedScripts);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -507,11 +514,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @return Whether the key was removed or not.
*/
public boolean removeKey(ECKey key) {
lock.lock();
keychainLock.writeLock().lock();
try {
return keychain.removeImportedKey(key);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
@@ -519,11 +526,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* Returns the number of keys in the key chain, including lookahead keys.
*/
public int getKeychainSize() {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.numKeys();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -531,11 +538,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* Returns a list of the non-deterministic keys that have been imported into the wallet, or the empty list if none.
*/
public List<ECKey> getImportedKeys() {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.getImportedKeys();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -579,16 +586,17 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* in the list that was not already present.
*/
public int importKeys(final List<ECKey> keys) {
lock.lock();
// API usage check.
checkNoDeterministicKeys(keys);
int result;
keychainLock.writeLock().lock();
try {
// API usage check.
checkNoDeterministicKeys(keys);
int result = keychain.importKeys(keys);
saveNow();
return result;
result = keychain.importKeys(keys);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
saveNow();
return result;
}
private void checkNoDeterministicKeys(List<ECKey> keys) {
@@ -600,23 +608,23 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
/** Takes a list of keys and a password, then encrypts and imports them in one step using the current keycrypter. */
public int importKeysAndEncrypt(final List<ECKey> keys, CharSequence password) {
lock.lock();
keychainLock.writeLock().lock();
try {
checkNotNull(getKeyCrypter(), "Wallet is not encrypted");
return importKeysAndEncrypt(keys, getKeyCrypter().deriveKey(password));
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
/** Takes a list of keys and an AES key, then encrypts and imports them in one step using the current keycrypter. */
public int importKeysAndEncrypt(final List<ECKey> keys, KeyParameter aesKey) {
lock.lock();
keychainLock.writeLock().lock();
try {
checkNoDeterministicKeys(keys);
return keychain.importKeysAndEncrypt(keys, aesKey);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
@@ -632,7 +640,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* </p>
*/
public void addAndActivateHDChain(DeterministicKeyChain chain) {
keychain.addAndActivateHDChain(chain);
keychainLock.writeLock().lock();
try {
keychain.addAndActivateHDChain(chain);
} finally {
keychainLock.writeLock().unlock();
}
}
/** See {@link com.dogecoin.dogecoinj.wallet.DeterministicKeyChain#setLookaheadSize(int)} for more info on this. */
@@ -647,28 +660,33 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
/** See {@link com.dogecoin.dogecoinj.wallet.DeterministicKeyChain#setLookaheadSize(int)} for more info on this. */
public int getKeychainLookaheadSize() {
return keychain.getLookaheadSize();
keychainLock.readLock().lock();
try {
return keychain.getLookaheadSize();
} finally {
keychainLock.readLock().unlock();
}
}
/** See {@link com.dogecoin.dogecoinj.wallet.DeterministicKeyChain#setLookaheadThreshold(int)} for more info on this. */
public void setKeychainLookaheadThreshold(int num) {
lock.lock();
keychainLock.writeLock().lock();
try {
maybeUpgradeToHD();
keychain.setLookaheadThreshold(num);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
/** See {@link com.dogecoin.dogecoinj.wallet.DeterministicKeyChain#setLookaheadThreshold(int)} for more info on this. */
public int getKeychainLookaheadThreshold() {
lock.lock();
keychainLock.readLock().lock();
try {
maybeUpgradeToHD();
return keychain.getLookaheadThreshold();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -679,12 +697,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* zero key in the recommended BIP32 hierarchy.
*/
public DeterministicKey getWatchingKey() {
lock.lock();
keychainLock.readLock().lock();
try {
maybeUpgradeToHD();
return keychain.getActiveKeyChain().getWatchingKey();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -739,29 +757,27 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @return how many scripts were added successfully
*/
public int addWatchedScripts(final List<Script> scripts) {
lock.lock();
int added = 0;
keychainLock.writeLock().lock();
try {
int added = 0;
for (final Script script : scripts) {
if (watchedScripts.contains(script)) continue;
watchedScripts.add(script);
added++;
}
queueOnScriptsAdded(scripts);
saveNow();
return added;
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
queueOnScriptsAdded(scripts);
saveNow();
return added;
}
/**
* Returns all addresses watched by this wallet.
*/
public List<Address> getWatchedAddresses() {
lock.lock();
keychainLock.readLock().lock();
try {
List<Address> addresses = new LinkedList<Address>();
for (Script script : watchedScripts)
@@ -769,7 +785,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
addresses.add(script.getToAddress(params));
return addresses;
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -782,21 +798,21 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
@Override
@Nullable
public ECKey findKeyFromPubHash(byte[] pubkeyHash) {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.findKeyFromPubHash(pubkeyHash);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
/** Returns true if the given key is in the wallet, false otherwise. Currently an O(N) operation. */
public boolean hasKey(ECKey key) {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.hasKey(key);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -809,11 +825,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
/** {@inheritDoc} */
@Override
public boolean isWatchedScript(Script script) {
lock.lock();
keychainLock.readLock().lock();
try {
return watchedScripts.contains(script);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -824,11 +840,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
@Override
@Nullable
public ECKey findKeyFromPubKey(byte[] pubkey) {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.findKeyFromPubKey(pubkey);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -845,11 +861,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
@Nullable
@Override
public RedeemData findRedeemDataFromScriptHash(byte[] payToScriptHash) {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.findRedeemDataFromScriptHash(payToScriptHash);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -864,7 +880,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* See {@link com.dogecoin.dogecoinj.wallet.DeterministicKeyChain#markKeyAsUsed(DeterministicKey)} for more info on this.
*/
private void markKeysAsUsed(Transaction tx) {
lock.lock();
keychainLock.writeLock().lock();
try {
for (TransactionOutput o : tx.getOutputs()) {
try {
@@ -882,7 +898,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
}
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
@@ -891,14 +907,14 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @throws com.dogecoin.dogecoinj.core.ECKey.MissingPrivateKeyException if the seed is unavailable (watching wallet)
*/
public DeterministicSeed getKeyChainSeed() {
lock.lock();
keychainLock.readLock().lock();
try {
DeterministicSeed seed = keychain.getActiveKeyChain().getSeed();
if (seed == null)
throw new ECKey.MissingPrivateKeyException();
return seed;
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -907,12 +923,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* use currentReceiveKey/freshReceiveKey instead.
*/
public DeterministicKey getKeyByPath(List<ChildNumber> path) {
lock.lock();
keychainLock.readLock().lock();
try {
maybeUpgradeToHD();
return keychain.getActiveKeyChain().getKeyByPath(path, false);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -922,14 +938,14 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* parameters to derive a key from the given password.
*/
public void encrypt(CharSequence password) {
lock.lock();
keychainLock.writeLock().lock();
try {
final KeyCrypterScrypt scrypt = new KeyCrypterScrypt();
keychain.encrypt(scrypt, scrypt.deriveKey(password));
saveNow();
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
saveNow();
}
/**
@@ -941,13 +957,13 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @throws KeyCrypterException Thrown if the wallet encryption fails. If so, the wallet state is unchanged.
*/
public void encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) {
lock.lock();
keychainLock.writeLock().lock();
try {
keychain.encrypt(keyCrypter, aesKey);
saveNow();
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
saveNow();
}
/**
@@ -955,15 +971,15 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @throws KeyCrypterException Thrown if the wallet decryption fails. If so, the wallet state is unchanged.
*/
public void decrypt(CharSequence password) {
lock.lock();
keychainLock.writeLock().lock();
try {
final KeyCrypter crypter = keychain.getKeyCrypter();
checkState(crypter != null, "Not encrypted");
keychain.decrypt(crypter.deriveKey(password));
saveNow();
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
saveNow();
}
/**
@@ -973,13 +989,13 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @throws KeyCrypterException Thrown if the wallet decryption fails. If so, the wallet state is unchanged.
*/
public void decrypt(KeyParameter aesKey) {
lock.lock();
keychainLock.writeLock().lock();
try {
keychain.decrypt(aesKey);
saveNow();
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
saveNow();
}
/**
@@ -990,11 +1006,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @throws IllegalStateException if the wallet is not encrypted.
*/
public boolean checkPassword(CharSequence password) {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.checkPassword(password);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -1004,11 +1020,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @return boolean true if AES key supplied can decrypt the first encrypted private key in the wallet, false otherwise.
*/
public boolean checkAESKey(KeyParameter aesKey) {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.checkAESKey(aesKey);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -1018,11 +1034,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
*/
@Nullable
public KeyCrypter getKeyCrypter() {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.getKeyCrypter();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -1032,7 +1048,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* (This is a convenience method - the encryption type is actually stored in the keyCrypter).
*/
public EncryptionType getEncryptionType() {
lock.lock();
keychainLock.readLock().lock();
try {
KeyCrypter crypter = keychain.getKeyCrypter();
if (crypter != null)
@@ -1040,7 +1056,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
else
return EncryptionType.UNENCRYPTED;
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -1058,11 +1074,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// TODO: Make this package private once the classes finish moving around.
/** Internal use only. */
public List<Protos.Key> serializeKeychainToProtobuf() {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.serializeToProtobuf();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -1474,7 +1490,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
RiskAnalysis analysis = riskAnalyzer.create(this, tx, dependencies);
RiskAnalysis.Result result = analysis.analyze();
if (result != RiskAnalysis.Result.OK) {
log.warn("Pending transaction {} was considered risky: {}", tx.getHashAsString(), analysis);
log.warn("Pending transaction was considered risky: {}\n{}", analysis, tx);
return true;
}
return false;
@@ -2078,13 +2094,9 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* like receiving money. The listener is executed by the given executor.
*/
public void addEventListener(WalletEventListener listener, Executor executor) {
lock.lock();
try {
eventListeners.add(new ListenerRegistration<WalletEventListener>(listener, executor));
keychain.addEventListener(listener, executor);
} finally {
lock.unlock();
}
// This is thread safe, so we don't need to take the lock.
eventListeners.add(new ListenerRegistration<WalletEventListener>(listener, executor));
keychain.addEventListener(listener, executor);
}
/**
@@ -2092,13 +2104,8 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* was never added.
*/
public boolean removeEventListener(WalletEventListener listener) {
lock.lock();
try {
keychain.removeEventListener(listener);
return ListenerRegistration.removeFromList(listener, eventListeners);
} finally {
lock.unlock();
}
keychain.removeEventListener(listener);
return ListenerRegistration.removeFromList(listener, eventListeners);
}
private void queueOnTransactionConfidenceChanged(final Transaction tx) {
@@ -2171,7 +2178,6 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
protected void queueOnScriptsAdded(final List<Script> scripts) {
checkState(lock.isHeldByCurrentThread());
for (final ListenerRegistration<WalletEventListener> registration : eventListeners) {
registration.executor.execute(new Runnable() {
@Override
@@ -2607,7 +2613,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
*/
@Override
public long getEarliestKeyCreationTime() {
lock.lock();
keychainLock.readLock().lock();
try {
long earliestTime = keychain.getEarliestKeyCreationTime();
for (Script script : watchedScripts)
@@ -2616,7 +2622,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
return Utils.currentTimeSeconds();
return earliestTime;
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -3787,29 +3793,27 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
*/
@Override
public int getBloomFilterElementCount() {
// This is typically called by the PeerGroup, in which case it will have already explicitly taken the lock
// before calling, but because this is public API we must still lock again regardless.
lock.lock();
try {
int size = keychain.getBloomFilterElementCount();
for (Transaction tx : getTransactions(false)) {
for (TransactionOutput out : tx.getOutputs()) {
try {
if (isTxOutputBloomFilterable(out))
size++;
} catch (ScriptException e) {
throw new RuntimeException(e); // If it is ours, we parsed the script correctly, so this shouldn't happen
}
int size = 0;
for (Transaction tx : getTransactions(false)) {
for (TransactionOutput out : tx.getOutputs()) {
try {
if (isTxOutputBloomFilterable(out))
size++;
} catch (ScriptException e) {
// If it is ours, we parsed the script correctly, so this shouldn't happen.
throw new RuntimeException(e);
}
}
// Some scripts may have more than one bloom element. That should normally be okay,
// because under-counting just increases false-positive rate.
}
keychainLock.readLock().lock();
try {
size += keychain.getBloomFilterElementCount();
// Some scripts may have more than one bloom element. That should normally be okay, because under-counting
// just increases false-positive rate.
size += watchedScripts.size();
return size;
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -3822,11 +3826,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
public boolean isRequiringUpdateAllBloomFilter() {
// This is typically called by the PeerGroup, in which case it will have already explicitly taken the lock
// before calling, but because this is public API we must still lock again regardless.
lock.lock();
keychainLock.readLock().lock();
try {
return !watchedScripts.isEmpty();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -3854,6 +3858,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// This is typically called by the PeerGroup, in which case it will have already explicitly taken the lock
// before calling, but because this is public API we must still lock again regardless.
lock.lock();
keychainLock.readLock().lock();
try {
BloomFilter filter = keychain.getBloomFilter(size, falsePositiveRate, nTweak);
@@ -3882,12 +3887,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
return filter;
} finally {
keychainLock.readLock().unlock();
lock.unlock();
}
}
private boolean isTxOutputBloomFilterable(TransactionOutput out) {
checkState(lock.isHeldByCurrentThread());
boolean isScriptTypeSupported = out.getScriptPubKey().isSentToRawPubKey() || out.getScriptPubKey().isPayToScriptHash();
return (out.isMine(this) && isScriptTypeSupported) ||
out.isWatched(this);
@@ -3899,7 +3904,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* sequence within it to reliably find relevant transactions.
*/
public boolean checkForFilterExhaustion(FilteredBlock block) {
lock.lock();
keychainLock.writeLock().lock();
try {
int epoch = keychain.getCombinedKeyLookaheadEpochs();
for (Transaction tx : block.getAssociatedTransactions().values()) {
@@ -3912,7 +3917,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// block at this point and await a new filter before restarting the download.
return newEpoch > epoch;
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
@@ -3984,6 +3989,26 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
}
/**
* Deserialize the wallet extension with the supplied data and add
* it to the wallet, unless there exists an extension with the
* same id.
*/
public void deserializeAndAddExtension(WalletExtension extension, byte[] data) throws Exception {
String id = checkNotNull(extension).getWalletExtensionID();
lock.lock();
try {
if (extensions.containsKey(id)) {
return;
} else {
extension.deserializeWalletExtension(this, data);
addExtension(extension);
}
} finally {
lock.unlock();
}
}
@Override
public synchronized void setTag(String tag, ByteString value) {
super.setTag(tag, value);
@@ -4295,15 +4320,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
/**
* <p>When a key rotation time is set, and money controlled by keys created before the given timestamp T will be
* <p>When a key rotation time is set, any money controlled by keys created before the given timestamp T will be
* automatically respent to any key that was created after T. This can be used to recover from a situation where
* a set of keys is believed to be compromised. You can stop key rotation by calling this method again with zero
* as the argument, or by using {@link #setKeyRotationEnabled(boolean)}. Once set up, calling
* {@link #maybeDoMaintenance(org.spongycastle.crypto.params.KeyParameter, boolean)} will create and possibly
* send rotation transactions: but it won't be done automatically (because you might have to ask for the users
* password).</p>
*
* <p>Note that this method won't do anything unless you call {@link #setKeyRotationEnabled(boolean)} first.</p>
* as the argument. Once set up, calling {@link #doMaintenance(org.spongycastle.crypto.params.KeyParameter, boolean)}
* will create and possibly send rotation transactions: but it won't be done automatically (because you might have
* to ask for the users password).</p>
*
* <p>The given time cannot be in the future.</p>
*/
@@ -4316,36 +4338,42 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
saveNow();
}
/** Toggles key rotation on and off. Note that this state is not serialized. */
public void setKeyRotationEnabled(boolean enabled) {
vKeyRotationEnabled = enabled;
}
/** Returns whether the keys creation time is before the key rotation time, if one was set. */
public boolean isKeyRotating(ECKey key) {
long time = vKeyRotationTimestamp;
return time != 0 && key.getCreationTimeSeconds() < time;
}
/** @deprecated Renamed to doMaintenance */
@Deprecated
public ListenableFuture<List<Transaction>> maybeDoMaintenance(@Nullable KeyParameter aesKey, boolean andSend) throws DeterministicUpgradeRequiresPassword {
return doMaintenance(aesKey, andSend);
}
/**
* A wallet app should call this from time to time if key rotation is enabled in order to let the wallet craft and
* send transactions needed to re-organise coins internally. A good time to call this would be after receiving coins
* for an unencrypted wallet, or after sending money for an encrypted wallet. If you have an encrypted wallet and
* just want to know if some maintenance needs doing, call this method with doSend set to false and look at the
* returned list of transactions.
* A wallet app should call this from time to time in order to let the wallet craft and send transactions needed
* to re-organise coins internally. A good time to call this would be after receiving coins for an unencrypted
* wallet, or after sending money for an encrypted wallet. If you have an encrypted wallet and just want to know
* if some maintenance needs doing, call this method with andSend set to false and look at the returned list of
* transactions. Maintenance might also include internal changes that involve some processing or work but
* which don't require making transactions - these will happen automatically unless the password is required
* in which case an exception will be thrown.
*
* @param aesKey the users password, if any.
* @param andSend if true, send the transactions via the tx broadcaster and return them, if false just return them.
* @param signAndSend if true, send the transactions via the tx broadcaster and return them, if false just return them.
* @return A list of transactions that the wallet just made/will make for internal maintenance. Might be empty.
* @throws com.dogecoin.dogecoinj.wallet.DeterministicUpgradeRequiresPassword if key rotation requires the users password.
*/
public ListenableFuture<List<Transaction>> maybeDoMaintenance(@Nullable KeyParameter aesKey, boolean andSend) {
public ListenableFuture<List<Transaction>> doMaintenance(@Nullable KeyParameter aesKey, boolean signAndSend) throws DeterministicUpgradeRequiresPassword {
List<Transaction> txns;
lock.lock();
keychainLock.writeLock().lock();
try {
txns = maybeRotateKeys(aesKey);
if (!andSend)
txns = maybeRotateKeys(aesKey, signAndSend);
if (!signAndSend)
return Futures.immediateFuture(txns);
} finally {
keychainLock.writeLock().unlock();
lock.unlock();
}
checkState(!lock.isHeldByCurrentThread());
@@ -4374,26 +4402,37 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
// Checks to see if any coins are controlled by rotating keys and if so, spends them.
private List<Transaction> maybeRotateKeys(@Nullable KeyParameter aesKey) {
private List<Transaction> maybeRotateKeys(@Nullable KeyParameter aesKey, boolean sign) throws DeterministicUpgradeRequiresPassword {
checkState(lock.isHeldByCurrentThread());
checkState(keychainLock.isWriteLockedByCurrentThread());
List<Transaction> results = Lists.newLinkedList();
// TODO: Handle chain replays here.
if (!vKeyRotationEnabled) return results;
// Snapshot volatiles so this method has an atomic view.
long keyRotationTimestamp = vKeyRotationTimestamp;
if (keyRotationTimestamp == 0) return results; // Nothing to do.
// We might have to create a new HD hierarchy if the previous ones are now rotating.
boolean allChainsRotating = true;
for (DeterministicKeyChain chain : keychain.getDeterministicKeyChains()) {
if (chain.getEarliestKeyCreationTime() > vKeyRotationTimestamp) {
if (chain.getEarliestKeyCreationTime() >= vKeyRotationTimestamp) {
allChainsRotating = false;
break;
}
}
if (allChainsRotating) {
log.info("All HD chains are currently rotating, creating a new one");
keychain.createAndActivateNewHDChain();
try {
if (keychain.getImportedKeys().isEmpty()) {
log.info("All HD chains are currently rotating and we have no random keys, creating fresh HD chain ...");
keychain.createAndActivateNewHDChain();
} else {
log.info("All HD chains are currently rotating, attempting to create a new one from the next oldest non-rotating key material ...");
keychain.upgradeToDeterministic(keyRotationTimestamp, aesKey);
log.info(" ... upgraded to HD again, based on next best oldest key.");
}
} catch (AllRandomKeysRotating rotating) {
log.info(" ... no non-rotating random keys available, generating entirely new HD tree: backup required after this.");
keychain.createAndActivateNewHDChain();
}
saveNow();
}
// Because transactions are size limited, we might not be able to re-key the entire wallet in one go. So
@@ -4401,14 +4440,14 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// fully done, at least for now (we may still get more transactions later and this method will be reinvoked).
Transaction tx;
do {
tx = rekeyOneBatch(keyRotationTimestamp, aesKey, results);
tx = rekeyOneBatch(keyRotationTimestamp, aesKey, results, sign);
if (tx != null) results.add(tx);
} while (tx != null && tx.getInputs().size() == KeyTimeCoinSelector.MAX_SIMULTANEOUS_INPUTS);
return results;
}
@Nullable
private Transaction rekeyOneBatch(long timeSecs, @Nullable KeyParameter aesKey, List<Transaction> others) {
private Transaction rekeyOneBatch(long timeSecs, @Nullable KeyParameter aesKey, List<Transaction> others, boolean sign) {
lock.lock();
try {
// Build the transaction using some custom logic for our special needs. Last parameter to
@@ -4440,7 +4479,8 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
rekeyTx.setPurpose(Transaction.Purpose.KEY_ROTATION);
SendRequest req = SendRequest.forTx(rekeyTx);
req.aesKey = aesKey;
signTransaction(req);
if (sign)
signTransaction(req);
// KeyTimeCoinSelector should never select enough inputs to push us oversize.
checkState(rekeyTx.bitcoinSerialize().length < Transaction.MAX_STANDARD_TX_SIZE);
return rekeyTx;
@@ -4462,7 +4502,39 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* If not, redisplay the confirm window and try again.
*/
@Override
public ReentrantLock getLock() {
return lock;
public Lock getLock() {
return new Lock() {
@Override
public void lock() {
lock.lock();
keychainLock.readLock().lock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public boolean tryLock() {
throw new UnsupportedOperationException();
}
@Override
public boolean tryLock(long l, TimeUnit unit) throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public void unlock() {
keychainLock.readLock().unlock();
lock.unlock();
}
@Override
public Condition newCondition() {
throw new UnsupportedOperationException();
}
};
}
}

View File

@@ -482,7 +482,7 @@ public class DeterministicKey extends ECKey {
builder.append("\n");
if (includePrivateKeys) {
builder.append(" ");
builder.append(toStringWithPrivate());
builder.append(toStringWithPrivate(params));
builder.append("\n");
}
}

View File

@@ -300,8 +300,10 @@ public class StoredPaymentChannelClientStates implements WalletExtension {
ECKey.fromPrivate(storedState.getMyKey().toByteArray()),
Coin.valueOf(storedState.getValueToMe()),
Coin.valueOf(storedState.getRefundFees()), false);
if (storedState.hasCloseTransactionHash())
channel.close = containingWallet.getTransaction(new Sha256Hash(storedState.toByteArray()));
if (storedState.hasCloseTransactionHash()) {
Sha256Hash closeTxHash = new Sha256Hash(storedState.getCloseTransactionHash().toByteArray());
channel.close = containingWallet.getTransaction(closeTxHash);
}
putChannel(channel, false);
}
} finally {

View File

@@ -513,13 +513,14 @@ public class WalletProtobufSerializer {
} else {
log.info("Loading wallet extension {}", id);
try {
extension.deserializeWalletExtension(wallet, extProto.getData().toByteArray());
wallet.addOrGetExistingExtension(extension);
wallet.deserializeAndAddExtension(extension, extProto.getData().toByteArray());
} catch (Exception e) {
if (extProto.getMandatory() && requireMandatoryExtensions)
if (extProto.getMandatory() && requireMandatoryExtensions) {
log.error("Error whilst reading extension {}, failing to read wallet", id, e);
throw new UnreadableWalletException("Could not parse mandatory extension in wallet: " + id);
else
} else {
log.error("Error whilst reading extension {}, ignoring", id, e);
}
}
}
}

View File

@@ -151,7 +151,7 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
* Checks the output to see if the script violates a standardness rule. Not complete.
*/
public static RuleViolation isOutputStandard(TransactionOutput output) {
if (MIN_ANALYSIS_NONDUST_OUTPUT.compareTo(output.getValue()) > 0)
if (output.getValue().compareTo(MIN_ANALYSIS_NONDUST_OUTPUT) < 0)
return RuleViolation.DUST;
for (ScriptChunk chunk : output.getScriptPubKey().getChunks()) {
if (chunk.isPushData() && !chunk.isShortestPossiblePushData())

View File

@@ -214,7 +214,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
return self();
}
/** The passphrase to use with the generated mnemonic, or null. Currently must be empty. */
/** The passphrase to use with the generated mnemonic, or null if you would like to use the default empty string. Currently must be the empty string. */
public T passphrase(String passphrase) {
// FIXME support non-empty passphrase
this.passphrase = passphrase;
@@ -228,15 +228,20 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
DeterministicKeyChain chain;
if (random != null) {
chain = new DeterministicKeyChain(random, bits, passphrase, seedCreationTimeSecs);
// Default passphrase to "" if not specified
chain = new DeterministicKeyChain(random, bits, getPassphrase(), seedCreationTimeSecs);
} else if (entropy != null) {
chain = new DeterministicKeyChain(entropy, passphrase, seedCreationTimeSecs);
chain = new DeterministicKeyChain(entropy, getPassphrase(), seedCreationTimeSecs);
} else {
chain = new DeterministicKeyChain(seed);
}
return chain;
}
protected String getPassphrase() {
return passphrase != null ? passphrase : DEFAULT_PASSPHRASE_FOR_MNEMONIC;
}
}
public static Builder<?> builder() {
@@ -1186,8 +1191,8 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
DeterministicKey parent = detkey.getParent();
if (parent == null) continue;
if (detkey.getPath().size() <= treeSize) continue;
if (parent.equals(internalKey) && detkey.getChildNumber().i() > issuedInternalKeys) continue;
if (parent.equals(externalKey) && detkey.getChildNumber().i() > issuedExternalKeys) continue;
if (parent.equals(internalKey) && detkey.getChildNumber().i() >= issuedInternalKeys) continue;
if (parent.equals(externalKey) && detkey.getChildNumber().i() >= issuedExternalKeys) continue;
issuedKeys.add(detkey);
}
return issuedKeys;

View File

@@ -30,6 +30,7 @@ import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.List;
import static com.google.common.base.Preconditions.checkArgument;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
@@ -78,7 +79,7 @@ public class DeterministicSeed implements EncryptableItem {
* @param creationTimeSeconds When the seed was originally created, UNIX time.
*/
public DeterministicSeed(List<String> mnemonicCode, @Nullable byte[] seed, String passphrase, long creationTimeSeconds) {
this((seed != null ? seed : MnemonicCode.toSeed(mnemonicCode, passphrase)), mnemonicCode, creationTimeSeconds);
this((seed != null ? seed : MnemonicCode.toSeed(mnemonicCode, checkNotNull(passphrase))), mnemonicCode, creationTimeSeconds);
}
/**
@@ -90,7 +91,7 @@ public class DeterministicSeed implements EncryptableItem {
* @param creationTimeSeconds When the seed was originally created, UNIX time.
*/
public DeterministicSeed(SecureRandom random, int bits, String passphrase, long creationTimeSeconds) {
this(getEntropy(random, bits), passphrase, creationTimeSeconds);
this(getEntropy(random, bits), checkNotNull(passphrase), creationTimeSeconds);
}
/**
@@ -101,8 +102,9 @@ public class DeterministicSeed implements EncryptableItem {
* @param creationTimeSeconds When the seed was originally created, UNIX time.
*/
public DeterministicSeed(byte[] entropy, String passphrase, long creationTimeSeconds) {
Preconditions.checkArgument(entropy.length % 4 == 0, "entropy size in bits not divisible by 32");
Preconditions.checkArgument(entropy.length * 8 >= DEFAULT_SEED_ENTROPY_BITS, "entropy size too small");
checkArgument(entropy.length % 4 == 0, "entropy size in bits not divisible by 32");
checkArgument(entropy.length * 8 >= DEFAULT_SEED_ENTROPY_BITS, "entropy size too small");
checkNotNull(passphrase);
try {
this.mnemonicCode = MnemonicCode.INSTANCE.toMnemonic(entropy);
@@ -116,7 +118,7 @@ public class DeterministicSeed implements EncryptableItem {
}
private static byte[] getEntropy(SecureRandom random, int bits) {
Preconditions.checkArgument(bits <= MAX_SEED_ENTROPY_BITS, "requested entropy size too large");
checkArgument(bits <= MAX_SEED_ENTROPY_BITS, "requested entropy size too large");
byte[] seed = new byte[bits / 8];
random.nextBytes(seed);

View File

@@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.protobuf.ByteString;
import org.bitcoinj.wallet.AllRandomKeysRotating;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
@@ -68,7 +69,7 @@ public class KeyChainGroup implements KeyBag {
private BasicKeyChain basic;
private NetworkParameters params;
private final LinkedList<DeterministicKeyChain> chains;
protected final LinkedList<DeterministicKeyChain> chains;
private final EnumMap<KeyChain.KeyPurpose, DeterministicKey> currentKeys;
private EnumMap<KeyChain.KeyPurpose, Address> currentAddresses;
@@ -630,12 +631,14 @@ public class KeyChainGroup implements KeyBag {
* and you should provide the users encryption key.
* @return the DeterministicKeyChain that was created by the upgrade.
*/
public DeterministicKeyChain upgradeToDeterministic(long keyRotationTimeSecs, @Nullable KeyParameter aesKey) throws DeterministicUpgradeRequiresPassword {
checkState(chains.isEmpty());
public DeterministicKeyChain upgradeToDeterministic(long keyRotationTimeSecs, @Nullable KeyParameter aesKey) throws DeterministicUpgradeRequiresPassword, AllRandomKeysRotating {
checkState(basic.numKeys() > 0);
checkArgument(keyRotationTimeSecs >= 0);
ECKey keyToUse = basic.findOldestKeyAfter(keyRotationTimeSecs);
checkArgument(keyToUse != null, "All keys are considered rotating, so we cannot upgrade deterministically.");
// Subtract one because the key rotation time might have been set to the creation time of the first known good
// key, in which case, that's the one we want to find.
ECKey keyToUse = basic.findOldestKeyAfter(keyRotationTimeSecs - 1);
if (keyToUse == null)
throw new AllRandomKeysRotating();
if (keyToUse.isEncrypted()) {
if (aesKey == null) {
@@ -658,7 +661,12 @@ public class KeyChainGroup implements KeyBag {
throw new IllegalStateException("AES Key was provided but wallet is not encrypted.");
}
log.info("Auto-upgrading pre-HD wallet using oldest non-rotating private key");
if (chains.isEmpty()) {
log.info("Auto-upgrading pre-HD wallet to HD!");
} else {
log.info("Wallet with existing HD chain is being re-upgraded due to change in key rotation time.");
}
log.info("Instantiating new HD chain using oldest non-rotating private key (address: {})", keyToUse.toAddress(params));
byte[] entropy = checkNotNull(keyToUse.getSecretBytes());
// Private keys should be at least 128 bits long.
checkState(entropy.length >= DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS / 8);

View File

@@ -97,9 +97,9 @@ public class MarriedKeyChain extends DeterministicKeyChain {
if (threshold == 0)
threshold = (followingKeys.size() + 1) / 2 + 1;
if (random != null) {
chain = new MarriedKeyChain(random, bits, passphrase, seedCreationTimeSecs);
chain = new MarriedKeyChain(random, bits, getPassphrase(), seedCreationTimeSecs);
} else if (entropy != null) {
chain = new MarriedKeyChain(entropy, passphrase, seedCreationTimeSecs);
chain = new MarriedKeyChain(entropy, getPassphrase(), seedCreationTimeSecs);
} else {
chain = new MarriedKeyChain(seed);
}

View File

@@ -0,0 +1,7 @@
package org.bitcoinj.wallet;
/**
* Indicates that an attempt was made to upgrade a random wallet to deterministic, but there were no non-rotating
* random keys to use as source material for the seed. Add a non-compromised key first!
*/
public class AllRandomKeysRotating extends RuntimeException {}

View File

@@ -317,9 +317,21 @@ public class ECKeyTest {
@Test
public void testToString() throws Exception {
ECKey key = ECKey.fromPrivate(BigInteger.TEN).decompress(); // An example private key.
NetworkParameters params = MainNetParams.get();
assertEquals("ECKey{pub HEX=04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7, isEncrypted=false}", key.toString());
assertEquals("ECKey{pub HEX=04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7, priv HEX=000000000000000000000000000000000000000000000000000000000000000a, priv WIF=5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreBoNWTw6, isEncrypted=false}", key.toStringWithPrivate(params));
}
assertEquals("ECKey{pub=04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7, isEncrypted=false}", key.toString());
assertEquals("ECKey{pub=04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7, priv=0a, isEncrypted=false}", key.toStringWithPrivate());
@Test
public void testGetPrivateKeyAsHex() throws Exception {
ECKey key = ECKey.fromPrivate(BigInteger.TEN).decompress(); // An example private key.
assertEquals("000000000000000000000000000000000000000000000000000000000000000a", key.getPrivateKeyAsHex());
}
@Test
public void testGetPublicKeyAsHex() throws Exception {
ECKey key = ECKey.fromPrivate(BigInteger.TEN).decompress(); // An example private key.
assertEquals("04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7", key.getPublicKeyAsHex());
}
@Test

View File

@@ -52,6 +52,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static com.dogecoin.dogecoinj.core.Coin.*;
import static com.dogecoin.dogecoinj.core.Utils.HEX;
@@ -78,6 +79,7 @@ public class WalletTest extends TestWithWallet {
@Override
public void setUp() throws Exception {
super.setUp();
// TODO: Move these fields into the right tests so we don't create two wallets for every test case.
encryptedWallet = new Wallet(params);
myEncryptedAddress = encryptedWallet.freshReceiveKey().toAddress(params);
encryptedWallet.encrypt(PASSWORD1);
@@ -96,7 +98,6 @@ public class WalletTest extends TestWithWallet {
createMarriedWallet(threshold, numKeys, true);
}
private void createMarriedWallet(int threshold, int numKeys, boolean addSigners) throws BlockStoreException {
wallet = new Wallet(params);
blockStore = new MemoryBlockStore(params);
@@ -1956,43 +1957,45 @@ public class WalletTest extends TestWithWallet {
// Generate a ton of small outputs
StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
int i = 0;
while (i <= CENT.divide(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)) {
Transaction tx = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
Coin tenThousand = Coin.valueOf(10000);
while (i <= 100) {
Transaction tx = createFakeTxWithChangeAddress(params, tenThousand, myAddress, notMyAddr);
tx.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
}
Coin balance = wallet.getBalance();
// Create a spend that will throw away change (category 3 type 2 in which the change causes fee which is worth more than change)
SendRequest request1 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(SATOSHI));
SendRequest request1 = SendRequest.to(notMyAddr, balance.subtract(SATOSHI));
wallet.completeTx(request1);
assertEquals(SATOSHI, request1.tx.getFee());
assertEquals(request1.tx.getInputs().size(), i); // We should have spent all inputs
// Give us one more input...
Transaction tx1 = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
Transaction tx1 = createFakeTxWithChangeAddress(params, tenThousand, myAddress, notMyAddr);
tx1.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
// ... and create a spend that will throw away change (category 3 type 1 in which the change causes dust output)
SendRequest request2 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(SATOSHI));
SendRequest request2 = SendRequest.to(notMyAddr, balance.subtract(SATOSHI));
wallet.completeTx(request2);
assertEquals(SATOSHI, request2.tx.getFee());
assertEquals(request2.tx.getInputs().size(), i - 1); // We should have spent all inputs - 1
// Give us one more input...
Transaction tx2 = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
Transaction tx2 = createFakeTxWithChangeAddress(params, tenThousand, myAddress, notMyAddr);
tx2.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
// ... and create a spend that will throw away change (category 3 type 1 in which the change causes dust output)
// but that also could have been category 2 if it wanted
SendRequest request3 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(SATOSHI));
SendRequest request3 = SendRequest.to(notMyAddr, CENT.add(tenThousand).subtract(SATOSHI));
wallet.completeTx(request3);
assertEquals(SATOSHI, request3.tx.getFee());
assertEquals(request3.tx.getInputs().size(), i - 2); // We should have spent all inputs - 2
//
SendRequest request4 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(SATOSHI));
SendRequest request4 = SendRequest.to(notMyAddr, balance.subtract(SATOSHI));
request4.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.divide(request3.tx.bitcoinSerialize().length);
wallet.completeTx(request4);
assertEquals(SATOSHI, request4.tx.getFee());
@@ -2000,24 +2003,24 @@ public class WalletTest extends TestWithWallet {
// Give us a few more inputs...
while (wallet.getBalance().compareTo(CENT.multiply(2)) < 0) {
Transaction tx3 = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
Transaction tx3 = createFakeTxWithChangeAddress(params, tenThousand, myAddress, notMyAddr);
tx3.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
}
// ...that is just slightly less than is needed for category 1
SendRequest request5 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(SATOSHI));
SendRequest request5 = SendRequest.to(notMyAddr, CENT.add(tenThousand).subtract(SATOSHI));
wallet.completeTx(request5);
assertEquals(SATOSHI, request5.tx.getFee());
assertEquals(1, request5.tx.getOutputs().size()); // We should have no change output
// Give us one more input...
Transaction tx4 = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
Transaction tx4 = createFakeTxWithChangeAddress(params, tenThousand, myAddress, notMyAddr);
tx4.getInput(0).setSequenceNumber(i); // Keep every transaction unique
wallet.receiveFromBlock(tx4, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
// ... that puts us in category 1 (no fee!)
SendRequest request6 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(SATOSHI));
SendRequest request6 = SendRequest.to(notMyAddr, CENT.add(tenThousand).subtract(SATOSHI));
wallet.completeTx(request6);
assertEquals(ZERO, request6.tx.getFee());
assertEquals(2, request6.tx.getOutputs().size()); // We should have a change output
@@ -2288,7 +2291,6 @@ public class WalletTest extends TestWithWallet {
wallet = new Wallet(params);
// Watch out for wallet-initiated broadcasts.
MockTransactionBroadcaster broadcaster = new MockTransactionBroadcaster(wallet);
wallet.setKeyRotationEnabled(true);
// Send three cents to two different random keys, then add a key and mark the initial keys as compromised.
ECKey key1 = new ECKey();
key1.setCreationTimeSeconds(Utils.currentTimeSeconds() - (86400 * 2));
@@ -2303,14 +2305,11 @@ public class WalletTest extends TestWithWallet {
assertEquals(0, broadcaster.size());
assertFalse(wallet.isKeyRotating(key1));
// We got compromised! We have an old style random-only wallet. So let's upgrade to HD: for that we need a fresh
// random key that's not rotating as the wallet won't create a new seed for us, it'll just refuse to upgrade.
// We got compromised!
Utils.rollMockClock(1);
ECKey key3 = new ECKey();
wallet.importKey(key3);
wallet.setKeyRotationTime(compromiseTime);
assertTrue(wallet.isKeyRotating(key1));
wallet.maybeDoMaintenance(null, true);
wallet.doMaintenance(null, true);
Transaction tx = broadcaster.waitForTransactionAndSucceed();
final Coin THREE_CENTS = CENT.add(CENT).add(CENT);
@@ -2327,12 +2326,12 @@ public class WalletTest extends TestWithWallet {
// Now receive some more money to the newly derived address via a new block and check that nothing happens.
sendMoneyToWallet(wallet, CENT, toAddress, AbstractBlockChain.NewBlockType.BEST_CHAIN);
assertTrue(wallet.maybeDoMaintenance(null, true).get().isEmpty());
assertTrue(wallet.doMaintenance(null, true).get().isEmpty());
assertEquals(0, broadcaster.size());
// Receive money via a new block on key1 and ensure it shows up as a maintenance task.
sendMoneyToWallet(wallet, CENT, key1.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.maybeDoMaintenance(null, true);
wallet.doMaintenance(null, true);
tx = broadcaster.waitForTransactionAndSucceed();
assertNotNull(wallet.findKeyFromPubHash(tx.getOutput(0).getScriptPubKey().getPubKeyHash()));
log.info("Unexpected thing: {}", tx);
@@ -2375,14 +2374,68 @@ public class WalletTest extends TestWithWallet {
// A day later, we get compromised.
Utils.rollMockClock(86400);
wallet.setKeyRotationTime(Utils.currentTimeSeconds());
wallet.setKeyRotationEnabled(true);
List<Transaction> txns = wallet.maybeDoMaintenance(null, false).get();
List<Transaction> txns = wallet.doMaintenance(null, false).get();
assertEquals(1, txns.size());
DeterministicKey watchKey2 = wallet.getWatchingKey();
assertNotEquals(watchKey1, watchKey2);
}
@SuppressWarnings("ConstantConditions")
@Test
public void keyRotationHD2() throws Exception {
// Check we handle the following scenario: a weak random key is created, then some good random keys are created
// but the weakness of the first isn't known yet. The wallet is upgraded to HD based on the weak key. Later, we
// find out about the weakness and set the rotation time to after the bad key's creation date. A new HD chain
// should be created based on the oldest known good key and the old chain + bad random key should rotate to it.
// We fix the private keys just to make the test deterministic (last byte differs).
Utils.setMockClock();
ECKey badKey = ECKey.fromPrivate(Utils.HEX.decode("00905b93f990267f4104f316261fc10f9f983551f9ef160854f40102eb71cffdbb"));
badKey.setCreationTimeSeconds(Utils.currentTimeSeconds());
Utils.rollMockClock(86400);
ECKey goodKey = ECKey.fromPrivate(Utils.HEX.decode("00905b93f990267f4104f316261fc10f9f983551f9ef160854f40102eb71cffdcc"));
goodKey.setCreationTimeSeconds(Utils.currentTimeSeconds());
// Do an upgrade based on the bad key.
final AtomicReference<List<DeterministicKeyChain>> fChains = new AtomicReference<List<DeterministicKeyChain>>();
KeyChainGroup kcg = new KeyChainGroup(params) {
{
fChains.set(chains);
}
};
kcg.importKeys(badKey, goodKey);
Utils.rollMockClock(86400);
wallet = new Wallet(params, kcg); // This avoids the automatic HD initialisation
assertTrue(fChains.get().isEmpty());
wallet.upgradeToDeterministic(null);
DeterministicKey badWatchingKey = wallet.getWatchingKey();
assertEquals(badKey.getCreationTimeSeconds(), badWatchingKey.getCreationTimeSeconds());
sendMoneyToWallet(wallet, CENT, badWatchingKey.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN);
// Now we set the rotation time to the time we started making good keys. This should create a new HD chain.
wallet.setKeyRotationTime(goodKey.getCreationTimeSeconds());
List<Transaction> txns = wallet.doMaintenance(null, false).get();
assertEquals(1, txns.size());
Address output = txns.get(0).getOutput(0).getAddressFromP2PKHScript(params);
ECKey usedKey = wallet.findKeyFromPubHash(output.getHash160());
assertEquals(goodKey.getCreationTimeSeconds(), usedKey.getCreationTimeSeconds());
assertEquals(goodKey.getCreationTimeSeconds(), wallet.freshReceiveKey().getCreationTimeSeconds());
assertEquals("mrM3TpCnav5YQuVA1xLercCGJH4DXujMtv", usedKey.toAddress(params).toString());
DeterministicKeyChain c = fChains.get().get(1);
assertEquals(c.getEarliestKeyCreationTime(), goodKey.getCreationTimeSeconds());
assertEquals(2, fChains.get().size());
// Commit the maint txns.
wallet.commitTx(txns.get(0));
// Check next maintenance does nothing.
assertTrue(wallet.doMaintenance(null, false).get().isEmpty());
assertEquals(c, fChains.get().get(1));
assertEquals(2, fChains.get().size());
}
@Test(expected = IllegalArgumentException.class)
public void importOfHDKeyForbidden() throws Exception {
wallet.importKey(wallet.freshReceiveKey());
@@ -2400,13 +2453,12 @@ public class WalletTest extends TestWithWallet {
}
MockTransactionBroadcaster broadcaster = new MockTransactionBroadcaster(wallet);
wallet.setKeyRotationEnabled(true);
Date compromise = Utils.now();
Utils.rollMockClock(86400);
wallet.freshReceiveKey();
wallet.setKeyRotationTime(compromise);
wallet.maybeDoMaintenance(null, true);
wallet.doMaintenance(null, true);
Transaction tx = broadcaster.waitForTransactionAndSucceed();
final Coin valueSentToMe = tx.getValueSentToMe(wallet);

View File

@@ -68,6 +68,14 @@ public class DeterministicKeyChainTest {
key3.sign(Sha256Hash.ZERO_HASH);
}
@Test
public void getKeys() throws Exception {
chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
chain.getKey(KeyChain.KeyPurpose.CHANGE);
chain.maybeLookAhead();
assertEquals(2, chain.getKeys(false).size());
}
@Test
public void signMessage() throws Exception {
ECKey key = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);

View File

@@ -446,9 +446,10 @@ public class WalletTool {
long rotationTimeSecs = Utils.currentTimeSeconds();
if (options.has(dateFlag)) {
rotationTimeSecs = options.valueOf(dateFlag).getTime() / 1000;
} else if (options.has(unixtimeFlag)) {
rotationTimeSecs = options.valueOf(unixtimeFlag);
}
log.info("Setting wallet key rotation time to {}", rotationTimeSecs);
wallet.setKeyRotationEnabled(true);
wallet.setKeyRotationTime(rotationTimeSecs);
KeyParameter aesKey = null;
if (wallet.isEncrypted()) {
@@ -456,7 +457,7 @@ public class WalletTool {
if (aesKey == null)
return;
}
Futures.getUnchecked(wallet.maybeDoMaintenance(aesKey, true));
Futures.getUnchecked(wallet.doMaintenance(aesKey, true));
}
private static void encrypt() {

View File

@@ -117,14 +117,7 @@ public class Main extends Application {
// or progress widget to keep the user engaged whilst we initialise, but we don't.
if (params == RegTestParams.get()) {
bitcoin.connectToLocalHost(); // You should run a regtest mode bitcoind locally.
} else if (params == MainNetParams.get()) {
// Checkpoints are block headers that ship inside our app: for a new user, we pick the last header
// in the checkpoints file and then download the rest from the network. It makes things much faster.
// Checkpoint files are made using the BuildCheckpoints tool and usually we have to download the
// last months worth or more (takes a few seconds).
bitcoin.setCheckpoints(getClass().getResourceAsStream("checkpoints"));
} else if (params == TestNet3Params.get()) {
bitcoin.setCheckpoints(getClass().getResourceAsStream("org.bitcoin.test.checkpoints"));
// As an example!
bitcoin.useTor();
}