/** * Return the fingerprint of this key's parent as an int value, or zero if this key is the root * node of the key hierarchy. Raise an exception if the arguments are inconsistent. This method * exists to avoid code repetition in the constructors. */ private int ascertainParentFingerprint(DeterministicKey parentKey, int parentFingerprint) throws IllegalArgumentException { if (parentFingerprint != 0) { if (parent != null) checkArgument( parent.getFingerprint() == parentFingerprint, "parent fingerprint mismatch", Integer.toHexString(parent.getFingerprint()), Integer.toHexString(parentFingerprint)); return parentFingerprint; } else return 0; }
/** * 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); } }
@Override public DeterministicKey decrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypterException { checkNotNull(keyCrypter); // Check that the keyCrypter matches the one used to encrypt the keys, if set. if (this.keyCrypter != null && !this.keyCrypter.equals(keyCrypter)) throw new KeyCrypterException( "The keyCrypter being used to decrypt the key is different to the one that was used to encrypt it"); BigInteger privKey = findOrDeriveEncryptedPrivateKey(keyCrypter, aesKey); DeterministicKey key = new DeterministicKey(childNumberPath, chainCode, privKey, parent); if (!Arrays.equals(key.getPubKey(), getPubKey())) throw new KeyCrypterException("Provided AES key is wrong"); if (parent == null) key.setCreationTimeSeconds(getCreationTimeSeconds()); return key; }
public DeterministicKey encrypt( KeyCrypter keyCrypter, KeyParameter aesKey, @Nullable DeterministicKey newParent) throws KeyCrypterException { // Same as the parent code, except we construct a DeterministicKey instead of an ECKey. checkNotNull(keyCrypter); if (newParent != null) checkArgument(newParent.isEncrypted()); final byte[] privKeyBytes = getPrivKeyBytes(); checkState(privKeyBytes != null, "Private key is not available"); EncryptedData encryptedPrivateKey = keyCrypter.encrypt(privKeyBytes, aesKey); DeterministicKey key = new DeterministicKey( childNumberPath, chainCode, keyCrypter, pub, encryptedPrivateKey, newParent); if (newParent == null) key.setCreationTimeSeconds(getCreationTimeSeconds()); return key; }
/** * Returns this keys {@link org.bitcoinj.crypto.KeyCrypter} <b>or</b> the keycrypter of its parent * key. */ @Override @Nullable public KeyCrypter getKeyCrypter() { if (keyCrypter != null) return keyCrypter; else if (parent != null) return parent.getKeyCrypter(); else return null; }
/** Constructs a key from its components. This is not normally something you should use. */ public DeterministicKey( ImmutableList<ChildNumber> childNumberPath, byte[] chainCode, BigInteger priv, @Nullable DeterministicKey parent) { super(priv, compressPoint(ECKey.publicPointFromPrivate(priv))); checkArgument(chainCode.length == 32); this.parent = parent; this.childNumberPath = checkNotNull(childNumberPath); this.chainCode = Arrays.copyOf(chainCode, chainCode.length); this.depth = this.childNumberPath.size(); this.parentFingerprint = (parent != null) ? parent.getFingerprint() : 0; }
private BigInteger derivePrivateKeyDownwards( DeterministicKey cursor, byte[] parentalPrivateKeyBytes) { DeterministicKey downCursor = new DeterministicKey( cursor.childNumberPath, cursor.chainCode, cursor.pub, new BigInteger(1, parentalPrivateKeyBytes), cursor.parent); // Now we have to rederive the keys along the path back to ourselves. That path can be found by // just truncating // our path with the length of the parents path. ImmutableList<ChildNumber> path = childNumberPath.subList(cursor.getPath().size(), childNumberPath.size()); for (ChildNumber num : path) { downCursor = HDKeyDerivation.deriveChildKey(downCursor, num); } // downCursor is now the same key as us, but with private key bytes. checkState(downCursor.pub.equals(pub)); return checkNotNull(downCursor.priv); }
/** * The creation time of a deterministic key is equal to that of its parent, unless this key is the * root of a tree in which case the time is stored alongside the key as per normal, see {@link * org.bitcoinj.core.ECKey#getCreationTimeSeconds()}. */ @Override public long getCreationTimeSeconds() { if (parent != null) return parent.getCreationTimeSeconds(); else return super.getCreationTimeSeconds(); }
/** * A deterministic key is considered to be encrypted if it has access to encrypted private key * bytes, OR if its parent does. The reason is because the parent would be encrypted under the * same key and this key knows how to rederive its own private key bytes from the parent, if * needed. */ @Override public boolean isEncrypted() { return priv == null && (super.isEncrypted() || (parent != null && parent.isEncrypted())); }
/** * A deterministic key is considered to be 'public key only' if it hasn't got a private key part * and it cannot be rederived. If the hierarchy is encrypted this returns true. */ @Override public boolean isPubKeyOnly() { return super.isPubKeyOnly() && (parent == null || parent.isPubKeyOnly()); }
/** * Returns the same key with the parent pointer removed (it still knows its own path and the * parent fingerprint). * * <p>If this key doesn't have private key bytes stored/cached itself, but could rederive them * from the parent, then the new key returned by this method won't be able to do that. Thus, using * dropPrivateBytes().dropParent() on a regular DeterministicKey will yield a new DeterministicKey * that cannot sign or do other things involving the private key at all. */ public DeterministicKey dropParent() { DeterministicKey key = new DeterministicKey(getPath(), getChainCode(), pub, priv, null); key.parentFingerprint = parentFingerprint; return key; }