mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-07-31 12:01:24 +00:00
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:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
@@ -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!
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -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");
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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())
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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 {}
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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() {
|
||||
|
@@ -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();
|
||||
}
|
||||
|
Reference in New Issue
Block a user