mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-11-03 05:57:21 +00:00
allow DKC derivation path to be overridden
This commit is contained in:
@@ -16,8 +16,9 @@
|
||||
|
||||
package org.bitcoinj.wallet;
|
||||
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.bitcoinj.crypto.KeyCrypter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.bitcoinj.crypto.*;
|
||||
import org.bitcoinj.store.UnreadableWalletException;
|
||||
|
||||
/**
|
||||
* Default factory for creating keychains while de-serializing.
|
||||
@@ -34,7 +35,11 @@ public class DefaultKeyChainFactory implements KeyChainFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey, boolean isFollowingKey, boolean isMarried) {
|
||||
public DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey,
|
||||
boolean isFollowingKey, boolean isMarried) throws UnreadableWalletException {
|
||||
if (!accountKey.getPath().equals(DeterministicKeyChain.ACCOUNT_ZERO_PATH))
|
||||
throw new UnreadableWalletException("Expecting account key but found key with path: " +
|
||||
HDUtils.formatPath(accountKey.getPath()));
|
||||
DeterministicKeyChain chain;
|
||||
if (isMarried)
|
||||
chain = new MarriedKeyChain(accountKey);
|
||||
|
||||
@@ -61,9 +61,9 @@ import static com.google.common.collect.Lists.newLinkedList;
|
||||
* A watching wallet is not instantiated using the public part of the master key as you may imagine. Instead, you
|
||||
* need to take the account key (first child of the master key) and provide the public part of that to the watching
|
||||
* wallet instead. You can do this by calling {@link #getWatchingKey()} and then serializing it with
|
||||
* {@link org.bitcoinj.crypto.DeterministicKey#serializePubB58()}. The resulting "xpub..." string encodes
|
||||
* {@link org.bitcoinj.crypto.DeterministicKey#serializePubB58(org.bitcoinj.core.NetworkParameters)}. The resulting "xpub..." string encodes
|
||||
* sufficient information about the account key to create a watching chain via
|
||||
* {@link org.bitcoinj.crypto.DeterministicKey#deserializeB58(org.bitcoinj.crypto.DeterministicKey, String)}
|
||||
* {@link org.bitcoinj.crypto.DeterministicKey#deserializeB58(org.bitcoinj.crypto.DeterministicKey, String, org.bitcoinj.core.NetworkParameters)}
|
||||
* (with null as the first parameter) and then
|
||||
* {@link DeterministicKeyChain#DeterministicKeyChain(org.bitcoinj.crypto.DeterministicKey)}.</p>
|
||||
*
|
||||
@@ -113,8 +113,10 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
|
||||
// that feature yet. In future we might hand out different accounts for cases where we wish to hand payers
|
||||
// a payment request that can generate lots of addresses independently.
|
||||
public static final ImmutableList<ChildNumber> ACCOUNT_ZERO_PATH = ImmutableList.of(ChildNumber.ZERO_HARDENED);
|
||||
public static final ImmutableList<ChildNumber> EXTERNAL_PATH = ImmutableList.of(ChildNumber.ZERO);
|
||||
public static final ImmutableList<ChildNumber> INTERNAL_PATH = ImmutableList.of(ChildNumber.ONE);
|
||||
public static final ImmutableList<ChildNumber> EXTERNAL_SUBPATH = ImmutableList.of(ChildNumber.ZERO);
|
||||
public static final ImmutableList<ChildNumber> INTERNAL_SUBPATH = ImmutableList.of(ChildNumber.ONE);
|
||||
// m / 44' / 0' / 0'
|
||||
public static final ImmutableList<ChildNumber> BIP44_ACCOUNT_ZERO_PATH = ImmutableList.of(new ChildNumber(44, true), ChildNumber.ZERO_HARDENED, ChildNumber.ZERO_HARDENED);
|
||||
|
||||
// We try to ensure we have at least this many keys ready and waiting to be handed out via getKey().
|
||||
// See docs for getLookaheadSize() for more info on what this is for. The -1 value means it hasn't been calculated
|
||||
@@ -311,7 +313,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
|
||||
*/
|
||||
public DeterministicKeyChain(DeterministicKey watchingKey, long creationTimeSeconds) {
|
||||
checkArgument(watchingKey.isPubKeyOnly(), "Private subtrees not currently supported: if you got this key from DKC.getWatchingKey() then use .dropPrivate().dropParent() on it first.");
|
||||
checkArgument(watchingKey.getPath().size() == 1, "You can only watch an account key currently");
|
||||
checkArgument(watchingKey.getPath().size() == getAccountPath().size(), "You can only watch an account key currently");
|
||||
basicKeyChain = new BasicKeyChain();
|
||||
this.creationTimeSeconds = creationTimeSeconds;
|
||||
this.seed = null;
|
||||
@@ -404,8 +406,8 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
|
||||
encryptNonLeaf(aesKey, chain, rootKey, getAccountPath().subList(0, i));
|
||||
}
|
||||
DeterministicKey account = encryptNonLeaf(aesKey, chain, rootKey, getAccountPath());
|
||||
externalKey = encryptNonLeaf(aesKey, chain, account, ImmutableList.<ChildNumber>builder().addAll(getAccountPath()).addAll(EXTERNAL_PATH).build());
|
||||
internalKey = encryptNonLeaf(aesKey, chain, account, ImmutableList.<ChildNumber>builder().addAll(getAccountPath()).addAll(INTERNAL_PATH).build());
|
||||
externalKey = encryptNonLeaf(aesKey, chain, account, ImmutableList.<ChildNumber>builder().addAll(getAccountPath()).addAll(EXTERNAL_SUBPATH).build());
|
||||
internalKey = encryptNonLeaf(aesKey, chain, account, ImmutableList.<ChildNumber>builder().addAll(getAccountPath()).addAll(INTERNAL_SUBPATH).build());
|
||||
|
||||
// Now copy the (pubkey only) leaf keys across to avoid rederiving them. The private key bytes are missing
|
||||
// anyway so there's nothing to encrypt.
|
||||
@@ -852,9 +854,6 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
|
||||
boolean isMarried = !isFollowingKey && !chains.isEmpty() && chains.get(chains.size() - 1).isFollowing();
|
||||
if (seed == null) {
|
||||
DeterministicKey accountKey = new DeterministicKey(immutablePath, chainCode, pubkey, null, null);
|
||||
if (!accountKey.getPath().equals(ACCOUNT_ZERO_PATH))
|
||||
throw new UnreadableWalletException("Expecting account key but found key with path: " +
|
||||
HDUtils.formatPath(accountKey.getPath()));
|
||||
chain = factory.makeWatchingKeyChain(key, iter.peek(), accountKey, isFollowingKey, isMarried);
|
||||
isWatchingAccountKey = true;
|
||||
} else {
|
||||
@@ -979,7 +978,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
|
||||
// anyway so there's nothing to decrypt.
|
||||
for (ECKey eckey : basicKeyChain.getKeys()) {
|
||||
DeterministicKey key = (DeterministicKey) eckey;
|
||||
if (key.getPath().size() != 3) continue; // Not a leaf key.
|
||||
if (key.getPath().size() != getAccountPath().size() + 2) continue; // Not a leaf key.
|
||||
checkState(key.isEncrypted());
|
||||
DeterministicKey parent = chain.hierarchy.get(checkNotNull(key.getParent()).getPath(), false, false);
|
||||
// Clone the key to the new decrypted hierarchy.
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
package org.bitcoinj.wallet;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.bitcoinj.crypto.ChildNumber;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.bitcoinj.crypto.KeyCrypter;
|
||||
import org.bitcoinj.store.UnreadableWalletException;
|
||||
|
||||
/**
|
||||
* Factory interface for creation keychains while de-serializing a wallet.
|
||||
@@ -45,5 +48,5 @@ public interface KeyChainFactory {
|
||||
* @param isFollowingKey whether the keychain is following in a marriage
|
||||
* @param isMarried whether the keychain is leading in a marriage
|
||||
*/
|
||||
DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey, boolean isFollowingKey, boolean isMarried);
|
||||
DeterministicKeyChain makeWatchingKeyChain(Protos.Key key, Protos.Key firstSubKey, DeterministicKey accountKey, boolean isFollowingKey, boolean isMarried) throws UnreadableWalletException;
|
||||
}
|
||||
|
||||
@@ -764,14 +764,14 @@ public class KeyChainGroup implements KeyBag {
|
||||
// kinds of KeyPurpose are introduced.
|
||||
if (activeChain.getIssuedExternalKeys() > 0) {
|
||||
DeterministicKey currentExternalKey = activeChain.getKeyByPath(
|
||||
ImmutableList.of(ChildNumber.ZERO_HARDENED, ChildNumber.ZERO, new ChildNumber(activeChain.getIssuedExternalKeys() - 1))
|
||||
Lists.newArrayList(ImmutableList.<ChildNumber>builder().addAll(activeChain.getAccountPath()).add(ChildNumber.ZERO, new ChildNumber(activeChain.getIssuedExternalKeys() - 1)).build())
|
||||
);
|
||||
currentKeys.put(KeyChain.KeyPurpose.RECEIVE_FUNDS, currentExternalKey);
|
||||
}
|
||||
|
||||
if (activeChain.getIssuedInternalKeys() > 0) {
|
||||
DeterministicKey currentInternalKey = activeChain.getKeyByPath(
|
||||
ImmutableList.of(ChildNumber.ZERO_HARDENED, new ChildNumber(1), new ChildNumber(activeChain.getIssuedInternalKeys() - 1))
|
||||
Lists.newArrayList(ImmutableList.<ChildNumber>builder().addAll(activeChain.getAccountPath()).add(new ChildNumber(1), new ChildNumber(activeChain.getIssuedInternalKeys() - 1)).build())
|
||||
);
|
||||
currentKeys.put(KeyChain.KeyPurpose.CHANGE, currentInternalKey);
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ public class MarriedKeyChain extends DeterministicKeyChain {
|
||||
List<DeterministicKeyChain> followingKeyChains = Lists.newArrayList();
|
||||
|
||||
for (DeterministicKey key : followingAccountKeys) {
|
||||
checkArgument(key.getPath().size() == 1, "Following keys have to be account keys");
|
||||
checkArgument(key.getPath().size() == getAccountPath().size(), "Following keys have to be account keys");
|
||||
DeterministicKeyChain chain = DeterministicKeyChain.watchAndFollow(key);
|
||||
if (lookaheadSize >= 0)
|
||||
chain.setLookaheadSize(lookaheadSize);
|
||||
|
||||
@@ -3078,7 +3078,8 @@ public class WalletTest extends TestWithWallet {
|
||||
public boolean signInputs(ProposedTransaction propTx, KeyBag keyBag) {
|
||||
assertEquals(propTx.partialTx.getInputs().size(), propTx.keyPaths.size());
|
||||
List<ChildNumber> externalZeroLeaf = ImmutableList.<ChildNumber>builder()
|
||||
.addAll(DeterministicKeyChain.EXTERNAL_PATH).add(ChildNumber.ZERO).build();
|
||||
.addAll(DeterministicKeyChain.ACCOUNT_ZERO_PATH)
|
||||
.addAll(DeterministicKeyChain.EXTERNAL_SUBPATH).add(ChildNumber.ZERO).build();
|
||||
for (TransactionInput input : propTx.partialTx.getInputs()) {
|
||||
List<ChildNumber> keypath = propTx.keyPaths.get(input.getConnectedOutput().getScriptPubKey());
|
||||
assertNotNull(keypath);
|
||||
|
||||
@@ -17,11 +17,8 @@
|
||||
package org.bitcoinj.wallet;
|
||||
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.crypto.ChildNumber;
|
||||
import org.bitcoinj.crypto.DeterministicHierarchy;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.bitcoinj.crypto.*;
|
||||
import org.bitcoinj.params.MainNetParams;
|
||||
import org.bitcoinj.crypto.KeyCrypter;
|
||||
import org.bitcoinj.params.UnitTestParams;
|
||||
import org.bitcoinj.store.UnreadableWalletException;
|
||||
import org.bitcoinj.utils.BriefLogFormatter;
|
||||
|
||||
@@ -78,7 +78,7 @@ public class KeyChainGroupTest {
|
||||
public void freshCurrentKeys() throws Exception {
|
||||
int numKeys = ((group.getLookaheadSize() + group.getLookaheadThreshold()) * 2) // * 2 because of internal/external
|
||||
+ 1 // keys issued
|
||||
+ 3 /* account key + int/ext parent keys */;
|
||||
+ group.getActiveKeyChain().getAccountPath().size() + 2 /* account key + int/ext parent keys */;
|
||||
assertEquals(numKeys, group.numKeys());
|
||||
assertEquals(2 * numKeys, group.getBloomFilterElementCount());
|
||||
ECKey r1 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
|
||||
@@ -186,7 +186,7 @@ public class KeyChainGroupTest {
|
||||
group.getBloomFilterElementCount();
|
||||
assertEquals(((group.getLookaheadSize() + group.getLookaheadThreshold()) * 2) // * 2 because of internal/external
|
||||
+ (2 - group.getLookaheadThreshold()) // keys issued
|
||||
+ 4 /* master, account, int, ext */, group.numKeys());
|
||||
+ group.getActiveKeyChain().getAccountPath().size() + 3 /* master, account, int, ext */, group.numKeys());
|
||||
|
||||
Address a3 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
|
||||
assertEquals(a2, a3);
|
||||
@@ -409,26 +409,27 @@ public class KeyChainGroupTest {
|
||||
|
||||
@Test
|
||||
public void serialization() throws Exception {
|
||||
assertEquals(INITIAL_KEYS + 1 /* for the seed */, group.serializeToProtobuf().size());
|
||||
int initialKeys = INITIAL_KEYS + group.getActiveKeyChain().getAccountPath().size() - 1;
|
||||
assertEquals(initialKeys + 1 /* for the seed */, group.serializeToProtobuf().size());
|
||||
group = KeyChainGroup.fromProtobufUnencrypted(params, group.serializeToProtobuf());
|
||||
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
|
||||
DeterministicKey key1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
|
||||
DeterministicKey key2 = group.freshKey(KeyChain.KeyPurpose.CHANGE);
|
||||
group.getBloomFilterElementCount();
|
||||
List<Protos.Key> protoKeys1 = group.serializeToProtobuf();
|
||||
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 1, protoKeys1.size());
|
||||
assertEquals(initialKeys + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 1, protoKeys1.size());
|
||||
group.importKeys(new ECKey());
|
||||
List<Protos.Key> protoKeys2 = group.serializeToProtobuf();
|
||||
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 2, protoKeys2.size());
|
||||
assertEquals(initialKeys + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 2, protoKeys2.size());
|
||||
|
||||
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys1);
|
||||
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 1, protoKeys1.size());
|
||||
assertEquals(initialKeys + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 1, protoKeys1.size());
|
||||
assertTrue(group.hasKey(key1));
|
||||
assertTrue(group.hasKey(key2));
|
||||
assertEquals(key2, group.currentKey(KeyChain.KeyPurpose.CHANGE));
|
||||
assertEquals(key1, group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS));
|
||||
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys2);
|
||||
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 2, protoKeys2.size());
|
||||
assertEquals(initialKeys + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 2, protoKeys2.size());
|
||||
assertTrue(group.hasKey(key1));
|
||||
assertTrue(group.hasKey(key2));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user