mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-12 10:15:52 +00:00
Wallet: Add static constructor createBasic() which creates a wallet with just a basic keychain, and no key derivation.
This commit is contained in:
parent
6278dc39b1
commit
d35583236a
@ -72,7 +72,8 @@ public class KeyChainGroup implements KeyBag {
|
||||
|
||||
private BasicKeyChain basic;
|
||||
private NetworkParameters params;
|
||||
protected final LinkedList<DeterministicKeyChain> chains;
|
||||
// Keychains for deterministically derived keys. If this is null, no chains should be created automatically.
|
||||
protected final @Nullable LinkedList<DeterministicKeyChain> chains;
|
||||
// currentKeys is used for normal, non-multisig/married wallets. currentAddresses is used when we're handing out
|
||||
// P2SH addresses. They're mutually exclusive.
|
||||
private final EnumMap<KeyChain.KeyPurpose, DeterministicKey> currentKeys;
|
||||
@ -81,6 +82,11 @@ public class KeyChainGroup implements KeyBag {
|
||||
private int lookaheadSize = -1;
|
||||
private int lookaheadThreshold = -1;
|
||||
|
||||
/** Creates a keychain group with just a basic chain. No deterministic chains will be created automatically. */
|
||||
public static KeyChainGroup createBasic(NetworkParameters params) {
|
||||
return new KeyChainGroup(params, new BasicKeyChain(), null, null, null);
|
||||
}
|
||||
|
||||
/** Creates a keychain group with no basic chain, and a single, lazily created HD chain. */
|
||||
public KeyChainGroup(NetworkParameters params) {
|
||||
this(params, null, new ArrayList<DeterministicKeyChain>(1), null, null);
|
||||
@ -118,19 +124,19 @@ public class KeyChainGroup implements KeyBag {
|
||||
: DeterministicKeyChain.builder().spend(accountKey).build()), null, null);
|
||||
}
|
||||
|
||||
private KeyChainGroup(NetworkParameters params, @Nullable BasicKeyChain basicKeyChain, List<DeterministicKeyChain> chains,
|
||||
private KeyChainGroup(NetworkParameters params, @Nullable BasicKeyChain basicKeyChain, @Nullable List<DeterministicKeyChain> chains,
|
||||
@Nullable EnumMap<KeyChain.KeyPurpose, DeterministicKey> currentKeys, @Nullable KeyCrypter crypter) {
|
||||
this.params = params;
|
||||
this.basic = basicKeyChain == null ? new BasicKeyChain() : basicKeyChain;
|
||||
this.chains = new LinkedList<>(checkNotNull(chains));
|
||||
this.chains = chains != null ? new LinkedList<DeterministicKeyChain>(chains) : null;
|
||||
this.keyCrypter = crypter;
|
||||
this.currentKeys = currentKeys == null
|
||||
? new EnumMap<KeyChain.KeyPurpose, DeterministicKey>(KeyChain.KeyPurpose.class)
|
||||
: currentKeys;
|
||||
this.currentAddresses = new EnumMap<>(KeyChain.KeyPurpose.class);
|
||||
maybeLookaheadScripts();
|
||||
|
||||
if (isMarried()) {
|
||||
maybeLookaheadScripts();
|
||||
for (Map.Entry<KeyChain.KeyPurpose, DeterministicKey> entry : this.currentKeys.entrySet()) {
|
||||
Address address = ScriptBuilder
|
||||
.createP2SHOutputScript(getActiveKeyChain().getRedeemData(entry.getValue()).redeemScript)
|
||||
@ -140,6 +146,11 @@ public class KeyChainGroup implements KeyBag {
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if it contains any deterministic keychain or one could be created. */
|
||||
public boolean isSupportsDeterministicChains() {
|
||||
return chains != null;
|
||||
}
|
||||
|
||||
// This keeps married redeem data in sync with the number of keys issued
|
||||
private void maybeLookaheadScripts() {
|
||||
for (DeterministicKeyChain chain : chains) {
|
||||
@ -149,6 +160,7 @@ 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);
|
||||
@ -159,6 +171,7 @@ public class KeyChainGroup implements KeyBag {
|
||||
* 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);
|
||||
for (ListenerRegistration<KeyChainEventListener> registration : basic.getListeners())
|
||||
chain.addEventListener(registration.listener, registration.executor);
|
||||
@ -268,6 +281,7 @@ 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.");
|
||||
@ -287,10 +301,10 @@ public class KeyChainGroup implements KeyBag {
|
||||
* for more information.
|
||||
*/
|
||||
public void setLookaheadSize(int lookaheadSize) {
|
||||
checkState(isSupportsDeterministicChains(), "doesn't support deterministic chains");
|
||||
this.lookaheadSize = lookaheadSize;
|
||||
for (DeterministicKeyChain chain : chains) {
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
chain.setLookaheadSize(lookaheadSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -299,6 +313,7 @@ public class KeyChainGroup implements KeyBag {
|
||||
* for more information.
|
||||
*/
|
||||
public int getLookaheadSize() {
|
||||
checkState(isSupportsDeterministicChains(), "doesn't support deterministic chains");
|
||||
if (lookaheadSize == -1)
|
||||
return getActiveKeyChain().getLookaheadSize();
|
||||
else
|
||||
@ -311,9 +326,9 @@ public class KeyChainGroup implements KeyBag {
|
||||
* for more information.
|
||||
*/
|
||||
public void setLookaheadThreshold(int num) {
|
||||
for (DeterministicKeyChain chain : chains) {
|
||||
checkState(isSupportsDeterministicChains(), "doesn't support deterministic chains");
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
chain.setLookaheadThreshold(num);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -322,6 +337,7 @@ public class KeyChainGroup implements KeyBag {
|
||||
* for more information.
|
||||
*/
|
||||
public int getLookaheadThreshold() {
|
||||
checkState(isSupportsDeterministicChains(), "doesn't support deterministic chains");
|
||||
if (lookaheadThreshold == -1)
|
||||
return getActiveKeyChain().getLookaheadThreshold();
|
||||
else
|
||||
@ -397,10 +413,10 @@ public class KeyChainGroup implements KeyBag {
|
||||
ECKey result;
|
||||
if ((result = basic.findKeyFromPubHash(pubKeyHash)) != null)
|
||||
return result;
|
||||
for (DeterministicKeyChain chain : chains) {
|
||||
if ((result = chain.findKeyFromPubHash(pubKeyHash)) != null)
|
||||
return result;
|
||||
}
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
if ((result = chain.findKeyFromPubHash(pubKeyHash)) != null)
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -409,11 +425,13 @@ public class KeyChainGroup implements KeyBag {
|
||||
* See {@link DeterministicKeyChain#markKeyAsUsed(DeterministicKey)} for more info on this.
|
||||
*/
|
||||
public void markPubKeyHashAsUsed(byte[] pubKeyHash) {
|
||||
for (DeterministicKeyChain chain : chains) {
|
||||
DeterministicKey key;
|
||||
if ((key = chain.markPubHashAsUsed(pubKeyHash)) != null) {
|
||||
maybeMarkCurrentKeyAsUsed(key);
|
||||
return;
|
||||
if (chains != null) {
|
||||
for (DeterministicKeyChain chain : chains) {
|
||||
DeterministicKey key;
|
||||
if ((key = chain.markPubHashAsUsed(pubKeyHash)) != null) {
|
||||
maybeMarkCurrentKeyAsUsed(key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -446,9 +464,10 @@ public class KeyChainGroup implements KeyBag {
|
||||
public boolean hasKey(ECKey key) {
|
||||
if (basic.hasKey(key))
|
||||
return true;
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
if (chain.hasKey(key))
|
||||
return true;
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
if (chain.hasKey(key))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -458,10 +477,10 @@ public class KeyChainGroup implements KeyBag {
|
||||
ECKey result;
|
||||
if ((result = basic.findKeyFromPubKey(pubKey)) != null)
|
||||
return result;
|
||||
for (DeterministicKeyChain chain : chains) {
|
||||
if ((result = chain.findKeyFromPubKey(pubKey)) != null)
|
||||
return result;
|
||||
}
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
if ((result = chain.findKeyFromPubKey(pubKey)) != null)
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -470,11 +489,13 @@ public class KeyChainGroup implements KeyBag {
|
||||
* See {@link DeterministicKeyChain#markKeyAsUsed(DeterministicKey)} for more info on this.
|
||||
*/
|
||||
public void markPubKeyAsUsed(byte[] pubkey) {
|
||||
for (DeterministicKeyChain chain : chains) {
|
||||
DeterministicKey key;
|
||||
if ((key = chain.markPubKeyAsUsed(pubkey)) != null) {
|
||||
maybeMarkCurrentKeyAsUsed(key);
|
||||
return;
|
||||
if (chains != null) {
|
||||
for (DeterministicKeyChain chain : chains) {
|
||||
DeterministicKey key;
|
||||
if ((key = chain.markPubKeyAsUsed(pubkey)) != null) {
|
||||
maybeMarkCurrentKeyAsUsed(key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -482,8 +503,9 @@ public class KeyChainGroup implements KeyBag {
|
||||
/** Returns the number of keys managed by this group, including the lookahead buffers. */
|
||||
public int numKeys() {
|
||||
int result = basic.numKeys();
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
result += chain.numKeys();
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
result += chain.numKeys();
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -503,7 +525,7 @@ public class KeyChainGroup implements KeyBag {
|
||||
* @see org.bitcoinj.wallet.MarriedKeyChain
|
||||
*/
|
||||
public final boolean isMarried() {
|
||||
return !chains.isEmpty() && getActiveKeyChain().isMarried();
|
||||
return chains != null && !chains.isEmpty() && getActiveKeyChain().isMarried();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -520,13 +542,14 @@ public class KeyChainGroup implements KeyBag {
|
||||
// This code must be exception safe.
|
||||
BasicKeyChain newBasic = basic.toEncrypted(keyCrypter, aesKey);
|
||||
List<DeterministicKeyChain> newChains = new ArrayList<>(chains.size());
|
||||
if (chains.isEmpty() && basic.numKeys() == 0) {
|
||||
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();
|
||||
}
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
newChains.add(chain.toEncrypted(keyCrypter, aesKey));
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
newChains.add(chain.toEncrypted(keyCrypter, aesKey));
|
||||
this.keyCrypter = keyCrypter;
|
||||
basic = newBasic;
|
||||
chains.clear();
|
||||
@ -544,8 +567,9 @@ public class KeyChainGroup implements KeyBag {
|
||||
checkNotNull(aesKey);
|
||||
BasicKeyChain newBasic = basic.toDecrypted(aesKey);
|
||||
List<DeterministicKeyChain> newChains = new ArrayList<>(chains.size());
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
newChains.add(chain.toDecrypted(aesKey));
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
newChains.add(chain.toDecrypted(aesKey));
|
||||
|
||||
this.keyCrypter = null;
|
||||
basic = newBasic;
|
||||
@ -567,7 +591,7 @@ public class KeyChainGroup implements KeyBag {
|
||||
public boolean isWatching() {
|
||||
BasicKeyChain.State basicState = basic.isWatching();
|
||||
BasicKeyChain.State activeState = BasicKeyChain.State.EMPTY;
|
||||
if (!chains.isEmpty()) {
|
||||
if (chains != null && !chains.isEmpty()) {
|
||||
if (getActiveKeyChain().isWatching())
|
||||
activeState = BasicKeyChain.State.WATCHING;
|
||||
else
|
||||
@ -598,16 +622,17 @@ public class KeyChainGroup implements KeyBag {
|
||||
|
||||
public long getEarliestKeyCreationTime() {
|
||||
long time = basic.getEarliestKeyCreationTime(); // Long.MAX_VALUE if empty.
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
time = Math.min(time, chain.getEarliestKeyCreationTime());
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
time = Math.min(time, chain.getEarliestKeyCreationTime());
|
||||
return time;
|
||||
}
|
||||
|
||||
public int getBloomFilterElementCount() {
|
||||
int result = basic.numBloomFilterEntries();
|
||||
for (DeterministicKeyChain chain : chains) {
|
||||
result += chain.numBloomFilterEntries();
|
||||
}
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
result += chain.numBloomFilterEntries();
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -615,10 +640,9 @@ public class KeyChainGroup implements KeyBag {
|
||||
BloomFilter filter = new BloomFilter(size, falsePositiveRate, nTweak);
|
||||
if (basic.numKeys() > 0)
|
||||
filter.merge(basic.getFilter(size, falsePositiveRate, nTweak));
|
||||
|
||||
for (DeterministicKeyChain chain : chains) {
|
||||
filter.merge(chain.getFilter(size, falsePositiveRate, nTweak));
|
||||
}
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
filter.merge(chain.getFilter(size, falsePositiveRate, nTweak));
|
||||
return filter;
|
||||
}
|
||||
|
||||
@ -636,15 +660,17 @@ public class KeyChainGroup implements KeyBag {
|
||||
checkNotNull(listener);
|
||||
checkNotNull(executor);
|
||||
basic.addEventListener(listener, executor);
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
chain.addEventListener(listener, executor);
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
chain.addEventListener(listener, executor);
|
||||
}
|
||||
|
||||
/** Removes a listener for events that are run when keys are added. */
|
||||
public boolean removeEventListener(KeyChainEventListener listener) {
|
||||
checkNotNull(listener);
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
chain.removeEventListener(listener);
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
chain.removeEventListener(listener);
|
||||
return basic.removeEventListener(listener);
|
||||
}
|
||||
|
||||
@ -655,10 +681,9 @@ public class KeyChainGroup implements KeyBag {
|
||||
result = basic.serializeToProtobuf();
|
||||
else
|
||||
result = Lists.newArrayList();
|
||||
for (DeterministicKeyChain chain : chains) {
|
||||
List<Protos.Key> protos = chain.serializeToProtobuf();
|
||||
result.addAll(protos);
|
||||
}
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
result.addAll(chain.serializeToProtobuf());
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -709,6 +734,7 @@ public class KeyChainGroup implements KeyBag {
|
||||
* @return the DeterministicKeyChain that was created by the upgrade.
|
||||
*/
|
||||
public DeterministicKeyChain upgradeToDeterministic(long keyRotationTimeSecs, @Nullable KeyParameter aesKey) throws DeterministicUpgradeRequiresPassword, AllRandomKeysRotating {
|
||||
checkState(isSupportsDeterministicChains(), "doesn't support deterministic chains");
|
||||
checkState(basic.numKeys() > 0);
|
||||
checkArgument(keyRotationTimeSecs >= 0);
|
||||
// Subtract one because the key rotation time might have been set to the creation time of the first known good
|
||||
@ -764,7 +790,7 @@ public class KeyChainGroup implements KeyBag {
|
||||
|
||||
/** Returns true if the group contains random keys but no HD chains. */
|
||||
public boolean isDeterministicUpgradeRequired() {
|
||||
return basic.numKeys() > 0 && chains.isEmpty();
|
||||
return basic.numKeys() > 0 && chains != null && chains.isEmpty();
|
||||
}
|
||||
|
||||
private static EnumMap<KeyChain.KeyPurpose, DeterministicKey> createCurrentKeysMap(List<DeterministicKeyChain> chains) {
|
||||
@ -814,13 +840,15 @@ public class KeyChainGroup implements KeyBag {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
if (basic != null)
|
||||
builder.append(basic.toString(includePrivateKeys, aesKey, params));
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
builder.append(chain.toString(includePrivateKeys, aesKey, params)).append('\n');
|
||||
if (chains != null)
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
builder.append(chain.toString(includePrivateKeys, aesKey, params)).append('\n');
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/** Returns a copy of the current list of chains. */
|
||||
public List<DeterministicKeyChain> getDeterministicKeyChains() {
|
||||
checkState(isSupportsDeterministicChains(), "doesn't support deterministic chains");
|
||||
return new ArrayList<>(chains);
|
||||
}
|
||||
/**
|
||||
@ -828,6 +856,7 @@ public class KeyChainGroup implements KeyBag {
|
||||
* lookahead and thus the Bloom filter that was previously calculated has become stale.
|
||||
*/
|
||||
public int getCombinedKeyLookaheadEpochs() {
|
||||
checkState(isSupportsDeterministicChains(), "doesn't support deterministic chains");
|
||||
int epoch = 0;
|
||||
for (DeterministicKeyChain chain : chains)
|
||||
epoch += chain.getKeyLookaheadEpoch();
|
||||
|
@ -265,6 +265,15 @@ public class Wallet extends BaseTaggableObject
|
||||
this(context, new KeyChainGroup(context.getParams()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new, empty wallet with just a basic keychain and no transactions. No deterministic chains will be created
|
||||
* automatically. This is meant for when you just want to import a few keys and operate on them.
|
||||
* @param params network parameters
|
||||
*/
|
||||
public static Wallet createBasic(NetworkParameters params) {
|
||||
return new Wallet(params, KeyChainGroup.createBasic(params));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param params network parameters
|
||||
* @param seed deterministic seed
|
||||
@ -352,12 +361,13 @@ public class Wallet extends BaseTaggableObject
|
||||
this.context = checkNotNull(context);
|
||||
this.params = checkNotNull(context.getParams());
|
||||
this.keyChainGroup = checkNotNull(keyChainGroup);
|
||||
if (params.getId().equals(NetworkParameters.ID_UNITTESTNET))
|
||||
if (this.keyChainGroup.isSupportsDeterministicChains()
|
||||
&& params.getId().equals(NetworkParameters.ID_UNITTESTNET))
|
||||
this.keyChainGroup.setLookaheadSize(5); // Cut down excess computation for unit tests.
|
||||
// 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.numKeys() == 0)
|
||||
if (this.keyChainGroup.isSupportsDeterministicChains() && this.keyChainGroup.numKeys() == 0)
|
||||
this.keyChainGroup.createAndActivateNewHDChain();
|
||||
watchedScripts = Sets.newHashSet();
|
||||
unspent = new HashMap<>();
|
||||
|
@ -134,6 +134,20 @@ public class WalletTest extends TestWithWallet {
|
||||
wallet.addAndActivateHDChain(chain);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createBasic() {
|
||||
Wallet wallet = Wallet.createBasic(UNITTEST);
|
||||
assertEquals(0, wallet.getKeyChainGroupSize());
|
||||
wallet.importKey(new ECKey());
|
||||
assertEquals(1, wallet.getKeyChainGroupSize());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void createBasic_noDerivation() {
|
||||
Wallet wallet = Wallet.createBasic(UNITTEST);
|
||||
wallet.currentReceiveAddress();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSeedAsWords1() {
|
||||
// Can't verify much here as the wallet is random each time. We could fix the RNG for the unit tests and solve.
|
||||
|
Loading…
x
Reference in New Issue
Block a user