3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-12 10:15:52 +00:00

Wallet, KeyChainGroup: Remove implicit creation and activation of a new chain.

The was likely the remains of lazy creation of wallets. Lazy creation wasn't of much use,
since wallets are usually accessed the moment after they're created. And also we introduced
a second "laziness" by only deriving a couple of keys per chain, deferring the hard work
for when bloom filtering is used.
This commit is contained in:
Andreas Schildbach 2019-02-12 12:03:37 +01:00
parent eb15ac07d3
commit 16b53836b8
6 changed files with 26 additions and 44 deletions

View File

@ -42,11 +42,10 @@ import java.util.concurrent.*;
import static com.google.common.base.Preconditions.*;
/**
* <p>A KeyChainGroup is used by the {@link Wallet} and
* manages: a {@link BasicKeyChain} object (which will normally be empty), and zero or more
* {@link DeterministicKeyChain}s. A deterministic key chain will be created lazily/on demand
* when a fresh or current key is requested, possibly being initialized from the private key bytes of the earliest non
* rotating key in the basic key chain if one is available, or from a fresh random seed if not.</p>
* <p>A KeyChainGroup is used by the {@link Wallet} and manages: a {@link BasicKeyChain} object
* (which will normally be empty), and zero or more {@link DeterministicKeyChain}s. The last added
* deterministic keychain is always the active keychain, that's the one we normally derive keys and
* addresses from.</p>
*
* <p>If a key rotation time is set, it may be necessary to add a new DeterministicKeyChain with a fresh seed
* and also preserve the old one, so funds can be swept from the rotating keys. In this case, there may be
@ -227,21 +226,13 @@ public class KeyChainGroup implements KeyBag {
}
}
/** Adds a new HD chain to the chains list, and make it the default chain (from which keys are issued). */
public void createAndActivateNewHDChain() {
checkState(isSupportsDeterministicChains(), "doesn't support deterministic chains");
// We can't do auto upgrade here because we don't know the rotation time, if any.
final DeterministicKeyChain chain = DeterministicKeyChain.builder().random(new SecureRandom()).build();
addAndActivateHDChain(chain);
}
/**
* Adds an HD chain to the chains list, and make it the default chain (from which keys are issued).
* Useful for adding a complex pre-configured keychain, such as a married wallet.
*/
public void addAndActivateHDChain(DeterministicKeyChain chain) {
checkState(isSupportsDeterministicChains(), "doesn't support deterministic chains");
log.info("Creating and activating a new HD chain: {}", chain);
log.info("Activating a new HD chain: {}", chain);
for (ListenerRegistration<KeyChainEventListener> registration : basic.getListeners())
chain.addEventListener(registration.listener, registration.executor);
if (lookaheadSize >= 0)
@ -357,16 +348,8 @@ public class KeyChainGroup implements KeyBag {
/** Returns the key chain that's used for generation of fresh/current keys. This is always the newest HD chain. */
public final DeterministicKeyChain getActiveKeyChain() {
checkState(isSupportsDeterministicChains(), "doesn't support deterministic chains");
if (chains.isEmpty()) {
if (basic.numKeys() > 0) {
log.warn("No HD chain present but random keys are: you probably deserialized an old wallet.");
// If called from the wallet (most likely) it'll try to upgrade us, as it knows the rotation time
// but not the password.
throw new DeterministicUpgradeRequiredException();
}
// Otherwise we have no HD chains and no random keys: we are a new born! So a random seed is fine.
createAndActivateNewHDChain();
}
if (chains.isEmpty())
throw new DeterministicUpgradeRequiredException();
return chains.get(chains.size() - 1);
}
@ -597,21 +580,17 @@ public class KeyChainGroup implements KeyBag {
public void encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) {
checkNotNull(keyCrypter);
checkNotNull(aesKey);
checkState(chains == null || !chains.isEmpty() || basic.numKeys() != 0, "can't encrypt entirely empty wallet");
// This code must be exception safe.
BasicKeyChain newBasic = basic.toEncrypted(keyCrypter, aesKey);
List<DeterministicKeyChain> newChains = new ArrayList<>(chains.size());
if (chains != null && chains.isEmpty() && basic.numKeys() == 0) {
// No HD chains and no random keys: encrypting an entirely empty keychain group. But we can't do that, we
// must have something to encrypt: so instantiate a new HD chain here.
createAndActivateNewHDChain();
}
List<DeterministicKeyChain> newChains = new ArrayList<>();
if (chains != null)
for (DeterministicKeyChain chain : chains)
newChains.add(chain.toEncrypted(keyCrypter, aesKey));
this.keyCrypter = keyCrypter;
basic = newBasic;
chains.clear();
chains.addAll(newChains);
this.chains.clear();
this.chains.addAll(newChains);
}
/**

View File

@ -77,6 +77,7 @@ import javax.annotation.*;
import java.io.*;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
@ -471,11 +472,6 @@ public class Wallet extends BaseTaggableObject
this.context = checkNotNull(context);
this.params = checkNotNull(context.getParams());
this.keyChainGroup = checkNotNull(keyChainGroup);
// If this keyChainGroup was created fresh just now (new wallet), make HD so a backup can be made immediately
// without having to call current/freshReceiveKey. If there are already keys in the chain of any kind then
// we're probably being deserialized so leave things alone: the API user can upgrade later.
if (this.keyChainGroup.isSupportsDeterministicChains() && this.keyChainGroup.numKeys() == 0)
this.keyChainGroup.createAndActivateNewHDChain();
watchedScripts = Sets.newHashSet();
unspent = new HashMap<>();
spent = new HashMap<>();
@ -5249,7 +5245,8 @@ public class Wallet extends BaseTaggableObject
try {
if (keyChainGroup.getImportedKeys().isEmpty()) {
log.info("All HD chains are currently rotating and we have no random keys, creating fresh HD chain ...");
keyChainGroup.createAndActivateNewHDChain();
DeterministicKeyChain chain = DeterministicKeyChain.builder().random(new SecureRandom()).build();
keyChainGroup.addAndActivateHDChain(chain);
} else {
log.info("All HD chains are currently rotating, attempting to create a new one from the next oldest non-rotating key material ...");
keyChainGroup.upgradeToDeterministic(keyRotationTimestamp, aesKey);
@ -5257,7 +5254,8 @@ public class Wallet extends BaseTaggableObject
}
} catch (AllRandomKeysRotating rotating) {
log.info(" ... no non-rotating random keys available, generating entirely new HD tree: backup required after this.");
keyChainGroup.createAndActivateNewHDChain();
DeterministicKeyChain chain = DeterministicKeyChain.builder().random(new SecureRandom()).build();
keyChainGroup.addAndActivateHDChain(chain);
}
saveNow();
}

View File

@ -37,6 +37,7 @@ import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.UnitTestParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.testing.FakeTxBuilder;
import org.bitcoinj.testing.FooWalletExtension;
@ -96,7 +97,7 @@ public class WalletProtobufSerializerTest {
myKey = new ECKey();
myKey.setCreationTimeSeconds(123456789L);
myAddress = LegacyAddress.fromKey(UNITTEST, myKey);
myWallet = new Wallet(UNITTEST, KeyChainGroup.builder(UNITTEST).build());
myWallet = new Wallet(UNITTEST, KeyChainGroup.builder(UNITTEST).fromRandom(Script.ScriptType.P2PKH).build());
myWallet.importKey(myKey);
mScriptCreationTime = new Date().getTime() / 1000 - 1234;
myWallet.addWatchedAddress(LegacyAddress.fromKey(UNITTEST, myWatchedKey), mScriptCreationTime);

View File

@ -22,6 +22,7 @@ import org.bitcoinj.core.listeners.PreMessageReceivedEventListener;
import org.bitcoinj.core.*;
import org.bitcoinj.net.*;
import org.bitcoinj.params.UnitTestParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.MemoryBlockStore;
import org.bitcoinj.utils.BriefLogFormatter;
@ -90,7 +91,8 @@ public class TestWithNetworkConnections {
// Allow subclasses to override the wallet object with their own.
if (wallet == null) {
// Reduce the number of keys we need to work with to speed up these tests.
KeyChainGroup kcg = KeyChainGroup.builder(UNITTEST).lookaheadSize(4).lookaheadThreshold(2).build();
KeyChainGroup kcg = KeyChainGroup.builder(UNITTEST).lookaheadSize(4).lookaheadThreshold(2)
.fromRandom(Script.ScriptType.P2PKH).build();
wallet = new Wallet(UNITTEST, kcg);
key = wallet.freshReceiveKey();
address = LegacyAddress.fromKey(UNITTEST, key);

View File

@ -29,6 +29,7 @@ import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.UnitTestParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.MemoryBlockStore;
import org.bitcoinj.utils.BriefLogFormatter;
@ -60,7 +61,7 @@ public class TestWithWallet {
public void setUp() throws Exception {
BriefLogFormatter.init();
Context.propagate(new Context(UNITTEST, 100, Coin.ZERO, false));
wallet = new Wallet(UNITTEST);
wallet = Wallet.createDeterministic(UNITTEST, Script.ScriptType.P2PKH);
myKey = wallet.currentReceiveKey();
myAddress = LegacyAddress.fromKey(UNITTEST, myKey);
blockStore = new MemoryBlockStore(UNITTEST);

View File

@ -3129,7 +3129,7 @@ public class WalletTest extends TestWithWallet {
@Test (expected = ECKey.MissingPrivateKeyException.class)
public void completeTxPartiallySignedThrows() throws Exception {
sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, CENT, wallet.freshReceiveKey());
sendMoneyToWallet(AbstractBlockChain.NewBlockType.BEST_CHAIN, CENT, wallet.currentReceiveKey());
SendRequest req = SendRequest.emptyWallet(OTHER_ADDRESS);
wallet.completeTx(req);
// Delete the sigs
@ -3137,6 +3137,7 @@ public class WalletTest extends TestWithWallet {
input.clearScriptBytes();
Wallet watching = Wallet.fromWatchingKey(UNITTEST, wallet.getWatchingKey().dropParent().dropPrivateBytes(),
Script.ScriptType.P2PKH);
watching.currentReceiveKey();
watching.completeTx(SendRequest.forTx(req.tx));
}
@ -3278,7 +3279,7 @@ public class WalletTest extends TestWithWallet {
@Test
public void keyEvents() throws Exception {
// Check that we can register an event listener, generate some keys and the callbacks are invoked properly.
wallet = new Wallet(UNITTEST, KeyChainGroup.builder(UNITTEST).build());
wallet = new Wallet(UNITTEST, KeyChainGroup.builder(UNITTEST).fromRandom(Script.ScriptType.P2PKH).build());
final List<ECKey> keys = Lists.newLinkedList();
wallet.addKeyChainEventListener(Threading.SAME_THREAD, new KeyChainEventListener() {
@Override