mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-11-13 19:07:12 +00:00
Fix BIP39 implementation
This commit is contained in:
@@ -22,7 +22,8 @@ import com.google.bitcoin.core.Utils;
|
||||
import com.google.bitcoin.crypto.*;
|
||||
import com.google.bitcoin.store.UnreadableWalletException;
|
||||
import com.google.bitcoin.utils.Threading;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.protobuf.ByteString;
|
||||
import org.bitcoinj.wallet.Protos;
|
||||
@@ -32,6 +33,8 @@ import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
@@ -77,10 +80,6 @@ import static com.google.common.collect.Lists.newLinkedList;
|
||||
*/
|
||||
public class DeterministicKeyChain implements EncryptableKeyChain {
|
||||
private static final Logger log = LoggerFactory.getLogger(DeterministicKeyChain.class);
|
||||
// It would take more than 10^12 years to brute-force a 128 bit seed using $1B worth
|
||||
// of computing equipment.
|
||||
public static final int DEFAULT_SEED_BITS = 128;
|
||||
public static final int MAX_SEED_BITS = 512;
|
||||
|
||||
private final ReentrantLock lock = Threading.lock("DeterministicKeyChain");
|
||||
|
||||
@@ -126,29 +125,28 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
|
||||
private boolean isFollowing;
|
||||
|
||||
/**
|
||||
* Generates a new key chain with a seed selected randomly from the given {@link java.security.SecureRandom}
|
||||
* object and the default seed size.
|
||||
* Generates a new key chain with entropy selected randomly from the given {@link java.security.SecureRandom}
|
||||
* object and the default entropy size.
|
||||
*/
|
||||
public DeterministicKeyChain(SecureRandom random) {
|
||||
this(getRandomSeed(random, DEFAULT_SEED_BITS), Utils.currentTimeSeconds());
|
||||
this(random, DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS, "", Utils.currentTimeSeconds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new key chain with a seed selected randomly from the given {@link java.security.SecureRandom}
|
||||
* Generates a new key chain with entropy selected randomly from the given {@link java.security.SecureRandom}
|
||||
* object and of the requested size in bits.
|
||||
*/
|
||||
public DeterministicKeyChain(SecureRandom random, int bits) {
|
||||
this(getRandomSeed(random, bits), Utils.currentTimeSeconds());
|
||||
this(random, bits, "", Utils.currentTimeSeconds());
|
||||
}
|
||||
|
||||
private static byte[] getRandomSeed(SecureRandom random, int bits) {
|
||||
Preconditions.checkArgument(bits >= DEFAULT_SEED_BITS, "requested seed size too small");
|
||||
Preconditions.checkArgument(bits <= MAX_SEED_BITS, "requested seed size too large");
|
||||
Preconditions.checkArgument(bits % 8 == 0, "requested seed size not an even number of bytes");
|
||||
|
||||
byte[] seed = new byte[bits / 8];
|
||||
random.nextBytes(seed);
|
||||
return seed;
|
||||
/**
|
||||
* Generates a new key chain with entropy selected randomly from the given {@link java.security.SecureRandom}
|
||||
* object and of the requested size in bits. The derived seed is further protected with a user selected passphrase
|
||||
* (see BIP 39).
|
||||
*/
|
||||
public DeterministicKeyChain(SecureRandom random, int bits, String passphrase, long seedCreationTimeSecs) {
|
||||
this(new DeterministicSeed(random, bits, passphrase, seedCreationTimeSecs));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -496,10 +494,10 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
|
||||
}
|
||||
|
||||
/** Returns a list of words that represent the seed. */
|
||||
public List<String> toMnemonicCode() {
|
||||
public List<String> getMnemonicCode() {
|
||||
lock.lock();
|
||||
try {
|
||||
return seed.toMnemonicCode();
|
||||
return seed.getMnemonicCode();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -529,6 +527,11 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
|
||||
Protos.Key.Builder seedEntry = BasicKeyChain.serializeEncryptableItem(seed);
|
||||
seedEntry.setType(Protos.Key.Type.DETERMINISTIC_ROOT_SEED);
|
||||
entries.add(seedEntry.build());
|
||||
if (seed.hasMnemonicCode()) {
|
||||
Protos.Key.Builder mnemonicEntry = BasicKeyChain.serializeEncryptableItem(seed.getMnemonicEncryptableItem());
|
||||
mnemonicEntry.setType(Protos.Key.Type.DETERMINISTIC_MNEMONIC);
|
||||
entries.add(mnemonicEntry.build());
|
||||
}
|
||||
}
|
||||
Map<ECKey, Protos.Key.Builder> keys = basicKeyChain.serializeToEditableProtobufs();
|
||||
for (Map.Entry<ECKey, Protos.Key.Builder> entry : keys.entrySet()) {
|
||||
@@ -573,7 +576,19 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
|
||||
int lookaheadSize = -1;
|
||||
for (Protos.Key key : keys) {
|
||||
final Protos.Key.Type t = key.getType();
|
||||
if (t == Protos.Key.Type.DETERMINISTIC_ROOT_SEED) {
|
||||
if (t == Protos.Key.Type.DETERMINISTIC_MNEMONIC) {
|
||||
checkState(chain == null);
|
||||
checkState(seed != null);
|
||||
if (key.hasSecretBytes()) {
|
||||
seed.setMnemonicCode(key.getSecretBytes().toByteArray());
|
||||
} else if (key.hasEncryptedData()) {
|
||||
EncryptedData data = new EncryptedData(key.getEncryptedData().getInitialisationVector().toByteArray(),
|
||||
key.getEncryptedData().getEncryptedPrivateKey().toByteArray());
|
||||
seed.setEncryptedMnemonicCode(data);
|
||||
} else {
|
||||
throw new UnreadableWalletException("Malformed key proto: " + key.toString());
|
||||
}
|
||||
} else if (t == Protos.Key.Type.DETERMINISTIC_ROOT_SEED) {
|
||||
if (chain != null) {
|
||||
checkState(lookaheadSize >= 0);
|
||||
chain.setLookaheadSize(lookaheadSize);
|
||||
|
||||
@@ -18,11 +18,18 @@
|
||||
package com.google.bitcoin.wallet;
|
||||
|
||||
import com.google.bitcoin.crypto.*;
|
||||
import com.google.bitcoin.store.UnreadableWalletException;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Splitter;
|
||||
|
||||
import org.bitcoinj.wallet.Protos;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@@ -36,44 +43,99 @@ import static com.google.common.base.Preconditions.checkState;
|
||||
* code.
|
||||
*/
|
||||
public class DeterministicSeed implements EncryptableItem {
|
||||
// It would take more than 10^12 years to brute-force a 128 bit seed using $1B worth
|
||||
// of computing equipment.
|
||||
public static final int DEFAULT_SEED_ENTROPY_BITS = 128;
|
||||
public static final int MAX_SEED_ENTROPY_BITS = 512;
|
||||
public static final String UTF_8 = "UTF-8";
|
||||
|
||||
@Nullable private final byte[] unencryptedSeed;
|
||||
@Nullable private final EncryptedData encryptedSeed;
|
||||
@Nullable private List<String> mnemonicCode;
|
||||
@Nullable private EncryptedData encryptedMnemonicCode;
|
||||
private final long creationTimeSeconds;
|
||||
|
||||
private static MnemonicCode MNEMONIC_CODE;
|
||||
private static synchronized MnemonicCode getCachedMnemonicCode() {
|
||||
private static MnemonicCode MNEMONIC_CODEC;
|
||||
|
||||
private static synchronized MnemonicCode getCachedMnemonicCodec() {
|
||||
try {
|
||||
// This object can be large and has to load the word list from disk, so we lazy cache it.
|
||||
if (MNEMONIC_CODE == null) {
|
||||
MNEMONIC_CODE = new MnemonicCode();
|
||||
if (MNEMONIC_CODEC == null) {
|
||||
MNEMONIC_CODEC = new MnemonicCode();
|
||||
}
|
||||
return MNEMONIC_CODE;
|
||||
return MNEMONIC_CODEC;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public DeterministicSeed(byte[] unencryptedSeed, long creationTimeSeconds) {
|
||||
DeterministicSeed(byte[] unencryptedSeed, List<String> mnemonic, long creationTimeSeconds) {
|
||||
this.unencryptedSeed = checkNotNull(unencryptedSeed);
|
||||
this.encryptedSeed = null;
|
||||
this.mnemonicCode = mnemonic;
|
||||
this.encryptedMnemonicCode = null;
|
||||
this.creationTimeSeconds = creationTimeSeconds;
|
||||
}
|
||||
|
||||
public DeterministicSeed(EncryptedData encryptedSeed, long creationTimeSeconds) {
|
||||
/**
|
||||
* Constructs a seed from bytes. The mnemonic phrase is unknown.
|
||||
*/
|
||||
public DeterministicSeed(byte[] unencryptedSeed, long creationTimeSeconds) {
|
||||
this(unencryptedSeed, null, creationTimeSeconds);
|
||||
}
|
||||
|
||||
DeterministicSeed(EncryptedData encryptedSeed, EncryptedData encryptedMnemonic, long creationTimeSeconds) {
|
||||
this.unencryptedSeed = null;
|
||||
this.mnemonicCode = null;
|
||||
this.encryptedSeed = checkNotNull(encryptedSeed);
|
||||
this.encryptedMnemonicCode = encryptedMnemonic;
|
||||
this.creationTimeSeconds = creationTimeSeconds;
|
||||
}
|
||||
|
||||
DeterministicSeed(EncryptedData encryptedSeed, long creationTimeSeconds) {
|
||||
this(encryptedSeed, null, creationTimeSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a seed from a BIP 39 mnemonic code. See {@link com.google.bitcoin.crypto.MnemonicCode} for more
|
||||
* details on this scheme.
|
||||
* @param words A list of words.
|
||||
* @param mnemonicCode A list of words.
|
||||
* @param passphrase A user supplied passphrase, or an empty string if there is no passphrase
|
||||
* @param creationTimeSeconds When the seed was originally created, UNIX time.
|
||||
* @throws MnemonicException if there is a problem decoding the words.
|
||||
*/
|
||||
public DeterministicSeed(List<String> words, long creationTimeSeconds) throws MnemonicException.MnemonicChecksumException, MnemonicException.MnemonicLengthException, MnemonicException.MnemonicWordException {
|
||||
this(getCachedMnemonicCode().toEntropy(words), creationTimeSeconds);
|
||||
public DeterministicSeed(List<String> mnemonicCode, String passphrase, long creationTimeSeconds) {
|
||||
this(getCachedMnemonicCodec().toSeed(mnemonicCode, passphrase), mnemonicCode, creationTimeSeconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a seed from a BIP 39 mnemonic code. See {@link com.google.bitcoin.crypto.MnemonicCode} for more
|
||||
* details on this scheme.
|
||||
* @param random Entropy source
|
||||
* @param bits number of bits, must be divisible by 32
|
||||
* @param passphrase A user supplied passphrase, or an empty string if there is no passphrase
|
||||
* @param creationTimeSeconds When the seed was originally created, UNIX time.
|
||||
*/
|
||||
public DeterministicSeed(SecureRandom random, int bits, String passphrase, long creationTimeSeconds) {
|
||||
byte[] entropy = getEntropy(random, bits);
|
||||
try {
|
||||
this.mnemonicCode = getCachedMnemonicCodec().toMnemonic(entropy);
|
||||
} catch (MnemonicException.MnemonicLengthException e) {
|
||||
// cannot happen
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
this.unencryptedSeed = getCachedMnemonicCodec().toSeed(mnemonicCode, passphrase);
|
||||
this.encryptedSeed = null;
|
||||
this.creationTimeSeconds = creationTimeSeconds;
|
||||
}
|
||||
|
||||
private static byte[] getEntropy(SecureRandom random, int bits) {
|
||||
Preconditions.checkArgument(bits >= DEFAULT_SEED_ENTROPY_BITS, "requested entropy size too small");
|
||||
Preconditions.checkArgument(bits <= MAX_SEED_ENTROPY_BITS, "requested entropy size too large");
|
||||
Preconditions.checkArgument(bits % 32 == 0, "requested entropy size not divisible by 32");
|
||||
|
||||
byte[] seed = new byte[bits / 8];
|
||||
random.nextBytes(seed);
|
||||
return seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,7 +149,8 @@ public class DeterministicSeed implements EncryptableItem {
|
||||
if (isEncrypted())
|
||||
return "DeterministicSeed [encrypted]";
|
||||
else
|
||||
return "DeterministicSeed " + toHexString();
|
||||
return "DeterministicSeed " + toHexString() +
|
||||
((mnemonicCode != null) ? " " + Joiner.on(" ").join(mnemonicCode) : "");
|
||||
}
|
||||
|
||||
/** Returns the seed as hex or null if encrypted. */
|
||||
@@ -121,34 +184,66 @@ public class DeterministicSeed implements EncryptableItem {
|
||||
return creationTimeSeconds;
|
||||
}
|
||||
|
||||
public EncryptableItem getMnemonicEncryptableItem() {
|
||||
return new EncryptableItem() {
|
||||
@Override
|
||||
public boolean isEncrypted() {
|
||||
return DeterministicSeed.this.isEncrypted();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] getSecretBytes() {
|
||||
return getMnemonicAsBytes();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public EncryptedData getEncryptedData() {
|
||||
return encryptedMnemonicCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protos.Wallet.EncryptionType getEncryptionType() {
|
||||
return Protos.Wallet.EncryptionType.ENCRYPTED_SCRYPT_AES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreationTimeSeconds() {
|
||||
return creationTimeSeconds;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public DeterministicSeed encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) {
|
||||
checkState(encryptedSeed == null, "Trying to encrypt seed twice");
|
||||
checkState(unencryptedSeed != null, "Seed bytes missing so cannot encrypt");
|
||||
EncryptedData data = keyCrypter.encrypt(unencryptedSeed, aesKey);
|
||||
return new DeterministicSeed(data, creationTimeSeconds);
|
||||
EncryptedData seed = keyCrypter.encrypt(unencryptedSeed, aesKey);
|
||||
EncryptedData mnemonic = (mnemonicCode != null) ? keyCrypter.encrypt(getMnemonicAsBytes(), aesKey) : null;
|
||||
return new DeterministicSeed(seed, mnemonic, creationTimeSeconds);
|
||||
}
|
||||
|
||||
private byte[] getMnemonicAsBytes() {
|
||||
try {
|
||||
return Joiner.on(" ").join(mnemonicCode).getBytes(UTF_8);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public DeterministicSeed decrypt(KeyCrypter crypter, KeyParameter aesKey) {
|
||||
checkState(isEncrypted());
|
||||
checkNotNull(encryptedSeed);
|
||||
return new DeterministicSeed(crypter.decrypt(encryptedSeed, aesKey), creationTimeSeconds);
|
||||
}
|
||||
|
||||
/** Returns a list of words that represent the seed, or IllegalStateException if the seed is encrypted or missing. */
|
||||
public List<String> toMnemonicCode(MnemonicCode code) {
|
||||
byte[] seed = crypter.decrypt(encryptedSeed, aesKey);
|
||||
List<String> mnemonic = null;
|
||||
try {
|
||||
if (isEncrypted())
|
||||
throw new IllegalStateException("The seed is encrypted");
|
||||
final byte[] seed = checkNotNull(getSecretBytes());
|
||||
return code.toMnemonic(seed);
|
||||
} catch (MnemonicException.MnemonicLengthException e) {
|
||||
throw new RuntimeException(e); // Cannot happen.
|
||||
if (encryptedMnemonicCode != null)
|
||||
mnemonic = decodeMnemonicCode(crypter.decrypt(encryptedMnemonicCode, aesKey));
|
||||
} catch (UnreadableWalletException e) {
|
||||
// TODO what is the best way to handle this exception?
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a list of words that represent the seed, or IllegalStateException if the seed is encrypted or missing. */
|
||||
public List<String> toMnemonicCode() {
|
||||
return toMnemonicCode(getCachedMnemonicCode());
|
||||
return new DeterministicSeed(seed, mnemonic, creationTimeSeconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -175,4 +270,46 @@ public class DeterministicSeed implements EncryptableItem {
|
||||
result = 31 * result + (int) (creationTimeSeconds ^ (creationTimeSeconds >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if our mnemonic is a valid mnemonic phrase for our word list.
|
||||
* Does nothing if we are encrypted.
|
||||
*
|
||||
* @throws com.google.bitcoin.crypto.MnemonicException if check fails
|
||||
*/
|
||||
public void check() throws MnemonicException {
|
||||
if (mnemonicCode != null)
|
||||
getCachedMnemonicCodec().check(mnemonicCode);
|
||||
}
|
||||
|
||||
/** Get the mnemonic code, or null if unknown. */
|
||||
@Nullable
|
||||
public List<String> getMnemonicCode() {
|
||||
return mnemonicCode;
|
||||
}
|
||||
|
||||
/** Set encrypted mnemonic code. Used by protobuf deserializer. */
|
||||
public void setEncryptedMnemonicCode(EncryptedData encryptedMnemonicCode) {
|
||||
this.encryptedMnemonicCode = encryptedMnemonicCode;
|
||||
}
|
||||
|
||||
/** Set mnemonic code from UTF-8 encoded bytes. */
|
||||
public void setMnemonicCode(@Nullable byte[] mnemonicCode) throws UnreadableWalletException {
|
||||
this.mnemonicCode = decodeMnemonicCode(mnemonicCode);
|
||||
}
|
||||
|
||||
/** Whether the mnemonic code is known for this seed. */
|
||||
public boolean hasMnemonicCode() {
|
||||
return mnemonicCode != null || encryptedMnemonicCode != null;
|
||||
}
|
||||
|
||||
private List<String> decodeMnemonicCode(byte[] mnemonicCode) throws UnreadableWalletException {
|
||||
String code = null;
|
||||
try {
|
||||
code = new String(mnemonicCode, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new UnreadableWalletException(e.toString());
|
||||
}
|
||||
return Splitter.on(" ").splitToList(code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ public class KeyChainGroup {
|
||||
/** Adds a new HD chain to the chains list, and make it the default chain (from which keys are issued). */
|
||||
public void createAndActivateNewHDChain() {
|
||||
// We can't do auto upgrade here because we don't know the rotation time, if any.
|
||||
final DeterministicKeyChain chain = new DeterministicKeyChain(new SecureRandom(), DeterministicKeyChain.DEFAULT_SEED_BITS);
|
||||
final DeterministicKeyChain chain = new DeterministicKeyChain(new SecureRandom());
|
||||
log.info("Creating and activating a new HD chain: {}", chain);
|
||||
for (ListenerRegistration<KeyChainEventListener> registration : basic.getListeners())
|
||||
chain.addEventListener(registration.listener, registration.executor);
|
||||
@@ -733,12 +733,12 @@ public class KeyChainGroup {
|
||||
log.info("Auto-upgrading pre-HD wallet using oldest non-rotating private key");
|
||||
byte[] seed = checkNotNull(keyToUse.getSecretBytes());
|
||||
// Private keys should be at least 128 bits long.
|
||||
checkState(seed.length >= DeterministicKeyChain.DEFAULT_SEED_BITS / 8);
|
||||
checkState(seed.length >= DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS / 8);
|
||||
// We reduce the entropy here to 128 bits because people like to write their seeds down on paper, and 128
|
||||
// bits should be sufficient forever unless the laws of the universe change or ECC is broken; in either case
|
||||
// we all have bigger problems.
|
||||
seed = Arrays.copyOfRange(seed, 0, DeterministicKeyChain.DEFAULT_SEED_BITS / 8); // final argument is exclusive range.
|
||||
checkState(seed.length == DeterministicKeyChain.DEFAULT_SEED_BITS / 8);
|
||||
seed = Arrays.copyOfRange(seed, 0, DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS / 8); // final argument is exclusive range.
|
||||
checkState(seed.length == DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS / 8);
|
||||
DeterministicKeyChain chain = new DeterministicKeyChain(seed, keyToUse.getCreationTimeSeconds());
|
||||
if (aesKey != null) {
|
||||
chain = chain.toEncrypted(checkNotNull(basic.getKeyCrypter()), aesKey);
|
||||
@@ -807,7 +807,7 @@ public class KeyChainGroup {
|
||||
if (seed.isEncrypted()) {
|
||||
builder2.append(String.format("Seed is encrypted%n"));
|
||||
} else if (includePrivateKeys) {
|
||||
final List<String> words = seed.toMnemonicCode();
|
||||
final List<String> words = seed.getMnemonicCode();
|
||||
builder2.append(
|
||||
String.format("Seed as words: %s%nSeed as hex: %s%n", Joiner.on(' ').join(words),
|
||||
seed.toHexString())
|
||||
|
||||
@@ -2271,7 +2271,7 @@ public final class Protos {
|
||||
*
|
||||
* <pre>
|
||||
* The public EC key derived from the private key. We allow both to be stored to avoid mobile clients having to
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED entries this is missing.
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED and DETERMINISTIC_MNEMONIC entries this is missing.
|
||||
* </pre>
|
||||
*/
|
||||
boolean hasPublicKey();
|
||||
@@ -2280,7 +2280,7 @@ public final class Protos {
|
||||
*
|
||||
* <pre>
|
||||
* The public EC key derived from the private key. We allow both to be stored to avoid mobile clients having to
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED entries this is missing.
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED and DETERMINISTIC_MNEMONIC entries this is missing.
|
||||
* </pre>
|
||||
*/
|
||||
com.google.protobuf.ByteString getPublicKey();
|
||||
@@ -2548,6 +2548,17 @@ public final class Protos {
|
||||
* </pre>
|
||||
*/
|
||||
DETERMINISTIC_KEY(3, 4),
|
||||
/**
|
||||
* <code>DETERMINISTIC_MNEMONIC = 5;</code>
|
||||
*
|
||||
* <pre>
|
||||
**
|
||||
* Not really a key, but rather contains the mnemonic phrase for a deterministic key hierarchy in the private_key field.
|
||||
* The label and public_key fields are missing. Creation timestamp will exist.
|
||||
* Must be immediately after DETERMINISTIC_KEY
|
||||
* </pre>
|
||||
*/
|
||||
DETERMINISTIC_MNEMONIC(4, 5),
|
||||
;
|
||||
|
||||
/**
|
||||
@@ -2589,6 +2600,17 @@ public final class Protos {
|
||||
* </pre>
|
||||
*/
|
||||
public static final int DETERMINISTIC_KEY_VALUE = 4;
|
||||
/**
|
||||
* <code>DETERMINISTIC_MNEMONIC = 5;</code>
|
||||
*
|
||||
* <pre>
|
||||
**
|
||||
* Not really a key, but rather contains the mnemonic phrase for a deterministic key hierarchy in the private_key field.
|
||||
* The label and public_key fields are missing. Creation timestamp will exist.
|
||||
* Must be immediately after DETERMINISTIC_KEY
|
||||
* </pre>
|
||||
*/
|
||||
public static final int DETERMINISTIC_MNEMONIC_VALUE = 5;
|
||||
|
||||
|
||||
public final int getNumber() { return value; }
|
||||
@@ -2599,6 +2621,7 @@ public final class Protos {
|
||||
case 2: return ENCRYPTED_SCRYPT_AES;
|
||||
case 3: return DETERMINISTIC_ROOT_SEED;
|
||||
case 4: return DETERMINISTIC_KEY;
|
||||
case 5: return DETERMINISTIC_MNEMONIC;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@@ -2735,7 +2758,7 @@ public final class Protos {
|
||||
*
|
||||
* <pre>
|
||||
* The public EC key derived from the private key. We allow both to be stored to avoid mobile clients having to
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED entries this is missing.
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED and DETERMINISTIC_MNEMONIC entries this is missing.
|
||||
* </pre>
|
||||
*/
|
||||
public boolean hasPublicKey() {
|
||||
@@ -2746,7 +2769,7 @@ public final class Protos {
|
||||
*
|
||||
* <pre>
|
||||
* The public EC key derived from the private key. We allow both to be stored to avoid mobile clients having to
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED entries this is missing.
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED and DETERMINISTIC_MNEMONIC entries this is missing.
|
||||
* </pre>
|
||||
*/
|
||||
public com.google.protobuf.ByteString getPublicKey() {
|
||||
@@ -3500,7 +3523,7 @@ public final class Protos {
|
||||
*
|
||||
* <pre>
|
||||
* The public EC key derived from the private key. We allow both to be stored to avoid mobile clients having to
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED entries this is missing.
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED and DETERMINISTIC_MNEMONIC entries this is missing.
|
||||
* </pre>
|
||||
*/
|
||||
public boolean hasPublicKey() {
|
||||
@@ -3511,7 +3534,7 @@ public final class Protos {
|
||||
*
|
||||
* <pre>
|
||||
* The public EC key derived from the private key. We allow both to be stored to avoid mobile clients having to
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED entries this is missing.
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED and DETERMINISTIC_MNEMONIC entries this is missing.
|
||||
* </pre>
|
||||
*/
|
||||
public com.google.protobuf.ByteString getPublicKey() {
|
||||
@@ -3522,7 +3545,7 @@ public final class Protos {
|
||||
*
|
||||
* <pre>
|
||||
* The public EC key derived from the private key. We allow both to be stored to avoid mobile clients having to
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED entries this is missing.
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED and DETERMINISTIC_MNEMONIC entries this is missing.
|
||||
* </pre>
|
||||
*/
|
||||
public Builder setPublicKey(com.google.protobuf.ByteString value) {
|
||||
@@ -3539,7 +3562,7 @@ public final class Protos {
|
||||
*
|
||||
* <pre>
|
||||
* The public EC key derived from the private key. We allow both to be stored to avoid mobile clients having to
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED entries this is missing.
|
||||
* do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED and DETERMINISTIC_MNEMONIC entries this is missing.
|
||||
* </pre>
|
||||
*/
|
||||
public Builder clearPublicKey() {
|
||||
@@ -16161,66 +16184,67 @@ public final class Protos {
|
||||
"ey\030\002 \002(\014\"y\n\020DeterministicKey\022\022\n\nchain_co" +
|
||||
"de\030\001 \002(\014\022\014\n\004path\030\002 \003(\r\022\026\n\016issued_subkeys" +
|
||||
"\030\003 \001(\r\022\026\n\016lookahead_size\030\004 \001(\r\022\023\n\013isFoll" +
|
||||
"owing\030\005 \001(\010\"\302\002\n\003Key\022\036\n\004type\030\001 \002(\0162\020.wall" +
|
||||
"owing\030\005 \001(\010\"\336\002\n\003Key\022\036\n\004type\030\001 \002(\0162\020.wall" +
|
||||
"et.Key.Type\022\024\n\014secret_bytes\030\002 \001(\014\022-\n\016enc" +
|
||||
"rypted_data\030\006 \001(\0132\025.wallet.EncryptedData",
|
||||
"\022\022\n\npublic_key\030\003 \001(\014\022\r\n\005label\030\004 \001(\t\022\032\n\022c" +
|
||||
"reation_timestamp\030\005 \001(\003\0223\n\021deterministic" +
|
||||
"_key\030\007 \001(\0132\030.wallet.DeterministicKey\"b\n\004" +
|
||||
"_key\030\007 \001(\0132\030.wallet.DeterministicKey\"~\n\004" +
|
||||
"Type\022\014\n\010ORIGINAL\020\001\022\030\n\024ENCRYPTED_SCRYPT_A" +
|
||||
"ES\020\002\022\033\n\027DETERMINISTIC_ROOT_SEED\020\003\022\025\n\021DET" +
|
||||
"ERMINISTIC_KEY\020\004\"5\n\006Script\022\017\n\007program\030\001 " +
|
||||
"\002(\014\022\032\n\022creation_timestamp\030\002 \002(\003\"\222\001\n\020Tran" +
|
||||
"sactionInput\022\"\n\032transaction_out_point_ha" +
|
||||
"sh\030\001 \002(\014\022#\n\033transaction_out_point_index\030" +
|
||||
"\002 \002(\r\022\024\n\014script_bytes\030\003 \002(\014\022\020\n\010sequence\030",
|
||||
"\004 \001(\r\022\r\n\005value\030\005 \001(\003\"\177\n\021TransactionOutpu" +
|
||||
"t\022\r\n\005value\030\001 \002(\003\022\024\n\014script_bytes\030\002 \002(\014\022!" +
|
||||
"\n\031spent_by_transaction_hash\030\003 \001(\014\022\"\n\032spe" +
|
||||
"nt_by_transaction_index\030\004 \001(\005\"\234\003\n\025Transa" +
|
||||
"ctionConfidence\0220\n\004type\030\001 \001(\0162\".wallet.T" +
|
||||
"ransactionConfidence.Type\022\032\n\022appeared_at" +
|
||||
"_height\030\002 \001(\005\022\036\n\026overriding_transaction\030" +
|
||||
"\003 \001(\014\022\r\n\005depth\030\004 \001(\005\022\021\n\twork_done\030\005 \001(\003\022" +
|
||||
")\n\014broadcast_by\030\006 \003(\0132\023.wallet.PeerAddre" +
|
||||
"ss\0224\n\006source\030\007 \001(\0162$.wallet.TransactionC",
|
||||
"onfidence.Source\"O\n\004Type\022\013\n\007UNKNOWN\020\000\022\014\n" +
|
||||
"\010BUILDING\020\001\022\013\n\007PENDING\020\002\022\025\n\021NOT_IN_BEST_" +
|
||||
"CHAIN\020\003\022\010\n\004DEAD\020\004\"A\n\006Source\022\022\n\016SOURCE_UN" +
|
||||
"KNOWN\020\000\022\022\n\016SOURCE_NETWORK\020\001\022\017\n\013SOURCE_SE" +
|
||||
"LF\020\002\"\236\004\n\013Transaction\022\017\n\007version\030\001 \002(\005\022\014\n" +
|
||||
"\004hash\030\002 \002(\014\022&\n\004pool\030\003 \001(\0162\030.wallet.Trans" +
|
||||
"action.Pool\022\021\n\tlock_time\030\004 \001(\r\022\022\n\nupdate" +
|
||||
"d_at\030\005 \001(\003\0223\n\021transaction_input\030\006 \003(\0132\030." +
|
||||
"wallet.TransactionInput\0225\n\022transaction_o" +
|
||||
"utput\030\007 \003(\0132\031.wallet.TransactionOutput\022\022",
|
||||
"\n\nblock_hash\030\010 \003(\014\022 \n\030block_relativity_o" +
|
||||
"ffsets\030\013 \003(\005\0221\n\nconfidence\030\t \001(\0132\035.walle" +
|
||||
"t.TransactionConfidence\0225\n\007purpose\030\n \001(\016" +
|
||||
"2\033.wallet.Transaction.Purpose:\007UNKNOWN\"Y" +
|
||||
"\n\004Pool\022\013\n\007UNSPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010INACTI" +
|
||||
"VE\020\002\022\010\n\004DEAD\020\n\022\013\n\007PENDING\020\020\022\024\n\020PENDING_I" +
|
||||
"NACTIVE\020\022\":\n\007Purpose\022\013\n\007UNKNOWN\020\000\022\020\n\014USE" +
|
||||
"R_PAYMENT\020\001\022\020\n\014KEY_ROTATION\020\002\"N\n\020ScryptP" +
|
||||
"arameters\022\014\n\004salt\030\001 \002(\014\022\020\n\001n\030\002 \001(\003:\0051638" +
|
||||
"4\022\014\n\001r\030\003 \001(\005:\0018\022\014\n\001p\030\004 \001(\005:\0011\"8\n\tExtensi",
|
||||
"on\022\n\n\002id\030\001 \002(\t\022\014\n\004data\030\002 \002(\014\022\021\n\tmandator" +
|
||||
"y\030\003 \002(\010\" \n\003Tag\022\013\n\003tag\030\001 \002(\t\022\014\n\004data\030\002 \002(" +
|
||||
"\014\"\261\004\n\006Wallet\022\032\n\022network_identifier\030\001 \002(\t" +
|
||||
"\022\034\n\024last_seen_block_hash\030\002 \001(\014\022\036\n\026last_s" +
|
||||
"een_block_height\030\014 \001(\r\022!\n\031last_seen_bloc" +
|
||||
"k_time_secs\030\016 \001(\003\022\030\n\003key\030\003 \003(\0132\013.wallet." +
|
||||
"Key\022(\n\013transaction\030\004 \003(\0132\023.wallet.Transa" +
|
||||
"ction\022&\n\016watched_script\030\017 \003(\0132\016.wallet.S" +
|
||||
"cript\022C\n\017encryption_type\030\005 \001(\0162\035.wallet." +
|
||||
"Wallet.EncryptionType:\013UNENCRYPTED\0227\n\025en",
|
||||
"cryption_parameters\030\006 \001(\0132\030.wallet.Scryp" +
|
||||
"tParameters\022\022\n\007version\030\007 \001(\005:\0011\022$\n\texten" +
|
||||
"sion\030\n \003(\0132\021.wallet.Extension\022\023\n\013descrip" +
|
||||
"tion\030\013 \001(\t\022\031\n\021key_rotation_time\030\r \001(\004\022\031\n" +
|
||||
"\004tags\030\020 \003(\0132\013.wallet.Tag\";\n\016EncryptionTy" +
|
||||
"pe\022\017\n\013UNENCRYPTED\020\001\022\030\n\024ENCRYPTED_SCRYPT_" +
|
||||
"AES\020\002B\035\n\023org.bitcoinj.walletB\006Protos"
|
||||
"ERMINISTIC_KEY\020\004\022\032\n\026DETERMINISTIC_MNEMON" +
|
||||
"IC\020\005\"5\n\006Script\022\017\n\007program\030\001 \002(\014\022\032\n\022creat" +
|
||||
"ion_timestamp\030\002 \002(\003\"\222\001\n\020TransactionInput" +
|
||||
"\022\"\n\032transaction_out_point_hash\030\001 \002(\014\022#\n\033" +
|
||||
"transaction_out_point_index\030\002 \002(\r\022\024\n\014scr",
|
||||
"ipt_bytes\030\003 \002(\014\022\020\n\010sequence\030\004 \001(\r\022\r\n\005val" +
|
||||
"ue\030\005 \001(\003\"\177\n\021TransactionOutput\022\r\n\005value\030\001" +
|
||||
" \002(\003\022\024\n\014script_bytes\030\002 \002(\014\022!\n\031spent_by_t" +
|
||||
"ransaction_hash\030\003 \001(\014\022\"\n\032spent_by_transa" +
|
||||
"ction_index\030\004 \001(\005\"\234\003\n\025TransactionConfide" +
|
||||
"nce\0220\n\004type\030\001 \001(\0162\".wallet.TransactionCo" +
|
||||
"nfidence.Type\022\032\n\022appeared_at_height\030\002 \001(" +
|
||||
"\005\022\036\n\026overriding_transaction\030\003 \001(\014\022\r\n\005dep" +
|
||||
"th\030\004 \001(\005\022\021\n\twork_done\030\005 \001(\003\022)\n\014broadcast" +
|
||||
"_by\030\006 \003(\0132\023.wallet.PeerAddress\0224\n\006source",
|
||||
"\030\007 \001(\0162$.wallet.TransactionConfidence.So" +
|
||||
"urce\"O\n\004Type\022\013\n\007UNKNOWN\020\000\022\014\n\010BUILDING\020\001\022" +
|
||||
"\013\n\007PENDING\020\002\022\025\n\021NOT_IN_BEST_CHAIN\020\003\022\010\n\004D" +
|
||||
"EAD\020\004\"A\n\006Source\022\022\n\016SOURCE_UNKNOWN\020\000\022\022\n\016S" +
|
||||
"OURCE_NETWORK\020\001\022\017\n\013SOURCE_SELF\020\002\"\236\004\n\013Tra" +
|
||||
"nsaction\022\017\n\007version\030\001 \002(\005\022\014\n\004hash\030\002 \002(\014\022" +
|
||||
"&\n\004pool\030\003 \001(\0162\030.wallet.Transaction.Pool\022" +
|
||||
"\021\n\tlock_time\030\004 \001(\r\022\022\n\nupdated_at\030\005 \001(\003\0223" +
|
||||
"\n\021transaction_input\030\006 \003(\0132\030.wallet.Trans" +
|
||||
"actionInput\0225\n\022transaction_output\030\007 \003(\0132",
|
||||
"\031.wallet.TransactionOutput\022\022\n\nblock_hash" +
|
||||
"\030\010 \003(\014\022 \n\030block_relativity_offsets\030\013 \003(\005" +
|
||||
"\0221\n\nconfidence\030\t \001(\0132\035.wallet.Transactio" +
|
||||
"nConfidence\0225\n\007purpose\030\n \001(\0162\033.wallet.Tr" +
|
||||
"ansaction.Purpose:\007UNKNOWN\"Y\n\004Pool\022\013\n\007UN" +
|
||||
"SPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010INACTIVE\020\002\022\010\n\004DEAD" +
|
||||
"\020\n\022\013\n\007PENDING\020\020\022\024\n\020PENDING_INACTIVE\020\022\":\n" +
|
||||
"\007Purpose\022\013\n\007UNKNOWN\020\000\022\020\n\014USER_PAYMENT\020\001\022" +
|
||||
"\020\n\014KEY_ROTATION\020\002\"N\n\020ScryptParameters\022\014\n" +
|
||||
"\004salt\030\001 \002(\014\022\020\n\001n\030\002 \001(\003:\00516384\022\014\n\001r\030\003 \001(\005",
|
||||
":\0018\022\014\n\001p\030\004 \001(\005:\0011\"8\n\tExtension\022\n\n\002id\030\001 \002" +
|
||||
"(\t\022\014\n\004data\030\002 \002(\014\022\021\n\tmandatory\030\003 \002(\010\" \n\003T" +
|
||||
"ag\022\013\n\003tag\030\001 \002(\t\022\014\n\004data\030\002 \002(\014\"\261\004\n\006Wallet" +
|
||||
"\022\032\n\022network_identifier\030\001 \002(\t\022\034\n\024last_see" +
|
||||
"n_block_hash\030\002 \001(\014\022\036\n\026last_seen_block_he" +
|
||||
"ight\030\014 \001(\r\022!\n\031last_seen_block_time_secs\030" +
|
||||
"\016 \001(\003\022\030\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n\013trans" +
|
||||
"action\030\004 \003(\0132\023.wallet.Transaction\022&\n\016wat" +
|
||||
"ched_script\030\017 \003(\0132\016.wallet.Script\022C\n\017enc" +
|
||||
"ryption_type\030\005 \001(\0162\035.wallet.Wallet.Encry",
|
||||
"ptionType:\013UNENCRYPTED\0227\n\025encryption_par" +
|
||||
"ameters\030\006 \001(\0132\030.wallet.ScryptParameters\022" +
|
||||
"\022\n\007version\030\007 \001(\005:\0011\022$\n\textension\030\n \003(\0132\021" +
|
||||
".wallet.Extension\022\023\n\013description\030\013 \001(\t\022\031" +
|
||||
"\n\021key_rotation_time\030\r \001(\004\022\031\n\004tags\030\020 \003(\0132" +
|
||||
"\013.wallet.Tag\";\n\016EncryptionType\022\017\n\013UNENCR" +
|
||||
"YPTED\020\001\022\030\n\024ENCRYPTED_SCRYPT_AES\020\002B\035\n\023org" +
|
||||
".bitcoinj.walletB\006Protos"
|
||||
};
|
||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||
|
||||
@@ -97,7 +97,12 @@ public class WalletTest extends TestWithWallet {
|
||||
@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.
|
||||
assertEquals(12, wallet.getKeyChainSeed().toMnemonicCode().size());
|
||||
assertEquals(12, wallet.getKeyChainSeed().getMnemonicCode().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkSeed() throws MnemonicException {
|
||||
wallet.getKeyChainSeed().check();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -54,14 +54,6 @@ public class DeterministicKeyChainTest {
|
||||
assertEquals(secs, checkNotNull(chain.getSeed()).getCreationTimeSeconds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mnemonicCode() throws Exception {
|
||||
final List<String> words = chain.toMnemonicCode();
|
||||
assertEquals("aerobic toe save section draw warm cute upon raccoon mother priority pilot taste sweet next traffic fatal sword dentist original crisp team caution rebel",
|
||||
Joiner.on(" ").join(words));
|
||||
new DeterministicSeed(words, checkNotNull(chain.getSeed()).getCreationTimeSeconds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void derive() throws Exception {
|
||||
ECKey key1 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
|
||||
|
||||
@@ -380,25 +380,25 @@ public class KeyChainGroupTest {
|
||||
|
||||
@Test
|
||||
public void serialization() throws Exception {
|
||||
assertEquals(INITIAL_KEYS + 1 /* for the seed */, group.serializeToProtobuf().size());
|
||||
assertEquals(INITIAL_KEYS + 2 /* 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);
|
||||
List<Protos.Key> protoKeys1 = group.serializeToProtobuf();
|
||||
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 1, protoKeys1.size());
|
||||
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 2 /* 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(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 2 /* 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(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 2 /* 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(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 2 /* for the seed */ + 2, protoKeys2.size());
|
||||
assertTrue(group.hasKey(key1));
|
||||
assertTrue(group.hasKey(key2));
|
||||
|
||||
|
||||
@@ -99,6 +99,13 @@ message Key {
|
||||
* key can be rederived on the fly.
|
||||
*/
|
||||
DETERMINISTIC_KEY = 4;
|
||||
|
||||
/**
|
||||
* Not really a key, but rather contains the mnemonic phrase for a deterministic key hierarchy in the private_key field.
|
||||
* The label and public_key fields are missing. Creation timestamp will exist.
|
||||
* Must be immediately after DETERMINISTIC_KEY
|
||||
*/
|
||||
DETERMINISTIC_MNEMONIC = 5;
|
||||
}
|
||||
required Type type = 1;
|
||||
|
||||
@@ -109,7 +116,7 @@ message Key {
|
||||
optional EncryptedData encrypted_data = 6;
|
||||
|
||||
// The public EC key derived from the private key. We allow both to be stored to avoid mobile clients having to
|
||||
// do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED entries this is missing.
|
||||
// do lots of slow EC math on startup. For DETERMINISTIC_ROOT_SEED and DETERMINISTIC_MNEMONIC entries this is missing.
|
||||
optional bytes public_key = 3;
|
||||
|
||||
// User-provided label associated with the key.
|
||||
|
||||
@@ -853,8 +853,10 @@ public class WalletTool {
|
||||
if (seedStr.contains(" ")) {
|
||||
// Parse as mnemonic code.
|
||||
final List<String> split = ImmutableList.copyOf(Splitter.on(" ").omitEmptyStrings().split(seedStr));
|
||||
String passphrase = ""; // TODO allow user to specify a passphrase
|
||||
seed = new DeterministicSeed(split, passphrase, creationTimeSecs);
|
||||
try {
|
||||
seed = new DeterministicSeed(split, creationTimeSecs);
|
||||
seed.check();
|
||||
} catch (MnemonicException.MnemonicLengthException e) {
|
||||
System.err.println("The seed did not have 12 words in, perhaps you need quotes around it?");
|
||||
return;
|
||||
@@ -864,6 +866,9 @@ public class WalletTool {
|
||||
} catch (MnemonicException.MnemonicChecksumException e) {
|
||||
System.err.println("The seed did not pass checksumming, perhaps one of the words is wrong?");
|
||||
return;
|
||||
} catch (MnemonicException e) {
|
||||
// not reached - all subclasses handled above
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
// Parse as hex or base58
|
||||
|
||||
@@ -12,4 +12,4 @@ if [ ! -e target/bitcoinj-tools-*.jar ] || [[ "$ALWAYS_BUILD_WALLETTOOL" != "" ]
|
||||
mvn package -DskipTests
|
||||
fi
|
||||
|
||||
java -jar target/bitcoinj-tools-*.jar $*
|
||||
java -jar target/bitcoinj-tools-*.jar "$@"
|
||||
|
||||
Reference in New Issue
Block a user