From 03bacf4fa9a025fda50a06d8a911a847c44ab9cd Mon Sep 17 00:00:00 2001 From: Devrandom Date: Sat, 20 Sep 2014 16:02:30 -0700 Subject: [PATCH] Cache deterministic seed --- .../google/bitcoin/crypto/MnemonicCode.java | 5 +- .../bitcoin/wallet/DeterministicKeyChain.java | 38 +- .../bitcoin/wallet/DeterministicSeed.java | 29 +- .../main/java/org/bitcoinj/wallet/Protos.java | 531 +++++++++++++++--- .../wallet/DeterministicKeyChainTest.java | 4 +- .../deterministic-wallet-serialization.txt | 1 + core/src/wallet.proto | 7 + .../bitcoin/examples/RestoreFromSeed.java | 14 +- .../com/google/bitcoin/tools/WalletTool.java | 2 +- .../WalletSettingsController.java | 2 +- 10 files changed, 543 insertions(+), 90 deletions(-) diff --git a/core/src/main/java/com/google/bitcoin/crypto/MnemonicCode.java b/core/src/main/java/com/google/bitcoin/crypto/MnemonicCode.java index 9ed9de3c..d2d5ff63 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/MnemonicCode.java +++ b/core/src/main/java/com/google/bitcoin/crypto/MnemonicCode.java @@ -135,7 +135,10 @@ public class MnemonicCode { String pass = Joiner.on(' ').join(words); String salt = "mnemonic" + passphrase; - return PBKDF2SHA512.derive(pass, salt, PBKDF2_ROUNDS, 64); + long start = System.currentTimeMillis(); + byte[] seed = PBKDF2SHA512.derive(pass, salt, PBKDF2_ROUNDS, 64); + log.info("PBKDF2 took {}ms", System.currentTimeMillis() - start); + return seed; } /** diff --git a/core/src/main/java/com/google/bitcoin/wallet/DeterministicKeyChain.java b/core/src/main/java/com/google/bitcoin/wallet/DeterministicKeyChain.java index c7d0846e..0b4e0034 100644 --- a/core/src/main/java/com/google/bitcoin/wallet/DeterministicKeyChain.java +++ b/core/src/main/java/com/google/bitcoin/wallet/DeterministicKeyChain.java @@ -586,6 +586,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain { if (seed != null) { Protos.Key.Builder mnemonicEntry = BasicKeyChain.serializeEncryptableItem(seed); mnemonicEntry.setType(Protos.Key.Type.DETERMINISTIC_MNEMONIC); + serializeSeedEncryptableItem(seed, mnemonicEntry); entries.add(mnemonicEntry.build()); } Map keys = basicKeyChain.serializeToEditableProtobufs(); @@ -628,6 +629,7 @@ public class DeterministicKeyChain implements EncryptableKeyChain { List chains = newLinkedList(); DeterministicSeed seed = null; DeterministicKeyChain chain = null; + int lookaheadSize = -1; for (Protos.Key key : keys) { final Protos.Key.Type t = key.getType(); @@ -642,11 +644,25 @@ public class DeterministicKeyChain implements EncryptableKeyChain { long timestamp = key.getCreationTimestamp() / 1000; String passphrase = DEFAULT_PASSPHRASE_FOR_MNEMONIC; // FIXME allow non-empty passphrase if (key.hasSecretBytes()) { - seed = new DeterministicSeed(key.getSecretBytes().toStringUtf8(), passphrase, timestamp); + if (key.hasEncryptedDeterministicSeed()) + throw new UnreadableWalletException("Malformed key proto: " + key.toString()); + byte[] seedBytes = null; + if (key.hasDeterministicSeed()) { + seedBytes = key.getDeterministicSeed().toByteArray(); + } + seed = new DeterministicSeed(key.getSecretBytes().toStringUtf8(), seedBytes, passphrase, timestamp); } else if (key.hasEncryptedData()) { + if (key.hasDeterministicSeed()) + throw new UnreadableWalletException("Malformed key proto: " + key.toString()); EncryptedData data = new EncryptedData(key.getEncryptedData().getInitialisationVector().toByteArray(), key.getEncryptedData().getEncryptedPrivateKey().toByteArray()); - seed = new DeterministicSeed(data, timestamp); + EncryptedData encryptedSeedBytes = null; + if (key.hasEncryptedDeterministicSeed()) { + Protos.EncryptedData encryptedSeed = key.getEncryptedDeterministicSeed(); + encryptedSeedBytes = new EncryptedData(encryptedSeed.getInitialisationVector().toByteArray(), + encryptedSeed.getEncryptedPrivateKey().toByteArray()); + } + seed = new DeterministicSeed(data, encryptedSeedBytes, timestamp); } else { throw new UnreadableWalletException("Malformed key proto: " + key.toString()); } @@ -1059,4 +1075,22 @@ public class DeterministicKeyChain implements EncryptableKeyChain { } return keys.build(); } + + /*package*/ static void serializeSeedEncryptableItem(DeterministicSeed seed, Protos.Key.Builder proto) { + // The seed can be missing if we have not derived it yet from the mnemonic. + // This will not normally happen once all the wallets are on the latest code that caches + // the seed. + if (seed.isEncrypted() && seed.getEncryptedSeedData() != null) { + EncryptedData data = seed.getEncryptedSeedData(); + proto.getEncryptedDeterministicSeedBuilder() + .setEncryptedPrivateKey(ByteString.copyFrom(data.encryptedBytes)) + .setInitialisationVector(ByteString.copyFrom(data.initialisationVector)); + // We don't allow mixing of encryption types at the moment. + checkState(seed.getEncryptionType() == Protos.Wallet.EncryptionType.ENCRYPTED_SCRYPT_AES); + } else { + final byte[] secret = seed.getSeedBytes(); + if (secret != null) + proto.setDeterministicSeed(ByteString.copyFrom(secret)); + } + } } diff --git a/core/src/main/java/com/google/bitcoin/wallet/DeterministicSeed.java b/core/src/main/java/com/google/bitcoin/wallet/DeterministicSeed.java index 32d683b6..4e8cda53 100644 --- a/core/src/main/java/com/google/bitcoin/wallet/DeterministicSeed.java +++ b/core/src/main/java/com/google/bitcoin/wallet/DeterministicSeed.java @@ -48,10 +48,11 @@ public class DeterministicSeed implements EncryptableItem { @Nullable private final byte[] seed; @Nullable private List mnemonicCode; @Nullable private EncryptedData encryptedMnemonicCode; + @Nullable private EncryptedData encryptedSeed; private final long creationTimeSeconds; - public DeterministicSeed(String mnemonicCode, String passphrase, long creationTimeSeconds) throws UnreadableWalletException { - this(decodeMnemonicCode(mnemonicCode), passphrase, creationTimeSeconds); + public DeterministicSeed(String mnemonicCode, byte[] seed, String passphrase, long creationTimeSeconds) throws UnreadableWalletException { + this(decodeMnemonicCode(mnemonicCode), seed, passphrase, creationTimeSeconds); } public DeterministicSeed(byte[] seed, List mnemonic, long creationTimeSeconds) { @@ -61,10 +62,11 @@ public class DeterministicSeed implements EncryptableItem { this.creationTimeSeconds = creationTimeSeconds; } - public DeterministicSeed(EncryptedData encryptedMnemonic, long creationTimeSeconds) { + public DeterministicSeed(EncryptedData encryptedMnemonic, @Nullable EncryptedData encryptedSeed, long creationTimeSeconds) { this.seed = null; this.mnemonicCode = null; this.encryptedMnemonicCode = checkNotNull(encryptedMnemonic); + this.encryptedSeed = encryptedSeed; this.creationTimeSeconds = creationTimeSeconds; } @@ -72,11 +74,12 @@ public class DeterministicSeed implements EncryptableItem { * Constructs a seed from a BIP 39 mnemonic code. See {@link com.google.bitcoin.crypto.MnemonicCode} for more * details on this scheme. * @param mnemonicCode A list of words. + * @param seed The derived seed, or pass null to derive it from mnemonicCode (slow) * @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(List mnemonicCode, String passphrase, long creationTimeSeconds) { - this(MnemonicCode.toSeed(mnemonicCode, passphrase), mnemonicCode, creationTimeSeconds); + public DeterministicSeed(List mnemonicCode, @Nullable byte[] seed, String passphrase, long creationTimeSeconds) { + this((seed != null ? seed : MnemonicCode.toSeed(mnemonicCode, passphrase)), mnemonicCode, creationTimeSeconds); } /** @@ -167,6 +170,11 @@ public class DeterministicSeed implements EncryptableItem { return Protos.Wallet.EncryptionType.ENCRYPTED_SCRYPT_AES; } + @Nullable + public EncryptedData getEncryptedSeedData() { + return encryptedSeed; + } + @Override public long getCreationTimeSeconds() { return creationTimeSeconds; @@ -175,8 +183,9 @@ public class DeterministicSeed implements EncryptableItem { public DeterministicSeed encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) { checkState(encryptedMnemonicCode == null, "Trying to encrypt seed twice"); checkState(mnemonicCode != null, "Mnemonic missing so cannot encrypt"); - EncryptedData mnemonic = keyCrypter.encrypt(getMnemonicAsBytes(), aesKey); - return new DeterministicSeed(mnemonic, creationTimeSeconds); + EncryptedData encryptedMnemonic = keyCrypter.encrypt(getMnemonicAsBytes(), aesKey); + EncryptedData encryptedSeed = keyCrypter.encrypt(seed, aesKey); + return new DeterministicSeed(encryptedMnemonic, encryptedSeed, creationTimeSeconds); } private byte[] getMnemonicAsBytes() { @@ -187,13 +196,17 @@ public class DeterministicSeed implements EncryptableItem { checkState(isEncrypted()); checkNotNull(encryptedMnemonicCode); List mnemonic = null; + byte[] seed = null; try { mnemonic = decodeMnemonicCode(crypter.decrypt(encryptedMnemonicCode, aesKey)); + if (encryptedSeed != null) { + seed = crypter.decrypt(encryptedSeed, aesKey); + } } catch (UnreadableWalletException e) { // TODO what is the best way to handle this exception? throw new RuntimeException(e); } - return new DeterministicSeed(mnemonic, passphrase, creationTimeSeconds); + return new DeterministicSeed(mnemonic, seed, passphrase, creationTimeSeconds); } @Override diff --git a/core/src/main/java/org/bitcoinj/wallet/Protos.java b/core/src/main/java/org/bitcoinj/wallet/Protos.java index bcb1fc97..63eb6ef4 100644 --- a/core/src/main/java/org/bitcoinj/wallet/Protos.java +++ b/core/src/main/java/org/bitcoinj/wallet/Protos.java @@ -2345,6 +2345,54 @@ public final class Protos { * optional .wallet.DeterministicKey deterministic_key = 7; */ org.bitcoinj.wallet.Protos.DeterministicKeyOrBuilder getDeterministicKeyOrBuilder(); + + // optional bytes deterministic_seed = 8; + /** + * optional bytes deterministic_seed = 8; + * + *
+     **
+     * The seed for a deterministic key hierarchy.  Derived from the mnemonic,
+     * but cached here for quick startup.  Only applicable to a DETERMINISTIC_MNEMONIC key entry.
+     * 
+ */ + boolean hasDeterministicSeed(); + /** + * optional bytes deterministic_seed = 8; + * + *
+     **
+     * The seed for a deterministic key hierarchy.  Derived from the mnemonic,
+     * but cached here for quick startup.  Only applicable to a DETERMINISTIC_MNEMONIC key entry.
+     * 
+ */ + com.google.protobuf.ByteString getDeterministicSeed(); + + // optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+     ** Encrypted version of the seed 
+     * 
+ */ + boolean hasEncryptedDeterministicSeed(); + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+     ** Encrypted version of the seed 
+     * 
+ */ + org.bitcoinj.wallet.Protos.EncryptedData getEncryptedDeterministicSeed(); + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+     ** Encrypted version of the seed 
+     * 
+ */ + org.bitcoinj.wallet.Protos.EncryptedDataOrBuilder getEncryptedDeterministicSeedOrBuilder(); } /** * Protobuf type {@code wallet.Key} @@ -2465,6 +2513,24 @@ public final class Protos { bitField0_ |= 0x00000040; break; } + case 66: { + bitField0_ |= 0x00000080; + deterministicSeed_ = input.readBytes(); + break; + } + case 74: { + org.bitcoinj.wallet.Protos.EncryptedData.Builder subBuilder = null; + if (((bitField0_ & 0x00000100) == 0x00000100)) { + subBuilder = encryptedDeterministicSeed_.toBuilder(); + } + encryptedDeterministicSeed_ = input.readMessage(org.bitcoinj.wallet.Protos.EncryptedData.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(encryptedDeterministicSeed_); + encryptedDeterministicSeed_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000100; + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -2856,6 +2922,68 @@ public final class Protos { return deterministicKey_; } + // optional bytes deterministic_seed = 8; + public static final int DETERMINISTIC_SEED_FIELD_NUMBER = 8; + private com.google.protobuf.ByteString deterministicSeed_; + /** + * optional bytes deterministic_seed = 8; + * + *
+     **
+     * The seed for a deterministic key hierarchy.  Derived from the mnemonic,
+     * but cached here for quick startup.  Only applicable to a DETERMINISTIC_MNEMONIC key entry.
+     * 
+ */ + public boolean hasDeterministicSeed() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + /** + * optional bytes deterministic_seed = 8; + * + *
+     **
+     * The seed for a deterministic key hierarchy.  Derived from the mnemonic,
+     * but cached here for quick startup.  Only applicable to a DETERMINISTIC_MNEMONIC key entry.
+     * 
+ */ + public com.google.protobuf.ByteString getDeterministicSeed() { + return deterministicSeed_; + } + + // optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + public static final int ENCRYPTED_DETERMINISTIC_SEED_FIELD_NUMBER = 9; + private org.bitcoinj.wallet.Protos.EncryptedData encryptedDeterministicSeed_; + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+     ** Encrypted version of the seed 
+     * 
+ */ + public boolean hasEncryptedDeterministicSeed() { + return ((bitField0_ & 0x00000100) == 0x00000100); + } + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+     ** Encrypted version of the seed 
+     * 
+ */ + public org.bitcoinj.wallet.Protos.EncryptedData getEncryptedDeterministicSeed() { + return encryptedDeterministicSeed_; + } + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+     ** Encrypted version of the seed 
+     * 
+ */ + public org.bitcoinj.wallet.Protos.EncryptedDataOrBuilder getEncryptedDeterministicSeedOrBuilder() { + return encryptedDeterministicSeed_; + } + private void initFields() { type_ = org.bitcoinj.wallet.Protos.Key.Type.ORIGINAL; secretBytes_ = com.google.protobuf.ByteString.EMPTY; @@ -2864,6 +2992,8 @@ public final class Protos { label_ = ""; creationTimestamp_ = 0L; deterministicKey_ = org.bitcoinj.wallet.Protos.DeterministicKey.getDefaultInstance(); + deterministicSeed_ = com.google.protobuf.ByteString.EMPTY; + encryptedDeterministicSeed_ = org.bitcoinj.wallet.Protos.EncryptedData.getDefaultInstance(); } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -2886,6 +3016,12 @@ public final class Protos { return false; } } + if (hasEncryptedDeterministicSeed()) { + if (!getEncryptedDeterministicSeed().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } memoizedIsInitialized = 1; return true; } @@ -2914,6 +3050,12 @@ public final class Protos { if (((bitField0_ & 0x00000040) == 0x00000040)) { output.writeMessage(7, deterministicKey_); } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + output.writeBytes(8, deterministicSeed_); + } + if (((bitField0_ & 0x00000100) == 0x00000100)) { + output.writeMessage(9, encryptedDeterministicSeed_); + } getUnknownFields().writeTo(output); } @@ -2951,6 +3093,14 @@ public final class Protos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(7, deterministicKey_); } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(8, deterministicSeed_); + } + if (((bitField0_ & 0x00000100) == 0x00000100)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(9, encryptedDeterministicSeed_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -3072,6 +3222,7 @@ public final class Protos { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { getEncryptedDataFieldBuilder(); getDeterministicKeyFieldBuilder(); + getEncryptedDeterministicSeedFieldBuilder(); } } private static Builder create() { @@ -3102,6 +3253,14 @@ public final class Protos { deterministicKeyBuilder_.clear(); } bitField0_ = (bitField0_ & ~0x00000040); + deterministicSeed_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000080); + if (encryptedDeterministicSeedBuilder_ == null) { + encryptedDeterministicSeed_ = org.bitcoinj.wallet.Protos.EncryptedData.getDefaultInstance(); + } else { + encryptedDeterministicSeedBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000100); return this; } @@ -3166,6 +3325,18 @@ public final class Protos { } else { result.deterministicKey_ = deterministicKeyBuilder_.build(); } + if (((from_bitField0_ & 0x00000080) == 0x00000080)) { + to_bitField0_ |= 0x00000080; + } + result.deterministicSeed_ = deterministicSeed_; + if (((from_bitField0_ & 0x00000100) == 0x00000100)) { + to_bitField0_ |= 0x00000100; + } + if (encryptedDeterministicSeedBuilder_ == null) { + result.encryptedDeterministicSeed_ = encryptedDeterministicSeed_; + } else { + result.encryptedDeterministicSeed_ = encryptedDeterministicSeedBuilder_.build(); + } result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -3205,6 +3376,12 @@ public final class Protos { if (other.hasDeterministicKey()) { mergeDeterministicKey(other.getDeterministicKey()); } + if (other.hasDeterministicSeed()) { + setDeterministicSeed(other.getDeterministicSeed()); + } + if (other.hasEncryptedDeterministicSeed()) { + mergeEncryptedDeterministicSeed(other.getEncryptedDeterministicSeed()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -3226,6 +3403,12 @@ public final class Protos { return false; } } + if (hasEncryptedDeterministicSeed()) { + if (!getEncryptedDeterministicSeed().isInitialized()) { + + return false; + } + } return true; } @@ -3817,6 +4000,219 @@ public final class Protos { return deterministicKeyBuilder_; } + // optional bytes deterministic_seed = 8; + private com.google.protobuf.ByteString deterministicSeed_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes deterministic_seed = 8; + * + *
+       **
+       * The seed for a deterministic key hierarchy.  Derived from the mnemonic,
+       * but cached here for quick startup.  Only applicable to a DETERMINISTIC_MNEMONIC key entry.
+       * 
+ */ + public boolean hasDeterministicSeed() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + /** + * optional bytes deterministic_seed = 8; + * + *
+       **
+       * The seed for a deterministic key hierarchy.  Derived from the mnemonic,
+       * but cached here for quick startup.  Only applicable to a DETERMINISTIC_MNEMONIC key entry.
+       * 
+ */ + public com.google.protobuf.ByteString getDeterministicSeed() { + return deterministicSeed_; + } + /** + * optional bytes deterministic_seed = 8; + * + *
+       **
+       * The seed for a deterministic key hierarchy.  Derived from the mnemonic,
+       * but cached here for quick startup.  Only applicable to a DETERMINISTIC_MNEMONIC key entry.
+       * 
+ */ + public Builder setDeterministicSeed(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000080; + deterministicSeed_ = value; + onChanged(); + return this; + } + /** + * optional bytes deterministic_seed = 8; + * + *
+       **
+       * The seed for a deterministic key hierarchy.  Derived from the mnemonic,
+       * but cached here for quick startup.  Only applicable to a DETERMINISTIC_MNEMONIC key entry.
+       * 
+ */ + public Builder clearDeterministicSeed() { + bitField0_ = (bitField0_ & ~0x00000080); + deterministicSeed_ = getDefaultInstance().getDeterministicSeed(); + onChanged(); + return this; + } + + // optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + private org.bitcoinj.wallet.Protos.EncryptedData encryptedDeterministicSeed_ = org.bitcoinj.wallet.Protos.EncryptedData.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.bitcoinj.wallet.Protos.EncryptedData, org.bitcoinj.wallet.Protos.EncryptedData.Builder, org.bitcoinj.wallet.Protos.EncryptedDataOrBuilder> encryptedDeterministicSeedBuilder_; + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+       ** Encrypted version of the seed 
+       * 
+ */ + public boolean hasEncryptedDeterministicSeed() { + return ((bitField0_ & 0x00000100) == 0x00000100); + } + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+       ** Encrypted version of the seed 
+       * 
+ */ + public org.bitcoinj.wallet.Protos.EncryptedData getEncryptedDeterministicSeed() { + if (encryptedDeterministicSeedBuilder_ == null) { + return encryptedDeterministicSeed_; + } else { + return encryptedDeterministicSeedBuilder_.getMessage(); + } + } + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+       ** Encrypted version of the seed 
+       * 
+ */ + public Builder setEncryptedDeterministicSeed(org.bitcoinj.wallet.Protos.EncryptedData value) { + if (encryptedDeterministicSeedBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + encryptedDeterministicSeed_ = value; + onChanged(); + } else { + encryptedDeterministicSeedBuilder_.setMessage(value); + } + bitField0_ |= 0x00000100; + return this; + } + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+       ** Encrypted version of the seed 
+       * 
+ */ + public Builder setEncryptedDeterministicSeed( + org.bitcoinj.wallet.Protos.EncryptedData.Builder builderForValue) { + if (encryptedDeterministicSeedBuilder_ == null) { + encryptedDeterministicSeed_ = builderForValue.build(); + onChanged(); + } else { + encryptedDeterministicSeedBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000100; + return this; + } + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+       ** Encrypted version of the seed 
+       * 
+ */ + public Builder mergeEncryptedDeterministicSeed(org.bitcoinj.wallet.Protos.EncryptedData value) { + if (encryptedDeterministicSeedBuilder_ == null) { + if (((bitField0_ & 0x00000100) == 0x00000100) && + encryptedDeterministicSeed_ != org.bitcoinj.wallet.Protos.EncryptedData.getDefaultInstance()) { + encryptedDeterministicSeed_ = + org.bitcoinj.wallet.Protos.EncryptedData.newBuilder(encryptedDeterministicSeed_).mergeFrom(value).buildPartial(); + } else { + encryptedDeterministicSeed_ = value; + } + onChanged(); + } else { + encryptedDeterministicSeedBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000100; + return this; + } + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+       ** Encrypted version of the seed 
+       * 
+ */ + public Builder clearEncryptedDeterministicSeed() { + if (encryptedDeterministicSeedBuilder_ == null) { + encryptedDeterministicSeed_ = org.bitcoinj.wallet.Protos.EncryptedData.getDefaultInstance(); + onChanged(); + } else { + encryptedDeterministicSeedBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000100); + return this; + } + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+       ** Encrypted version of the seed 
+       * 
+ */ + public org.bitcoinj.wallet.Protos.EncryptedData.Builder getEncryptedDeterministicSeedBuilder() { + bitField0_ |= 0x00000100; + onChanged(); + return getEncryptedDeterministicSeedFieldBuilder().getBuilder(); + } + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+       ** Encrypted version of the seed 
+       * 
+ */ + public org.bitcoinj.wallet.Protos.EncryptedDataOrBuilder getEncryptedDeterministicSeedOrBuilder() { + if (encryptedDeterministicSeedBuilder_ != null) { + return encryptedDeterministicSeedBuilder_.getMessageOrBuilder(); + } else { + return encryptedDeterministicSeed_; + } + } + /** + * optional .wallet.EncryptedData encrypted_deterministic_seed = 9; + * + *
+       ** Encrypted version of the seed 
+       * 
+ */ + private com.google.protobuf.SingleFieldBuilder< + org.bitcoinj.wallet.Protos.EncryptedData, org.bitcoinj.wallet.Protos.EncryptedData.Builder, org.bitcoinj.wallet.Protos.EncryptedDataOrBuilder> + getEncryptedDeterministicSeedFieldBuilder() { + if (encryptedDeterministicSeedBuilder_ == null) { + encryptedDeterministicSeedBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.bitcoinj.wallet.Protos.EncryptedData, org.bitcoinj.wallet.Protos.EncryptedData.Builder, org.bitcoinj.wallet.Protos.EncryptedDataOrBuilder>( + encryptedDeterministicSeed_, + getParentForChildren(), + isClean()); + encryptedDeterministicSeed_ = null; + } + return encryptedDeterministicSeedBuilder_; + } + // @@protoc_insertion_point(builder_scope:wallet.Key) } @@ -18610,75 +19006,78 @@ 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\"\301\002\n\003Key\022\036\n\004type\030\001 \002(\0162\020.wall" + + "owing\030\005 \001(\010\"\232\003\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\"a\n\004" + - "Type\022\014\n\010ORIGINAL\020\001\022\030\n\024ENCRYPTED_SCRYPT_A" + - "ES\020\002\022\032\n\026DETERMINISTIC_MNEMONIC\020\003\022\025\n\021DETE" + - "RMINISTIC_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\020Trans" + - "actionInput\022\"\n\032transaction_out_point_has" + - "h\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\021TransactionOutput" + - "\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\032spen" + - "t_by_transaction_index\030\004 \001(\005\"\211\003\n\025Transac" + - "tionConfidence\0220\n\004type\030\001 \001(\0162\".wallet.Tr" + - "ansactionConfidence.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)\n\014broadcast_by\030\006 \003(" + - "\0132\023.wallet.PeerAddress\0224\n\006source\030\007 \001(\0162$" + - ".wallet.TransactionConfidence.Source\"O\n\004", - "Type\022\013\n\007UNKNOWN\020\000\022\014\n\010BUILDING\020\001\022\013\n\007PENDI" + - "NG\020\002\022\025\n\021NOT_IN_BEST_CHAIN\020\003\022\010\n\004DEAD\020\004\"A\n" + - "\006Source\022\022\n\016SOURCE_UNKNOWN\020\000\022\022\n\016SOURCE_NE" + - "TWORK\020\001\022\017\n\013SOURCE_SELF\020\002\"\264\005\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.Transaction.Pool\022\021\n\tlock_" + - "time\030\004 \001(\r\022\022\n\nupdated_at\030\005 \001(\003\0223\n\021transa" + - "ction_input\030\006 \003(\0132\030.wallet.TransactionIn" + - "put\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\nconf" + - "idence\030\t \001(\0132\035.wallet.TransactionConfide" + - "nce\0225\n\007purpose\030\n \001(\0162\033.wallet.Transactio" + - "n.Purpose:\007UNKNOWN\022+\n\rexchange_rate\030\014 \001(" + - "\0132\024.wallet.ExchangeRate\022\014\n\004memo\030\r \001(\t\"Y\n" + - "\004Pool\022\013\n\007UNSPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010INACTIV" + - "E\020\002\022\010\n\004DEAD\020\n\022\013\n\007PENDING\020\020\022\024\n\020PENDING_IN" + - "ACTIVE\020\022\"\224\001\n\007Purpose\022\013\n\007UNKNOWN\020\000\022\020\n\014USE" + - "R_PAYMENT\020\001\022\020\n\014KEY_ROTATION\020\002\022\034\n\030ASSURAN" + - "CE_CONTRACT_CLAIM\020\003\022\035\n\031ASSURANCE_CONTRAC", - "T_PLEDGE\020\004\022\033\n\027ASSURANCE_CONTRACT_STUB\020\005\"" + - "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\003Tag\022\013\n\003tag\030\001 \002(\t\022\014\n" + - "\004data\030\002 \002(\014\"5\n\021TransactionSigner\022\022\n\nclas" + - "s_name\030\001 \002(\t\022\014\n\004data\030\002 \001(\014\"\211\005\n\006Wallet\022\032\n" + - "\022network_identifier\030\001 \002(\t\022\034\n\024last_seen_b" + - "lock_hash\030\002 \001(\014\022\036\n\026last_seen_block_heigh" + - "t\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\013transact" + - "ion\030\004 \003(\0132\023.wallet.Transaction\022&\n\016watche" + - "d_script\030\017 \003(\0132\016.wallet.Script\022C\n\017encryp" + - "tion_type\030\005 \001(\0162\035.wallet.Wallet.Encrypti" + - "onType:\013UNENCRYPTED\0227\n\025encryption_parame" + - "ters\030\006 \001(\0132\030.wallet.ScryptParameters\022\022\n\007" + - "version\030\007 \001(\005:\0011\022$\n\textension\030\n \003(\0132\021.wa" + - "llet.Extension\022\023\n\013description\030\013 \001(\t\022\031\n\021k" + - "ey_rotation_time\030\r \001(\004\022\031\n\004tags\030\020 \003(\0132\013.w" + - "allet.Tag\0226\n\023transaction_signers\030\021 \003(\0132\031", - ".wallet.TransactionSigner\022\036\n\023sigsRequire" + - "dToSpend\030\022 \001(\r:\0011\";\n\016EncryptionType\022\017\n\013U" + - "NENCRYPTED\020\001\022\030\n\024ENCRYPTED_SCRYPT_AES\020\002\"R" + - "\n\014ExchangeRate\022\022\n\ncoin_value\030\001 \002(\003\022\022\n\nfi" + - "at_value\030\002 \002(\003\022\032\n\022fiat_currency_code\030\003 \002" + - "(\tB\035\n\023org.bitcoinj.walletB\006Protos" + "_key\030\007 \001(\0132\030.wallet.DeterministicKey\022\032\n\022" + + "deterministic_seed\030\010 \001(\014\022;\n\034encrypted_de" + + "terministic_seed\030\t \001(\0132\025.wallet.Encrypte" + + "dData\"a\n\004Type\022\014\n\010ORIGINAL\020\001\022\030\n\024ENCRYPTED" + + "_SCRYPT_AES\020\002\022\032\n\026DETERMINISTIC_MNEMONIC\020" + + "\003\022\025\n\021DETERMINISTIC_KEY\020\004\"5\n\006Script\022\017\n\007pr" + + "ogram\030\001 \002(\014\022\032\n\022creation_timestamp\030\002 \002(\003\"" + + "\222\001\n\020TransactionInput\022\"\n\032transaction_out_", + "point_hash\030\001 \002(\014\022#\n\033transaction_out_poin" + + "t_index\030\002 \002(\r\022\024\n\014script_bytes\030\003 \002(\014\022\020\n\010s" + + "equence\030\004 \001(\r\022\r\n\005value\030\005 \001(\003\"\177\n\021Transact" + + "ionOutput\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\032spent_by_transaction_index\030\004 \001(\005\"\211\003" + + "\n\025TransactionConfidence\0220\n\004type\030\001 \001(\0162\"." + + "wallet.TransactionConfidence.Type\022\032\n\022app" + + "eared_at_height\030\002 \001(\005\022\036\n\026overriding_tran" + + "saction\030\003 \001(\014\022\r\n\005depth\030\004 \001(\005\022)\n\014broadcas", + "t_by\030\006 \003(\0132\023.wallet.PeerAddress\0224\n\006sourc" + + "e\030\007 \001(\0162$.wallet.TransactionConfidence.S" + + "ource\"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\004" + + "DEAD\020\004\"A\n\006Source\022\022\n\016SOURCE_UNKNOWN\020\000\022\022\n\016" + + "SOURCE_NETWORK\020\001\022\017\n\013SOURCE_SELF\020\002\"\264\005\n\013Tr" + + "ansaction\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\022" + + "3\n\021transaction_input\030\006 \003(\0132\030.wallet.Tran", + "sactionInput\0225\n\022transaction_output\030\007 \003(\013" + + "2\031.wallet.TransactionOutput\022\022\n\nblock_has" + + "h\030\010 \003(\014\022 \n\030block_relativity_offsets\030\013 \003(" + + "\005\0221\n\nconfidence\030\t \001(\0132\035.wallet.Transacti" + + "onConfidence\0225\n\007purpose\030\n \001(\0162\033.wallet.T" + + "ransaction.Purpose:\007UNKNOWN\022+\n\rexchange_" + + "rate\030\014 \001(\0132\024.wallet.ExchangeRate\022\014\n\004memo" + + "\030\r \001(\t\"Y\n\004Pool\022\013\n\007UNSPENT\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\020P" + + "ENDING_INACTIVE\020\022\"\224\001\n\007Purpose\022\013\n\007UNKNOWN", + "\020\000\022\020\n\014USER_PAYMENT\020\001\022\020\n\014KEY_ROTATION\020\002\022\034" + + "\n\030ASSURANCE_CONTRACT_CLAIM\020\003\022\035\n\031ASSURANC" + + "E_CONTRACT_PLEDGE\020\004\022\033\n\027ASSURANCE_CONTRAC" + + "T_STUB\020\005\"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\004dat" + + "a\030\002 \002(\014\022\021\n\tmandatory\030\003 \002(\010\" \n\003Tag\022\013\n\003tag" + + "\030\001 \002(\t\022\014\n\004data\030\002 \002(\014\"5\n\021TransactionSigne" + + "r\022\022\n\nclass_name\030\001 \002(\t\022\014\n\004data\030\002 \001(\014\"\211\005\n\006" + + "Wallet\022\032\n\022network_identifier\030\001 \002(\t\022\034\n\024la", + "st_seen_block_hash\030\002 \001(\014\022\036\n\026last_seen_bl" + + "ock_height\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" + + "\013transaction\030\004 \003(\0132\023.wallet.Transaction\022" + + "&\n\016watched_script\030\017 \003(\0132\016.wallet.Script\022" + + "C\n\017encryption_type\030\005 \001(\0162\035.wallet.Wallet" + + ".EncryptionType:\013UNENCRYPTED\0227\n\025encrypti" + + "on_parameters\030\006 \001(\0132\030.wallet.ScryptParam" + + "eters\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\0226\n\023transaction_signer" + + "s\030\021 \003(\0132\031.wallet.TransactionSigner\022\036\n\023si" + + "gsRequiredToSpend\030\022 \001(\r:\0011\";\n\016Encryption" + + "Type\022\017\n\013UNENCRYPTED\020\001\022\030\n\024ENCRYPTED_SCRYP" + + "T_AES\020\002\"R\n\014ExchangeRate\022\022\n\ncoin_value\030\001 " + + "\002(\003\022\022\n\nfiat_value\030\002 \002(\003\022\032\n\022fiat_currency" + + "_code\030\003 \002(\tB\035\n\023org.bitcoinj.walletB\006Prot" + + "os" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -18708,7 +19107,7 @@ public final class Protos { internal_static_wallet_Key_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_wallet_Key_descriptor, - new java.lang.String[] { "Type", "SecretBytes", "EncryptedData", "PublicKey", "Label", "CreationTimestamp", "DeterministicKey", }); + new java.lang.String[] { "Type", "SecretBytes", "EncryptedData", "PublicKey", "Label", "CreationTimestamp", "DeterministicKey", "DeterministicSeed", "EncryptedDeterministicSeed", }); internal_static_wallet_Script_descriptor = getDescriptor().getMessageTypes().get(4); internal_static_wallet_Script_fieldAccessorTable = new diff --git a/core/src/test/java/com/google/bitcoin/wallet/DeterministicKeyChainTest.java b/core/src/test/java/com/google/bitcoin/wallet/DeterministicKeyChainTest.java index 76dd50f2..ed72d412 100644 --- a/core/src/test/java/com/google/bitcoin/wallet/DeterministicKeyChainTest.java +++ b/core/src/test/java/com/google/bitcoin/wallet/DeterministicKeyChainTest.java @@ -129,9 +129,9 @@ public class DeterministicKeyChainTest { DeterministicKey key2 = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicKey key3 = chain.getKey(KeyChain.KeyPurpose.CHANGE); List keys = chain.serializeToProtobuf(); - // 1 root seed, 1 master key, 1 account key, 2 internal keys, 3 derived, 20 lookahead and 5 lookahead threshold. + // 1 mnemonic/seed, 1 master key, 1 account key, 2 internal keys, 3 derived, 20 lookahead and 5 lookahead threshold. int numItems = - 1 // root seed + 1 // mnemonic/seed + 1 // master key + 1 // account key + 2 // ext/int parent keys diff --git a/core/src/test/resources/com/google/bitcoin/wallet/deterministic-wallet-serialization.txt b/core/src/test/resources/com/google/bitcoin/wallet/deterministic-wallet-serialization.txt index ae095fad..33b501c1 100644 --- a/core/src/test/resources/com/google/bitcoin/wallet/deterministic-wallet-serialization.txt +++ b/core/src/test/resources/com/google/bitcoin/wallet/deterministic-wallet-serialization.txt @@ -1,6 +1,7 @@ type: DETERMINISTIC_MNEMONIC secret_bytes: "aerobic toe save section draw warm cute upon raccoon mother priority pilot taste sweet next traffic fatal sword dentist original crisp team caution rebel" creation_timestamp: 1389353062000 +deterministic_seed: "E\032\356\206\230,\275\263\364=\334^f\307\037\350\321X7R\262z\205\3564\371tp\2639R\342\027 J\266\253\250\320\022\031\233\271~O$\330\260\214\fz\231tI\353\215*\037\355\205\213.\224?" type: DETERMINISTIC_KEY secret_bytes: "\270E0\202(\362b\023\276\264\347\226E2\360\221\347\325\233L\203\3276\272\213\2436&\304\373\221\025" diff --git a/core/src/wallet.proto b/core/src/wallet.proto index b5229110..4aa4d921 100644 --- a/core/src/wallet.proto +++ b/core/src/wallet.proto @@ -121,6 +121,13 @@ message Key { optional int64 creation_timestamp = 5; optional DeterministicKey deterministic_key = 7; + + // The seed for a deterministic key hierarchy. Derived from the mnemonic, + // but cached here for quick startup. Only applicable to a DETERMINISTIC_MNEMONIC key entry. + optional bytes deterministic_seed = 8; + + // Encrypted version of the seed + optional EncryptedData encrypted_deterministic_seed = 9; } message Script { diff --git a/examples/src/main/java/com/google/bitcoin/examples/RestoreFromSeed.java b/examples/src/main/java/com/google/bitcoin/examples/RestoreFromSeed.java index 7814e493..01ef8652 100644 --- a/examples/src/main/java/com/google/bitcoin/examples/RestoreFromSeed.java +++ b/examples/src/main/java/com/google/bitcoin/examples/RestoreFromSeed.java @@ -1,17 +1,13 @@ package com.google.bitcoin.examples; -import java.io.File; - -import com.google.bitcoin.core.BlockChain; -import com.google.bitcoin.core.DownloadListener; -import com.google.bitcoin.core.NetworkParameters; -import com.google.bitcoin.core.PeerGroup; -import com.google.bitcoin.core.Wallet; +import com.google.bitcoin.core.*; import com.google.bitcoin.net.discovery.DnsDiscovery; import com.google.bitcoin.params.TestNet3Params; import com.google.bitcoin.store.SPVBlockStore; import com.google.bitcoin.wallet.DeterministicSeed; +import java.io.File; + /** * The following example shows you how to restore a HD wallet from a previously generated deterministic seed. * In this example we manually setup the blockchain, peer group, etc. You can also use the WalletAppKit which provides a restoreWalletFromSeed function to load a wallet from a deterministic seed. @@ -27,9 +23,9 @@ public class RestoreFromSeed { // Here we restore our wallet from a seed with no passphrase. Also have a look at the BackupToMnemonicSeed.java example that shows how to backup a wallet by creating a mnemonic sentence. String seedCode = "yard impulse luxury drive today throw farm pepper survey wreck glass federal"; String passphrase = ""; - Long creationtime = new Long(1409478661); + Long creationtime = 1409478661L; - DeterministicSeed seed = new DeterministicSeed(seedCode, passphrase, creationtime); + DeterministicSeed seed = new DeterministicSeed(seedCode, null, passphrase, creationtime); // The wallet class provides a easy fromSeed() function that loads a new wallet from a given seed. Wallet wallet = Wallet.fromSeed(params, seed); diff --git a/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java b/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java index 55e593ab..f0a082cc 100644 --- a/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java +++ b/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java @@ -884,7 +884,7 @@ public class WalletTool { // Parse as mnemonic code. final List split = ImmutableList.copyOf(Splitter.on(" ").omitEmptyStrings().split(seedStr)); String passphrase = ""; // TODO allow user to specify a passphrase - seed = new DeterministicSeed(split, passphrase, creationTimeSecs); + seed = new DeterministicSeed(split, null, passphrase, creationTimeSecs); try { seed.check(); } catch (MnemonicException.MnemonicLengthException e) { diff --git a/wallettemplate/src/main/java/wallettemplate/WalletSettingsController.java b/wallettemplate/src/main/java/wallettemplate/WalletSettingsController.java index c5cc47b5..49d5100a 100644 --- a/wallettemplate/src/main/java/wallettemplate/WalletSettingsController.java +++ b/wallettemplate/src/main/java/wallettemplate/WalletSettingsController.java @@ -153,7 +153,7 @@ public class WalletSettingsController { Main.instance.controller.restoreFromSeedAnimation(); long birthday = datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC); - DeterministicSeed seed = new DeterministicSeed(Splitter.on(' ').splitToList(wordsArea.getText()), "", birthday); + DeterministicSeed seed = new DeterministicSeed(Splitter.on(' ').splitToList(wordsArea.getText()), null, "", birthday); // Shut down bitcoinj and restart it with the new seed. Main.bitcoin.addListener(new Service.Listener() { @Override