@Test public void deterministicUpgradeEncrypted() throws Exception { group = new KeyChainGroup(params); final ECKey key = new ECKey(); group.importKeys(key); final KeyCrypterScrypt crypter = new KeyCrypterScrypt(); final KeyParameter aesKey = crypter.deriveKey("abc"); assertTrue(group.isDeterministicUpgradeRequired()); group.encrypt(crypter, aesKey); assertTrue(group.isDeterministicUpgradeRequired()); try { group.upgradeToDeterministic(0, null); fail(); } catch (DeterministicUpgradeRequiresPassword e) { // Expected. } group.upgradeToDeterministic(0, aesKey); assertFalse(group.isDeterministicUpgradeRequired()); final DeterministicSeed deterministicSeed = group.getActiveKeyChain().getSeed(); assertNotNull(deterministicSeed); assertTrue(deterministicSeed.isEncrypted()); byte[] entropy = checkNotNull(group.getActiveKeyChain().toDecrypted(aesKey).getSeed()).getEntropyBytes(); // Check we used the right key: oldest non rotating. byte[] truncatedBytes = Arrays.copyOfRange(key.getSecretBytes(), 0, 16); assertArrayEquals(entropy, truncatedBytes); }
@Test public void deterministicUpgradeUnencrypted() throws Exception { // Check that a group that contains only random keys has its HD chain created using the private // key bytes of // the oldest random key, so upgrading the same wallet twice gives the same outcome. group = new KeyChainGroup(params); group.setLookaheadSize(LOOKAHEAD_SIZE); // Don't want slow tests. ECKey key1 = new ECKey(); Utils.rollMockClock(86400); ECKey key2 = new ECKey(); group.importKeys(key2, key1); List<Protos.Key> protobufs = group.serializeToProtobuf(); group.upgradeToDeterministic(0, null); assertFalse(group.isDeterministicUpgradeRequired()); DeterministicKey dkey1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicSeed seed1 = group.getActiveKeyChain().getSeed(); assertNotNull(seed1); group = KeyChainGroup.fromProtobufUnencrypted(params, protobufs); group.upgradeToDeterministic(0, null); // Should give same result as last time. DeterministicKey dkey2 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicSeed seed2 = group.getActiveKeyChain().getSeed(); assertEquals(seed1, seed2); assertEquals(dkey1, dkey2); // Check we used the right (oldest) key despite backwards import order. byte[] truncatedBytes = Arrays.copyOfRange(key1.getSecretBytes(), 0, 16); assertArrayEquals(seed1.getEntropyBytes(), truncatedBytes); }
@Override public DeterministicKeyChain toDecrypted(KeyParameter aesKey) { checkState(getKeyCrypter() != null, "Key chain not encrypted"); checkState(seed != null, "Can't decrypt a watching chain"); checkState(seed.isEncrypted()); String passphrase = DEFAULT_PASSPHRASE_FOR_MNEMONIC; // FIXME allow non-empty passphrase DeterministicSeed decSeed = seed.decrypt(getKeyCrypter(), passphrase, aesKey); DeterministicKeyChain chain = new DeterministicKeyChain(decSeed); // Now double check that the keys match to catch the case where the key is wrong but padding // didn't catch it. if (!chain.getWatchingKey().getPubKeyPoint().equals(getWatchingKey().getPubKeyPoint())) throw new KeyCrypterException("Provided AES key is wrong"); chain.lookaheadSize = lookaheadSize; // Now copy the (pubkey only) leaf keys across to avoid rederiving them. The private key bytes // are missing // 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. checkState(key.isEncrypted()); DeterministicKey parent = chain.hierarchy.get(checkNotNull(key.getParent()).getPath(), false, false); // Clone the key to the new decrypted hierarchy. key = new DeterministicKey(key.getPubOnly(), parent); chain.hierarchy.putKey(key); chain.basicKeyChain.importKey(key); } chain.issuedExternalKeys = issuedExternalKeys; chain.issuedInternalKeys = issuedInternalKeys; return chain; }
DeterministicKeyChain(DeterministicSeed seed, @Nullable KeyCrypter crypter) { this.seed = seed; basicKeyChain = new BasicKeyChain(crypter); if (!seed.isEncrypted()) { rootKey = HDKeyDerivation.createMasterPrivateKey(checkNotNull(seed.getSeedBytes())); rootKey.setCreationTimeSeconds(seed.getCreationTimeSeconds()); initializeHierarchyUnencrypted(rootKey); } // Else... // We can't initialize ourselves with just an encrypted seed, so we expected deserialization // code to do the // rest of the setup (loading the root key). }
@Test public void deterministicUpgradeRotating() throws Exception { group = new KeyChainGroup(params); group.setLookaheadSize(LOOKAHEAD_SIZE); // Don't want slow tests. long now = Utils.currentTimeSeconds(); ECKey key1 = new ECKey(); Utils.rollMockClock(86400); ECKey key2 = new ECKey(); Utils.rollMockClock(86400); ECKey key3 = new ECKey(); group.importKeys(key2, key1, key3); group.upgradeToDeterministic(now + 10, null); DeterministicSeed seed = group.getActiveKeyChain().getSeed(); assertNotNull(seed); // Check we used the right key: oldest non rotating. byte[] truncatedBytes = Arrays.copyOfRange(key2.getSecretBytes(), 0, 16); assertArrayEquals(seed.getEntropyBytes(), truncatedBytes); }
@Override public DeterministicKeyChain toEncrypted(CharSequence password) { checkNotNull(password); checkArgument(password.length() > 0); checkState(seed != null, "Attempt to encrypt a watching chain."); checkState(!seed.isEncrypted()); KeyCrypter scrypt = new KeyCrypterScrypt(); KeyParameter derivedKey = scrypt.deriveKey(password); return toEncrypted(scrypt, derivedKey); }
/** Returns a list of words that represent the seed or null if this chain is a watching chain. */ @Nullable public List<String> getMnemonicCode() { if (seed == null) return null; lock.lock(); try { return seed.getMnemonicCode(); } finally { lock.unlock(); } }
@Override public long getEarliestKeyCreationTime() { return seed != null ? seed.getCreationTimeSeconds() : creationTimeSeconds; }