/** * @throws HDDerivationException if private derivation is attempted for a public-only parent key, * or if the resulting derived key is invalid (eg. private key == 0). */ public static DeterministicKey deriveChildKey(DeterministicKey parent, ChildNumber childNumber) throws HDDerivationException { RawKeyBytes rawKey = deriveChildKeyBytes(parent, childNumber); return new DeterministicKey( HDUtils.append(parent.getChildNumberPath(), childNumber), rawKey.chainCode, parent.hasPrivate() ? null : HDUtils.getCurve().decodePoint(rawKey.keyBytes), parent.hasPrivate() ? HDUtils.toBigInteger(rawKey.keyBytes) : null, parent); }
public static DeterministicKey createMasterPubKeyFromBytes(byte[] pubKeyBytes, byte[] chainCode) { return new DeterministicKey( ImmutableList.<ChildNumber>of(), chainCode, HDUtils.getCurve().decodePoint(pubKeyBytes), null, null); }
/** @throws HDDerivationException if privKeyBytes is invalid (0 or >= n). */ public static DeterministicKey createMasterPrivKeyFromBytes(byte[] privKeyBytes, byte[] chainCode) throws HDDerivationException { BigInteger privateKeyFieldElt = HDUtils.toBigInteger(privKeyBytes); assertNonZero(privateKeyFieldElt, "Generated master key is invalid."); assertLessThanN(privateKeyFieldElt, "Generated master key is invalid."); return new DeterministicKey( ImmutableList.<ChildNumber>of(), chainCode, null, privateKeyFieldElt, null); }
/** * @throws HDDerivationException if private derivation is attempted for a public-only parent key, * or if the resulting derived key is invalid (eg. private key == 0). */ public static DeterministicKey deriveChildKey(DeterministicKey parent, ChildNumber childNumber) throws HDDerivationException { if (parent.isPubKeyOnly()) { RawKeyBytes rawKey = deriveChildKeyBytesFromPublic(parent, childNumber); return new DeterministicKey( HDUtils.append(parent.getPath(), childNumber), rawKey.chainCode, ECKey.CURVE.getCurve().decodePoint(rawKey.keyBytes), // c'tor will compress null, parent); } else { RawKeyBytes rawKey = deriveChildKeyBytesFromPrivate(parent, childNumber); return new DeterministicKey( HDUtils.append(parent.getPath(), childNumber), rawKey.chainCode, new BigInteger(1, rawKey.keyBytes), parent); } }
/** * Deserialize an HD Key. * * @param parent The parent node in the given key's deterministic hierarchy. */ public static DeterministicKey deserialize( NetworkParameters params, byte[] serializedKey, @Nullable DeterministicKey parent) { ByteBuffer buffer = ByteBuffer.wrap(serializedKey); int header = buffer.getInt(); if (header != params.getBip32HeaderPriv() && header != params.getBip32HeaderPub()) throw new IllegalArgumentException( "Unknown header bytes: " + toBase58(serializedKey).substring(0, 4)); boolean pub = header == params.getBip32HeaderPub(); int depth = buffer.get() & 0xFF; // convert signed byte to positive int since depth cannot be negative final int parentFingerprint = buffer.getInt(); final int i = buffer.getInt(); final ChildNumber childNumber = new ChildNumber(i); ImmutableList<ChildNumber> path; if (parent != null) { if (parentFingerprint == 0) throw new IllegalArgumentException("Parent was provided but this key doesn't have one"); if (parent.getFingerprint() != parentFingerprint) throw new IllegalArgumentException("Parent fingerprints don't match"); path = HDUtils.append(parent.getPath(), childNumber); if (path.size() != depth) throw new IllegalArgumentException("Depth does not match"); } else { if (depth >= 1) // We have been given a key that is not a root key, yet we lack the object representing the // parent. // This can happen when deserializing an account key for a watching wallet. In this case, // we assume that // the client wants to conceal the key's position in the hierarchy. The path is truncated // at the // parent's node. path = ImmutableList.of(childNumber); else path = ImmutableList.of(); } byte[] chainCode = new byte[32]; buffer.get(chainCode); byte[] data = new byte[33]; buffer.get(data); checkArgument(!buffer.hasRemaining(), "Found unexpected data in key"); if (pub) { return new DeterministicKey( path, chainCode, new LazyECPoint(ECKey.CURVE.getCurve(), data), parent, depth, parentFingerprint); } else { return new DeterministicKey( path, chainCode, new BigInteger(1, data), parent, depth, parentFingerprint); } }
/** * Generates a new deterministic key from the given seed, which can be any arbitrary byte array. * However resist the temptation to use a string as the seed - any key derived from a password is * likely to be weak and easily broken by attackers (this is not theoretical, people have had * money stolen that way). This method checks that the given seed is at least 64 bits long. * * @throws HDDerivationException if generated master key is invalid (private key 0 or >= n). * @throws IllegalArgumentException if the seed is less than 8 bytes and could be brute forced. */ public static DeterministicKey createMasterPrivateKey(byte[] seed) throws HDDerivationException { checkArgument(seed.length > 8, "Seed is too short and could be brute forced"); // Calculate I = HMAC-SHA512(key="Bitcoin seed", msg=S) byte[] i = HDUtils.hmacSha512(MASTER_HMAC_SHA512, seed); // Split I into two 32-byte sequences, Il and Ir. // Use Il as master secret key, and Ir as master chain code. checkState(i.length == 64, i.length); byte[] il = Arrays.copyOfRange(i, 0, 32); byte[] ir = Arrays.copyOfRange(i, 32, 64); Arrays.fill(i, (byte) 0); DeterministicKey masterPrivKey = createMasterPrivKeyFromBytes(il, ir); Arrays.fill(il, (byte) 0); Arrays.fill(ir, (byte) 0); return masterPrivKey; }
private static RawKeyBytes deriveChildKeyBytesFromPublic( DeterministicKey parent, ChildNumber childNumber) throws HDDerivationException { checkArgument(!childNumber.isHardened(), "Can't use private derivation with public keys only."); byte[] parentPublicKey = ECKey.compressPoint(parent.getPubKeyPoint()).getEncoded(); assert parentPublicKey.length == 33 : parentPublicKey.length; ByteBuffer data = ByteBuffer.allocate(37); data.put(parentPublicKey); data.putInt(childNumber.i()); byte[] i = HDUtils.hmacSha512(parent.getChainCode(), data.array()); assert i.length == 64 : i.length; byte[] il = Arrays.copyOfRange(i, 0, 32); byte[] chainCode = Arrays.copyOfRange(i, 32, 64); BigInteger ilInt = new BigInteger(1, il); assertLessThanN(ilInt, "Illegal derived key: I_L >= n"); ECPoint Ki = ECKey.CURVE.getG().multiply(ilInt).add(parent.getPubKeyPoint()); assertNonInfinity(Ki, "Illegal derived key: derived public key equals infinity."); return new RawKeyBytes(Ki.getEncoded(true), chainCode); }
private static RawKeyBytes deriveChildKeyBytesFromPrivate( DeterministicKey parent, ChildNumber childNumber) throws HDDerivationException { checkArgument(parent.hasPrivKey(), "Parent key must have private key bytes for this method."); byte[] parentPublicKey = ECKey.compressPoint(parent.getPubKeyPoint()).getEncoded(); assert parentPublicKey.length == 33 : parentPublicKey.length; ByteBuffer data = ByteBuffer.allocate(37); if (childNumber.isHardened()) { data.put(parent.getPrivKeyBytes33()); } else { data.put(parentPublicKey); } data.putInt(childNumber.i()); byte[] i = HDUtils.hmacSha512(parent.getChainCode(), data.array()); assert i.length == 64 : i.length; byte[] il = Arrays.copyOfRange(i, 0, 32); byte[] chainCode = Arrays.copyOfRange(i, 32, 64); BigInteger ilInt = new BigInteger(1, il); assertLessThanN(ilInt, "Illegal derived key: I_L >= n"); final BigInteger priv = parent.getPrivKey(); BigInteger ki = priv.add(ilInt).mod(ECKey.CURVE.getN()); assertNonZero(ki, "Illegal derived key: derived private key equals 0."); return new RawKeyBytes(ki.toByteArray(), chainCode); }
private static RawKeyBytes deriveChildKeyBytes(DeterministicKey parent, ChildNumber childNumber) throws HDDerivationException { byte[] parentPublicKey = HDUtils.getBytes(parent.getPubPoint()); assert parentPublicKey.length == 33 : parentPublicKey.length; ByteBuffer data = ByteBuffer.allocate(37); if (childNumber.isPrivateDerivation()) { data.put(parent.getPrivKeyBytes33()); } else { data.put(parentPublicKey); } data.putInt(childNumber.getI()); byte[] i = HDUtils.hmacSha512(parent.getChainCode(), data.array()); assert i.length == 64 : i.length; byte[] il = Arrays.copyOfRange(i, 0, 32); byte[] chainCode = Arrays.copyOfRange(i, 32, 64); BigInteger ilInt = HDUtils.toBigInteger(il); assertLessThanN(ilInt, "Illegal derived key: I_L >= n"); byte[] keyBytes; final BigInteger privAsFieldElement = parent.getPrivAsFieldElement(); if (privAsFieldElement != null) { BigInteger ki = privAsFieldElement.add(ilInt).mod(HDUtils.getEcParams().getN()); assertNonZero(ki, "Illegal derived key: derived private key equals 0."); keyBytes = ki.toByteArray(); } else { checkArgument( !childNumber.isPrivateDerivation(), "Can't use private derivation with public keys only."); ECPoint Ki = HDUtils.getEcParams().getG().multiply(ilInt).add(parent.getPubPoint()); checkArgument( !Ki.equals(HDUtils.getCurve().getInfinity()), "Illegal derived key: derived public key equals infinity."); keyBytes = HDUtils.toCompressed(Ki.getEncoded()); } return new RawKeyBytes(keyBytes, chainCode); }
/** * Implementation of the (public derivation version) deterministic wallet child key generation * algorithm. */ public final class HDKeyDerivation { private HDKeyDerivation() {} private static final HMac MASTER_HMAC_SHA512 = HDUtils.createHmacSha512Digest("Bitcoin seed".getBytes()); /** * Generates a new deterministic key from the given seed, which can be any arbitrary byte array. * However resist the temptation to use a string as the seed - any key derived from a password is * likely to be weak and easily broken by attackers (this is not theoretical, people have had * money stolen that way). This method checks that the given seed is at least 64 bits long. * * @throws HDDerivationException if generated master key is invalid (private key 0 or >= n). * @throws IllegalArgumentException if the seed is less than 8 bytes and could be brute forced. */ public static DeterministicKey createMasterPrivateKey(byte[] seed) throws HDDerivationException { checkArgument(seed.length > 8, "Seed is too short and could be brute forced"); // Calculate I = HMAC-SHA512(key="Bitcoin seed", msg=S) byte[] i = HDUtils.hmacSha512(MASTER_HMAC_SHA512, seed); // Split I into two 32-byte sequences, Il and Ir. // Use Il as master secret key, and Ir as master chain code. checkState(i.length == 64, i.length); byte[] il = Arrays.copyOfRange(i, 0, 32); byte[] ir = Arrays.copyOfRange(i, 32, 64); Arrays.fill(i, (byte) 0); DeterministicKey masterPrivKey = createMasterPrivKeyFromBytes(il, ir); Arrays.fill(il, (byte) 0); Arrays.fill(ir, (byte) 0); return masterPrivKey; } /** @throws HDDerivationException if privKeyBytes is invalid (0 or >= n). */ public static DeterministicKey createMasterPrivKeyFromBytes(byte[] privKeyBytes, byte[] chainCode) throws HDDerivationException { BigInteger privateKeyFieldElt = HDUtils.toBigInteger(privKeyBytes); assertNonZero(privateKeyFieldElt, "Generated master key is invalid."); assertLessThanN(privateKeyFieldElt, "Generated master key is invalid."); return new DeterministicKey( ImmutableList.<ChildNumber>of(), chainCode, null, privateKeyFieldElt, null); } public static DeterministicKey createMasterPubKeyFromBytes(byte[] pubKeyBytes, byte[] chainCode) { return new DeterministicKey( ImmutableList.<ChildNumber>of(), chainCode, HDUtils.getCurve().decodePoint(pubKeyBytes), null, null); } /** * @param childNumber the "extended" child number, ie. with the 0x80000000 bit specifying * private/public derivation. */ public static DeterministicKey deriveChildKey(DeterministicKey parent, int childNumber) { return deriveChildKey(parent, new ChildNumber(childNumber)); } /** * @throws HDDerivationException if private derivation is attempted for a public-only parent key, * or if the resulting derived key is invalid (eg. private key == 0). */ public static DeterministicKey deriveChildKey(DeterministicKey parent, ChildNumber childNumber) throws HDDerivationException { RawKeyBytes rawKey = deriveChildKeyBytes(parent, childNumber); return new DeterministicKey( HDUtils.append(parent.getChildNumberPath(), childNumber), rawKey.chainCode, parent.hasPrivate() ? null : HDUtils.getCurve().decodePoint(rawKey.keyBytes), parent.hasPrivate() ? HDUtils.toBigInteger(rawKey.keyBytes) : null, parent); } private static RawKeyBytes deriveChildKeyBytes(DeterministicKey parent, ChildNumber childNumber) throws HDDerivationException { byte[] parentPublicKey = HDUtils.getBytes(parent.getPubPoint()); assert parentPublicKey.length == 33 : parentPublicKey.length; ByteBuffer data = ByteBuffer.allocate(37); if (childNumber.isPrivateDerivation()) { data.put(parent.getPrivKeyBytes33()); } else { data.put(parentPublicKey); } data.putInt(childNumber.getI()); byte[] i = HDUtils.hmacSha512(parent.getChainCode(), data.array()); assert i.length == 64 : i.length; byte[] il = Arrays.copyOfRange(i, 0, 32); byte[] chainCode = Arrays.copyOfRange(i, 32, 64); BigInteger ilInt = HDUtils.toBigInteger(il); assertLessThanN(ilInt, "Illegal derived key: I_L >= n"); byte[] keyBytes; final BigInteger privAsFieldElement = parent.getPrivAsFieldElement(); if (privAsFieldElement != null) { BigInteger ki = privAsFieldElement.add(ilInt).mod(HDUtils.getEcParams().getN()); assertNonZero(ki, "Illegal derived key: derived private key equals 0."); keyBytes = ki.toByteArray(); } else { checkArgument( !childNumber.isPrivateDerivation(), "Can't use private derivation with public keys only."); ECPoint Ki = HDUtils.getEcParams().getG().multiply(ilInt).add(parent.getPubPoint()); checkArgument( !Ki.equals(HDUtils.getCurve().getInfinity()), "Illegal derived key: derived public key equals infinity."); keyBytes = HDUtils.toCompressed(Ki.getEncoded()); } return new RawKeyBytes(keyBytes, chainCode); } private static void assertNonZero(BigInteger integer, String errorMessage) { checkArgument(!integer.equals(BigInteger.ZERO), errorMessage); } private static void assertLessThanN(BigInteger integer, String errorMessage) { checkArgument(integer.compareTo(HDUtils.getEcParams().getN()) < 0, errorMessage); } private static class RawKeyBytes { private final byte[] keyBytes, chainCode; private RawKeyBytes(byte[] keyBytes, byte[] chainCode) { this.keyBytes = keyBytes; this.chainCode = chainCode; } } }
private static void assertLessThanN(BigInteger integer, String errorMessage) { checkArgument(integer.compareTo(HDUtils.getEcParams().getN()) < 0, errorMessage); }
/** * Returns the path of this key as a human readable string starting with M to indicate the master * key. */ public String getPathAsString() { return HDUtils.formatPath(getPath()); }
/** * Implementation of the <a * href="https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki">BIP 32</a> deterministic * wallet child key generation algorithm. */ public final class HDKeyDerivation { private HDKeyDerivation() {} /** * Child derivation may fail (although with extremely low probability); in such case it is * re-attempted. This is the maximum number of re-attempts (to avoid an infinite loop in case of * bugs etc.). */ public static final int MAX_CHILD_DERIVATION_ATTEMPTS = 100; private static final HMac MASTER_HMAC_SHA512 = HDUtils.createHmacSha512Digest("Bitcoin seed".getBytes()); /** * Generates a new deterministic key from the given seed, which can be any arbitrary byte array. * However resist the temptation to use a string as the seed - any key derived from a password is * likely to be weak and easily broken by attackers (this is not theoretical, people have had * money stolen that way). This method checks that the given seed is at least 64 bits long. * * @throws HDDerivationException if generated master key is invalid (private key 0 or >= n). * @throws IllegalArgumentException if the seed is less than 8 bytes and could be brute forced. */ public static DeterministicKey createMasterPrivateKey(byte[] seed) throws HDDerivationException { checkArgument(seed.length > 8, "Seed is too short and could be brute forced"); // Calculate I = HMAC-SHA512(key="Bitcoin seed", msg=S) byte[] i = HDUtils.hmacSha512(MASTER_HMAC_SHA512, seed); // Split I into two 32-byte sequences, Il and Ir. // Use Il as master secret key, and Ir as master chain code. checkState(i.length == 64, i.length); byte[] il = Arrays.copyOfRange(i, 0, 32); byte[] ir = Arrays.copyOfRange(i, 32, 64); Arrays.fill(i, (byte) 0); DeterministicKey masterPrivKey = createMasterPrivKeyFromBytes(il, ir); Arrays.fill(il, (byte) 0); Arrays.fill(ir, (byte) 0); // Child deterministic keys will chain up to their parents to find the keys. masterPrivKey.setCreationTimeSeconds(Utils.currentTimeSeconds()); return masterPrivKey; } /** @throws HDDerivationException if privKeyBytes is invalid (0 or >= n). */ public static DeterministicKey createMasterPrivKeyFromBytes(byte[] privKeyBytes, byte[] chainCode) throws HDDerivationException { BigInteger priv = new BigInteger(1, privKeyBytes); assertNonZero(priv, "Generated master key is invalid."); assertLessThanN(priv, "Generated master key is invalid."); return new DeterministicKey(ImmutableList.<ChildNumber>of(), chainCode, priv, null); } public static DeterministicKey createMasterPubKeyFromBytes(byte[] pubKeyBytes, byte[] chainCode) { return new DeterministicKey( ImmutableList.<ChildNumber>of(), chainCode, ECKey.CURVE.getCurve().decodePoint(pubKeyBytes), null, null); } /** * Derives a key given the "extended" child number, ie. with the 0x80000000 bit specifying whether * to use hardened derivation or not. */ public static DeterministicKey deriveChildKey(DeterministicKey parent, int childNumber) { return deriveChildKey(parent, new ChildNumber(childNumber)); } /** * Derives a key of the "extended" child number, ie. with the 0x80000000 bit specifying whether to * use hardened derivation or not. If derivation fails, tries a next child. */ public static DeterministicKey deriveThisOrNextChildKey( DeterministicKey parent, int childNumber) { int nAttempts = 0; ChildNumber child = new ChildNumber(childNumber); boolean isHardened = child.isHardened(); while (nAttempts < MAX_CHILD_DERIVATION_ATTEMPTS) { try { child = new ChildNumber(child.num() + nAttempts, isHardened); return deriveChildKey(parent, child); } catch (HDDerivationException ignore) { } nAttempts++; } throw new HDDerivationException( "Maximum number of child derivation attempts reached, this is probably an indication of a bug."); } /** * @throws HDDerivationException if private derivation is attempted for a public-only parent key, * or if the resulting derived key is invalid (eg. private key == 0). */ public static DeterministicKey deriveChildKey(DeterministicKey parent, ChildNumber childNumber) throws HDDerivationException { if (parent.isPubKeyOnly()) { RawKeyBytes rawKey = deriveChildKeyBytesFromPublic(parent, childNumber); return new DeterministicKey( HDUtils.append(parent.getPath(), childNumber), rawKey.chainCode, ECKey.CURVE.getCurve().decodePoint(rawKey.keyBytes), // c'tor will compress null, parent); } else { RawKeyBytes rawKey = deriveChildKeyBytesFromPrivate(parent, childNumber); return new DeterministicKey( HDUtils.append(parent.getPath(), childNumber), rawKey.chainCode, new BigInteger(1, rawKey.keyBytes), parent); } } private static RawKeyBytes deriveChildKeyBytesFromPrivate( DeterministicKey parent, ChildNumber childNumber) throws HDDerivationException { checkArgument(parent.hasPrivKey(), "Parent key must have private key bytes for this method."); byte[] parentPublicKey = ECKey.compressPoint(parent.getPubKeyPoint()).getEncoded(); assert parentPublicKey.length == 33 : parentPublicKey.length; ByteBuffer data = ByteBuffer.allocate(37); if (childNumber.isHardened()) { data.put(parent.getPrivKeyBytes33()); } else { data.put(parentPublicKey); } data.putInt(childNumber.i()); byte[] i = HDUtils.hmacSha512(parent.getChainCode(), data.array()); assert i.length == 64 : i.length; byte[] il = Arrays.copyOfRange(i, 0, 32); byte[] chainCode = Arrays.copyOfRange(i, 32, 64); BigInteger ilInt = new BigInteger(1, il); assertLessThanN(ilInt, "Illegal derived key: I_L >= n"); final BigInteger priv = parent.getPrivKey(); BigInteger ki = priv.add(ilInt).mod(ECKey.CURVE.getN()); assertNonZero(ki, "Illegal derived key: derived private key equals 0."); return new RawKeyBytes(ki.toByteArray(), chainCode); } private static RawKeyBytes deriveChildKeyBytesFromPublic( DeterministicKey parent, ChildNumber childNumber) throws HDDerivationException { checkArgument(!childNumber.isHardened(), "Can't use private derivation with public keys only."); byte[] parentPublicKey = ECKey.compressPoint(parent.getPubKeyPoint()).getEncoded(); assert parentPublicKey.length == 33 : parentPublicKey.length; ByteBuffer data = ByteBuffer.allocate(37); data.put(parentPublicKey); data.putInt(childNumber.i()); byte[] i = HDUtils.hmacSha512(parent.getChainCode(), data.array()); assert i.length == 64 : i.length; byte[] il = Arrays.copyOfRange(i, 0, 32); byte[] chainCode = Arrays.copyOfRange(i, 32, 64); BigInteger ilInt = new BigInteger(1, il); assertLessThanN(ilInt, "Illegal derived key: I_L >= n"); ECPoint Ki = ECKey.CURVE.getG().multiply(ilInt).add(parent.getPubKeyPoint()); assertNonInfinity(Ki, "Illegal derived key: derived public key equals infinity."); return new RawKeyBytes(Ki.getEncoded(true), chainCode); } private static void assertNonZero(BigInteger integer, String errorMessage) { if (integer.equals(BigInteger.ZERO)) throw new HDDerivationException(errorMessage); } private static void assertNonInfinity(ECPoint point, String errorMessage) { if (point.equals(ECKey.CURVE.getCurve().getInfinity())) throw new HDDerivationException(errorMessage); } private static void assertLessThanN(BigInteger integer, String errorMessage) { if (integer.compareTo(ECKey.CURVE.getN()) > 0) throw new HDDerivationException(errorMessage); } private static class RawKeyBytes { private final byte[] keyBytes, chainCode; private RawKeyBytes(byte[] keyBytes, byte[] chainCode) { this.keyBytes = keyBytes; this.chainCode = chainCode; } } }
/** * Returns all the key chains found in the given list of keys. Typically there will only be one, * but in the case of key rotation it can happen that there are multiple chains found. */ public static List<DeterministicKeyChain> fromProtobuf( List<Protos.Key> keys, @Nullable KeyCrypter crypter) throws UnreadableWalletException { List<DeterministicKeyChain> chains = newLinkedList(); DeterministicSeed seed = null; DeterministicKeyChain chain = null; int lookaheadSize = -1; for (Protos.Key key : keys) { final Protos.Key.Type t = key.getType(); if (t == Protos.Key.Type.DETERMINISTIC_MNEMONIC) { if (chain != null) { checkState(lookaheadSize >= 0); chain.setLookaheadSize(lookaheadSize); chain.maybeLookAhead(); chains.add(chain); chain = null; } 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); } else if (key.hasEncryptedData()) { EncryptedData data = new EncryptedData( key.getEncryptedData().getInitialisationVector().toByteArray(), key.getEncryptedData().getEncryptedPrivateKey().toByteArray()); seed = new DeterministicSeed(data, timestamp); } else { throw new UnreadableWalletException("Malformed key proto: " + key.toString()); } if (log.isDebugEnabled()) log.debug("Deserializing: DETERMINISTIC_MNEMONIC: {}", seed); } else if (t == Protos.Key.Type.DETERMINISTIC_KEY) { if (!key.hasDeterministicKey()) throw new UnreadableWalletException( "Deterministic key missing extra data: " + key.toString()); byte[] chainCode = key.getDeterministicKey().getChainCode().toByteArray(); // Deserialize the path through the tree. LinkedList<ChildNumber> path = newLinkedList(); for (int i : key.getDeterministicKey().getPathList()) path.add(new ChildNumber(i)); // Deserialize the public key and path. ECPoint pubkey = ECKey.CURVE.getCurve().decodePoint(key.getPublicKey().toByteArray()); final ImmutableList<ChildNumber> immutablePath = ImmutableList.copyOf(path); // Possibly create the chain, if we didn't already do so yet. boolean isWatchingAccountKey = false; boolean isFollowingKey = false; // save previous chain if any if the key is marked as following. Current key and the next // ones are to be // placed in new following key chain if (key.getDeterministicKey().getIsFollowing()) { if (chain != null) { checkState(lookaheadSize >= 0); chain.setLookaheadSize(lookaheadSize); chain.maybeLookAhead(); chains.add(chain); chain = null; seed = null; } isFollowingKey = true; } if (chain == null) { if (seed == null) { DeterministicKey accountKey = new DeterministicKey(immutablePath, chainCode, pubkey, null, null); if (!accountKey.getPath().equals(ACCOUNT_ZERO_PATH)) throw new UnreadableWalletException( "Expecting account key but found key with path: " + HDUtils.formatPath(accountKey.getPath())); chain = new DeterministicKeyChain(accountKey, isFollowingKey); isWatchingAccountKey = true; } else { chain = new DeterministicKeyChain(seed, crypter); chain.lookaheadSize = LAZY_CALCULATE_LOOKAHEAD; // If the seed is encrypted, then the chain is incomplete at this point. However, we // will load // it up below as we parse in the keys. We just need to check at the end that we've // loaded // everything afterwards. } } // Find the parent key assuming this is not the root key, and not an account key for a // watching chain. DeterministicKey parent = null; if (!path.isEmpty() && !isWatchingAccountKey) { ChildNumber index = path.removeLast(); parent = chain.hierarchy.get(path, false, false); path.add(index); } DeterministicKey detkey; if (key.hasSecretBytes()) { // Not encrypted: private key is available. final BigInteger priv = new BigInteger(1, key.getSecretBytes().toByteArray()); detkey = new DeterministicKey(immutablePath, chainCode, pubkey, priv, parent); } else { if (key.hasEncryptedData()) { Protos.EncryptedData proto = key.getEncryptedData(); EncryptedData data = new EncryptedData( proto.getInitialisationVector().toByteArray(), proto.getEncryptedPrivateKey().toByteArray()); checkNotNull(crypter, "Encountered an encrypted key but no key crypter provided"); detkey = new DeterministicKey(immutablePath, chainCode, crypter, pubkey, data, parent); } else { // No secret key bytes and key is not encrypted: either a watching key or private key // bytes // will be rederived on the fly from the parent. detkey = new DeterministicKey(immutablePath, chainCode, pubkey, null, parent); } } if (key.hasCreationTimestamp()) detkey.setCreationTimeSeconds(key.getCreationTimestamp() / 1000); if (log.isDebugEnabled()) log.debug("Deserializing: DETERMINISTIC_KEY: {}", detkey); if (!isWatchingAccountKey) { // If the non-encrypted case, the non-leaf keys (account, internal, external) have already // been // rederived and inserted at this point and the two lines below are just a no-op. In the // encrypted // case though, we can't rederive and we must reinsert, potentially building the heirarchy // object // if need be. if (path.size() == 0) { // Master key. chain.rootKey = detkey; chain.hierarchy = new DeterministicHierarchy(detkey); } else if (path.size() == 2) { if (detkey.getChildNumber().num() == 0) { chain.externalKey = detkey; chain.issuedExternalKeys = key.getDeterministicKey().getIssuedSubkeys(); lookaheadSize = Math.max(lookaheadSize, key.getDeterministicKey().getLookaheadSize()); } else if (detkey.getChildNumber().num() == 1) { chain.internalKey = detkey; chain.issuedInternalKeys = key.getDeterministicKey().getIssuedSubkeys(); } } } chain.hierarchy.putKey(detkey); chain.basicKeyChain.importKey(detkey); } } if (chain != null) { checkState(lookaheadSize >= 0); chain.setLookaheadSize(lookaheadSize); chain.maybeLookAhead(); chains.add(chain); } return chains; }
/** Returns freshly derived key/s that have not been returned by this method before. */ @Override public List<DeterministicKey> getKeys(KeyPurpose purpose, int numberOfKeys) { checkArgument(numberOfKeys > 0); lock.lock(); try { DeterministicKey parentKey; int index; switch (purpose) { // Map both REFUND and RECEIVE_KEYS to the same branch for now. Refunds are a feature of // the BIP 70 // payment protocol. Later we may wish to map it to a different branch (in a new wallet // version?). // This would allow a watching wallet to only be able to see inbound payments, but not // change // (i.e. spends) or refunds. Might be useful for auditing ... case RECEIVE_FUNDS: case REFUND: issuedExternalKeys += numberOfKeys; index = issuedExternalKeys; parentKey = externalKey; break; case AUTHENTICATION: case CHANGE: issuedInternalKeys += numberOfKeys; index = issuedInternalKeys; parentKey = internalKey; break; default: throw new UnsupportedOperationException(); } // Optimization: potentially do a very quick key generation for just the number of keys we // need if we // didn't already create them, ignoring the configured lookahead size. This ensures we'll be // able to // retrieve the keys in the following loop, but if we're totally fresh and didn't get a chance // to // calculate the lookahead keys yet, this will not block waiting to calculate 100+ EC point // multiplies. // On slow/crappy Android phones looking ahead 100 keys can take ~5 seconds but the OS will // kill us // if we block for just one second on the UI thread. Because UI threads may need an address in // order // to render the screen, we need getKeys to be fast even if the wallet is totally brand new // and lookahead // didn't happen yet. // // It's safe to do this because when a network thread tries to calculate a Bloom filter, we'll // go ahead // and calculate the full lookahead zone there, so network requests will always use the right // amount. List<DeterministicKey> lookahead = maybeLookAhead(parentKey, index, 0, 0); basicKeyChain.importKeys(lookahead); List<DeterministicKey> keys = new ArrayList<DeterministicKey>(numberOfKeys); for (int i = 0; i < numberOfKeys; i++) { ImmutableList<ChildNumber> path = HDUtils.append(parentKey.getPath(), new ChildNumber(index - numberOfKeys + i, false)); DeterministicKey k = hierarchy.get(path, false, false); // Just a last minute sanity check before we hand the key out to the app for usage. This // isn't inspired // by any real problem reports from bitcoinj users, but I've heard of cases via the // grapevine of // places that lost money due to bitflips causing addresses to not match keys. Of course in // an // environment with flaky RAM there's no real way to always win: bitflips could be // introduced at any // other layer. But as we're potentially retrieving from long term storage here, check // anyway. checkForBitFlip(k); keys.add(k); } return keys; } finally { lock.unlock(); } }