org.bitcoinj.crypto.DeterministicKey Maven / Gradle / Ivy
/*
* Copyright 2013 Matija Mazi.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoinj.crypto;
import org.bitcoinj.core.*;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.math.ec.ECPoint;
import javax.annotation.Nullable;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Comparator;
import static org.bitcoinj.core.Utils.HEX;
import static com.google.common.base.Preconditions.*;
/**
* A deterministic key is a node in a {@link DeterministicHierarchy}. As per
* the BIP 32 specification it is a pair
* (key, chaincode). If you know its path in the tree and its chain code you can derive more keys from this. To obtain
* one of these, you can call {@link HDKeyDerivation#createMasterPrivateKey(byte[])}.
*/
public class DeterministicKey extends ECKey {
/** Sorts deterministic keys in the order of their child number. That's usually the order used to derive them. */
public static final Comparator CHILDNUM_ORDER = new Comparator() {
@Override
public int compare(ECKey k1, ECKey k2) {
ChildNumber cn1 = ((DeterministicKey) k1).getChildNumber();
ChildNumber cn2 = ((DeterministicKey) k2).getChildNumber();
return cn1.compareTo(cn2);
}
};
private final DeterministicKey parent;
private final ImmutableList childNumberPath;
private final int depth;
private int parentFingerprint; // 0 if this key is root node of key hierarchy
/** 32 bytes */
private final byte[] chainCode;
/** Constructs a key from its components. This is not normally something you should use. */
public DeterministicKey(ImmutableList childNumberPath,
byte[] chainCode,
LazyECPoint publicAsPoint,
@Nullable BigInteger priv,
@Nullable DeterministicKey parent) {
super(priv, compressPoint(checkNotNull(publicAsPoint)));
checkArgument(chainCode.length == 32);
this.parent = parent;
this.childNumberPath = checkNotNull(childNumberPath);
this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
this.depth = parent == null ? 0 : parent.depth + 1;
this.parentFingerprint = (parent != null) ? parent.getFingerprint() : 0;
}
public DeterministicKey(ImmutableList childNumberPath,
byte[] chainCode,
ECPoint publicAsPoint,
@Nullable BigInteger priv,
@Nullable DeterministicKey parent) {
this(childNumberPath, chainCode, new LazyECPoint(publicAsPoint), priv, parent);
}
/** Constructs a key from its components. This is not normally something you should use. */
public DeterministicKey(ImmutableList 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 = parent == null ? 0 : parent.depth + 1;
this.parentFingerprint = (parent != null) ? parent.getFingerprint() : 0;
}
/** Constructs a key from its components. This is not normally something you should use. */
public DeterministicKey(ImmutableList childNumberPath,
byte[] chainCode,
KeyCrypter crypter,
LazyECPoint pub,
EncryptedData priv,
@Nullable DeterministicKey parent) {
this(childNumberPath, chainCode, pub, null, parent);
this.encryptedPrivateKey = checkNotNull(priv);
this.keyCrypter = checkNotNull(crypter);
}
/**
* 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;
}
/**
* Constructs a key from its components, including its public key data and possibly-redundant
* information about its parent key. Invoked when deserializing, but otherwise not something that
* you normally should use.
*/
public DeterministicKey(ImmutableList childNumberPath,
byte[] chainCode,
LazyECPoint publicAsPoint,
@Nullable DeterministicKey parent,
int depth,
int parentFingerprint) {
super(null, compressPoint(checkNotNull(publicAsPoint)));
checkArgument(chainCode.length == 32);
this.parent = parent;
this.childNumberPath = checkNotNull(childNumberPath);
this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
this.depth = depth;
this.parentFingerprint = ascertainParentFingerprint(parent, parentFingerprint);
}
/**
* Constructs a key from its components, including its private key data and possibly-redundant
* information about its parent key. Invoked when deserializing, but otherwise not something that
* you normally should use.
*/
public DeterministicKey(ImmutableList childNumberPath,
byte[] chainCode,
BigInteger priv,
@Nullable DeterministicKey parent,
int depth,
int parentFingerprint) {
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 = depth;
this.parentFingerprint = ascertainParentFingerprint(parent, parentFingerprint);
}
/** Clones the key */
public DeterministicKey(DeterministicKey keyToClone, DeterministicKey newParent) {
super(keyToClone.priv, keyToClone.pub.get());
this.parent = newParent;
this.childNumberPath = keyToClone.childNumberPath;
this.chainCode = keyToClone.chainCode;
this.encryptedPrivateKey = keyToClone.encryptedPrivateKey;
this.depth = this.childNumberPath.size();
this.parentFingerprint = this.parent.getFingerprint();
}
/**
* Returns the path through some {@link DeterministicHierarchy} which reaches this keys position in the tree.
* A path can be written as 1/2/1 which means the first child of the root, the second child of that node, then
* the first child of that node.
*/
public ImmutableList getPath() {
return childNumberPath;
}
/**
* 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());
}
/**
* Return this key's depth in the hierarchy, where the root node is at depth zero.
* This may be different than the number of segments in the path if this key was
* deserialized without access to its parent.
*/
public int getDepth() {
return depth;
}
/** Returns the last element of the path returned by {@link DeterministicKey#getPath()} */
public ChildNumber getChildNumber() {
return childNumberPath.size() == 0 ? ChildNumber.ZERO : childNumberPath.get(childNumberPath.size() - 1);
}
/**
* Returns the chain code associated with this key. See the specification to learn more about chain codes.
*/
public byte[] getChainCode() {
return chainCode;
}
/**
* Returns RIPE-MD160(SHA256(pub key bytes)).
*/
public byte[] getIdentifier() {
return Utils.sha256hash160(getPubKey());
}
/** Returns the first 32 bits of the result of {@link #getIdentifier()}. */
public int getFingerprint() {
// TODO: why is this different than armory's fingerprint? BIP 32: "The first 32 bits of the identifier are called the fingerprint."
return ByteBuffer.wrap(Arrays.copyOfRange(getIdentifier(), 0, 4)).getInt();
}
@Nullable
public DeterministicKey getParent() {
return parent;
}
/**
* Return the fingerprint of the key from which this key was derived, if this is a
* child key, or else an array of four zero-value bytes.
*/
public int getParentFingerprint() {
return parentFingerprint;
}
/**
* Returns private key bytes, padded with zeros to 33 bytes.
* @throws java.lang.IllegalStateException if the private key bytes are missing.
*/
public byte[] getPrivKeyBytes33() {
byte[] bytes33 = new byte[33];
byte[] priv = getPrivKeyBytes();
System.arraycopy(priv, 0, bytes33, 33 - priv.length, priv.length);
return bytes33;
}
/**
* Returns the same key with the private bytes removed. May return the same instance. The purpose of this is to save
* memory: the private key can always be very efficiently rederived from a parent that a private key, so storing
* all the private keys in RAM is a poor tradeoff especially on constrained devices. This means that the returned
* key may still be usable for signing and so on, so don't expect it to be a true pubkey-only object! If you want
* that then you should follow this call with a call to {@link #dropParent()}.
*/
public DeterministicKey dropPrivateBytes() {
if (isPubKeyOnly())
return this;
else
return new DeterministicKey(getPath(), getChainCode(), pub, null, parent);
}
/**
* Returns the same key with the parent pointer removed (it still knows its own path and the parent fingerprint).
*
* 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;
}
static byte[] addChecksum(byte[] input) {
int inputLength = input.length;
byte[] checksummed = new byte[inputLength + 4];
System.arraycopy(input, 0, checksummed, 0, inputLength);
byte[] checksum = Sha256Hash.hashTwice(input);
System.arraycopy(checksum, 0, checksummed, inputLength, 4);
return checksummed;
}
@Override
public DeterministicKey encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypterException {
throw new UnsupportedOperationException("Must supply a new parent for encryption");
}
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;
}
/**
* 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());
}
/** {@inheritDoc} */
@Override
public boolean hasPrivKey() {
return findParentWithPrivKey() != null;
}
@Nullable
@Override
public byte[] getSecretBytes() {
return priv != null ? getPrivKeyBytes() : null;
}
/**
* 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()));
}
/**
* Returns this keys {@link org.bitcoinj.crypto.KeyCrypter} or 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;
}
@Override
public ECDSASignature sign(Sha256Hash input, @Nullable KeyParameter aesKey) throws KeyCrypterException {
if (isEncrypted()) {
// If the key is encrypted, ECKey.sign will decrypt it first before rerunning sign. Decryption walks the
// key heirarchy to find the private key (see below), so, we can just run the inherited method.
return super.sign(input, aesKey);
} else {
// If it's not encrypted, derive the private via the parents.
final BigInteger privateKey = findOrDerivePrivateKey();
if (privateKey == null) {
// This key is a part of a public-key only heirarchy and cannot be used for signing
throw new MissingPrivateKeyException();
}
return super.doSign(input, privateKey);
}
}
@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;
}
@Override
public DeterministicKey decrypt(KeyParameter aesKey) throws KeyCrypterException {
return (DeterministicKey) super.decrypt(aesKey);
}
// For when a key is encrypted, either decrypt our encrypted private key bytes, or work up the tree asking parents
// to decrypt and re-derive.
private BigInteger findOrDeriveEncryptedPrivateKey(KeyCrypter keyCrypter, KeyParameter aesKey) {
if (encryptedPrivateKey != null)
return new BigInteger(1, keyCrypter.decrypt(encryptedPrivateKey, aesKey));
// Otherwise we don't have it, but maybe we can figure it out from our parents. Walk up the tree looking for
// the first key that has some encrypted private key data.
DeterministicKey cursor = parent;
while (cursor != null) {
if (cursor.encryptedPrivateKey != null) break;
cursor = cursor.parent;
}
if (cursor == null)
throw new KeyCrypterException("Neither this key nor its parents have an encrypted private key");
byte[] parentalPrivateKeyBytes = keyCrypter.decrypt(cursor.encryptedPrivateKey, aesKey);
return derivePrivateKeyDownwards(cursor, parentalPrivateKeyBytes);
}
private DeterministicKey findParentWithPrivKey() {
DeterministicKey cursor = this;
while (cursor != null) {
if (cursor.priv != null) break;
cursor = cursor.parent;
}
return cursor;
}
@Nullable
private BigInteger findOrDerivePrivateKey() {
DeterministicKey cursor = findParentWithPrivKey();
if (cursor == null)
return null;
return derivePrivateKeyDownwards(cursor, cursor.priv.toByteArray());
}
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 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.
// If it's not, it means we tried decrypting with an invalid password and earlier checks e.g. for padding didn't
// catch it.
if (!downCursor.pub.equals(pub))
throw new KeyCrypterException("Could not decrypt bytes");
return checkNotNull(downCursor.priv);
}
/**
* Derives a child at the given index using hardened derivation. Note: index
is
* not the "i" value. If you want the softened derivation, then use instead
* HDKeyDerivation.deriveChildKey(this, new ChildNumber(child, false))
.
*/
public DeterministicKey derive(int child) {
return HDKeyDerivation.deriveChildKey(this, new ChildNumber(child, true));
}
/**
* Returns the private key of this deterministic key. Even if this object isn't storing the private key,
* it can be re-derived by walking up to the parents if necessary and this is what will happen.
* @throws java.lang.IllegalStateException if the parents are encrypted or a watching chain.
*/
@Override
public BigInteger getPrivKey() {
final BigInteger key = findOrDerivePrivateKey();
checkState(key != null, "Private key bytes not available");
return key;
}
public byte[] serializePublic(NetworkParameters params) {
return serialize(params, true);
}
public byte[] serializePrivate(NetworkParameters params) {
return serialize(params, false);
}
private byte[] serialize(NetworkParameters params, boolean pub) {
ByteBuffer ser = ByteBuffer.allocate(78);
ser.putInt(pub ? params.getBip32HeaderPub() : params.getBip32HeaderPriv());
ser.put((byte) getDepth());
ser.putInt(getParentFingerprint());
ser.putInt(getChildNumber().i());
ser.put(getChainCode());
ser.put(pub ? getPubKey() : getPrivKeyBytes33());
checkState(ser.position() == 78);
return ser.array();
}
public String serializePubB58(NetworkParameters params) {
return toBase58(serialize(params, true));
}
public String serializePrivB58(NetworkParameters params) {
return toBase58(serialize(params, false));
}
static String toBase58(byte[] ser) {
return Base58.encode(addChecksum(ser));
}
/** Deserialize a base-58-encoded HD Key with no parent */
public static DeterministicKey deserializeB58(String base58, NetworkParameters params) {
return deserializeB58(null, base58, params);
}
/**
* Deserialize a base-58-encoded HD Key.
* @param parent The parent node in the given key's deterministic hierarchy.
* @throws IllegalArgumentException if the base58 encoded key could not be parsed.
*/
public static DeterministicKey deserializeB58(@Nullable DeterministicKey parent, String base58, NetworkParameters params) {
return deserialize(params, Base58.decodeChecked(base58), parent);
}
/**
* Deserialize an HD Key with no parent
*/
public static DeterministicKey deserialize(NetworkParameters params, byte[] serializedKey) {
return deserialize(params, serializedKey, null);
}
/**
* 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 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);
}
}
/**
* 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();
}
/**
* The creation time of a deterministic key is equal to that of its parent, unless this key is the root of a tree.
* Thus, setting the creation time on a leaf is forbidden.
*/
@Override
public void setCreationTimeSeconds(long newCreationTimeSeconds) {
if (parent != null)
throw new IllegalStateException("Creation time can only be set on root keys.");
else
super.setCreationTimeSeconds(newCreationTimeSeconds);
}
/**
* Verifies equality of all fields but NOT the parent pointer (thus the same key derived in two separate heirarchy
* objects will equal each other.
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DeterministicKey other = (DeterministicKey) o;
return super.equals(other)
&& Arrays.equals(this.chainCode, other.chainCode)
&& Objects.equal(this.childNumberPath, other.childNumberPath);
}
@Override
public int hashCode() {
return Objects.hashCode(super.hashCode(), Arrays.hashCode(chainCode), childNumberPath);
}
@Override
public String toString() {
final MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(this).omitNullValues();
helper.add("pub", Utils.HEX.encode(pub.getEncoded()));
helper.add("chainCode", HEX.encode(chainCode));
helper.add("path", getPathAsString());
if (creationTimeSeconds > 0)
helper.add("creationTimeSeconds", creationTimeSeconds);
helper.add("isEncrypted", isEncrypted());
helper.add("isPubKeyOnly", isPubKeyOnly());
return helper.toString();
}
@Override
public void formatKeyWithAddress(boolean includePrivateKeys, @Nullable KeyParameter aesKey, StringBuilder builder,
NetworkParameters params) {
final Address address = toAddress(params);
builder.append(" addr:").append(address);
builder.append(" hash160:").append(Utils.HEX.encode(getPubKeyHash()));
builder.append(" (").append(getPathAsString()).append(")\n");
if (includePrivateKeys) {
builder.append(" ").append(toStringWithPrivate(aesKey, params)).append("\n");
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy