diff --git a/core/src/main/java/com/google/bitcoin/core/Wallet.java b/core/src/main/java/com/google/bitcoin/core/Wallet.java index f1a18d420..d76245459 100644 --- a/core/src/main/java/com/google/bitcoin/core/Wallet.java +++ b/core/src/main/java/com/google/bitcoin/core/Wallet.java @@ -208,6 +208,10 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha this(params, new KeyChainGroup()); } + public static Wallet fromSeed(NetworkParameters params, DeterministicSeed seed) { + return new Wallet(params, new KeyChainGroup(seed)); + } + // TODO: When this class moves to the Wallet package, along with the protobuf serializer, then hide this. /** For internal use only. */ public Wallet(NetworkParameters params, KeyChainGroup keyChainGroup) { 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 8e2585d5c..179a68e13 100644 --- a/core/src/main/java/com/google/bitcoin/crypto/MnemonicCode.java +++ b/core/src/main/java/com/google/bitcoin/crypto/MnemonicCode.java @@ -42,6 +42,9 @@ public class MnemonicCode { public static String BIP39_ENGLISH_SHA256 = "ad90bf3beb7b0eb7e5acd74727dc0da96e0a280a258354e7293fb7e211ac03db"; + /** UNIX time for when the BIP39 standard was finalised. This can be used as a default seed birthday. */ + public static long BIP39_STANDARDISATION_TIME_SECS = 1381276800; + private static final int PBKDF2_ROUNDS = 2048; public MnemonicCode() throws IOException { 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 525556f96..b5c0018f8 100644 --- a/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java +++ b/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java @@ -19,6 +19,8 @@ package com.google.bitcoin.tools; import com.google.bitcoin.core.*; import com.google.bitcoin.crypto.KeyCrypterException; +import com.google.bitcoin.crypto.MnemonicCode; +import com.google.bitcoin.crypto.MnemonicException; import com.google.bitcoin.net.discovery.DnsDiscovery; import com.google.bitcoin.params.MainNetParams; import com.google.bitcoin.params.RegTestParams; @@ -30,7 +32,9 @@ import com.google.bitcoin.store.*; import com.google.bitcoin.uri.BitcoinURI; import com.google.bitcoin.uri.BitcoinURIParseException; import com.google.bitcoin.utils.BriefLogFormatter; +import com.google.bitcoin.wallet.DeterministicSeed; import com.google.common.base.Charsets; +import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.io.Resources; import com.google.common.util.concurrent.ListenableFuture; @@ -67,12 +71,13 @@ import java.util.logging.LogManager; public class WalletTool { private static final Logger log = LoggerFactory.getLogger(WalletTool.class); + private static OptionSet options; private static OptionSpec dateFlag; private static OptionSpec unixtimeFlag; + private static OptionSpec seedFlag; private static NetworkParameters params; private static File walletFile; - private static OptionSet options; private static BlockStore store; private static AbstractBlockChain chain; private static PeerGroup peers; @@ -176,23 +181,13 @@ public class WalletTool { parser.accepts("help"); parser.accepts("force"); parser.accepts("debuglog"); - OptionSpec walletFileName = parser.accepts("wallet") - .withRequiredArg() - .defaultsTo("wallet"); - OptionSpec netFlag = parser.accepts("net") - .withOptionalArg() - .ofType(NetworkEnum.class) - .defaultsTo(NetworkEnum.PROD); - dateFlag = parser.accepts("date") - .withRequiredArg() - .ofType(Date.class) + OptionSpec walletFileName = parser.accepts("wallet").withRequiredArg().defaultsTo("wallet"); + seedFlag = parser.accepts("seed").withRequiredArg(); + OptionSpec netFlag = parser.accepts("net").withOptionalArg().ofType(NetworkEnum.class).defaultsTo(NetworkEnum.PROD); + dateFlag = parser.accepts("date").withRequiredArg().ofType(Date.class) .withValuesConvertedBy(DateConverter.datePattern("yyyy/MM/dd")); - OptionSpec waitForFlag = parser.accepts("waitfor") - .withRequiredArg() - .ofType(WaitForEnum.class); - OptionSpec modeFlag = parser.accepts("mode") - .withRequiredArg() - .ofType(ValidationMode.class) + OptionSpec waitForFlag = parser.accepts("waitfor").withRequiredArg().ofType(WaitForEnum.class); + OptionSpec modeFlag = parser.accepts("mode").withRequiredArg().ofType(ValidationMode.class) .defaultsTo(ValidationMode.SPV); OptionSpec chainFlag = parser.accepts("chain").withRequiredArg(); // For addkey/delkey. @@ -798,7 +793,40 @@ public class WalletTool { System.err.println("Wallet creation requested but " + walletFile + " already exists, use --force"); return; } - wallet = new Wallet(params); + if (options.has(seedFlag)) { + long creationTimeSecs = MnemonicCode.BIP39_STANDARDISATION_TIME_SECS; + if (options.has(dateFlag)) + creationTimeSecs = options.valueOf(dateFlag).getTime() / 1000; + String seedStr = options.valueOf(seedFlag); + DeterministicSeed seed; + if (seedStr.contains(" ")) { + // Parse as mnemonic code. + final List split = ImmutableList.copyOf(Splitter.on(" ").omitEmptyStrings().split(seedStr)); + try { + seed = new DeterministicSeed(split, creationTimeSecs); + } catch (MnemonicException.MnemonicLengthException e) { + System.err.println("The seed did not have 12 words in, perhaps you need quotes around it?"); + return; + } catch (MnemonicException.MnemonicWordException e) { + System.err.println("The seed contained an unrecognised word: " + e.badWord); + return; + } catch (MnemonicException.MnemonicChecksumException e) { + System.err.println("The seed did not pass checksumming, perhaps one of the words is wrong?"); + return; + } + } else { + // Parse as hex or base58 + byte[] bits = Utils.parseAsHexOrBase58(seedStr); + if (bits.length != 16) { + System.err.println("The given hex/base58 string is not 16 bytes"); + return; + } + seed = new DeterministicSeed(bits, creationTimeSecs); + } + wallet = Wallet.fromSeed(params, seed); + } else { + wallet = new Wallet(params); + } if (password != null) wallet.encrypt(password); wallet.saveToFile(walletFile);