package com.greenaddress.greenapi;
import android.util.SparseArray;
import com.blockstream.libwally.Wally;
import com.google.common.collect.ImmutableList;
import com.greenaddress.greenbits.ui.BuildConfig;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
import org.bitcoinj.crypto.LazyECPoint;
import java.util.ArrayList;
import static com.blockstream.libwally.Wally.BIP32_FLAG_KEY_PUBLIC;
import static com.blockstream.libwally.Wally.BIP32_FLAG_SKIP_HASH;
import static com.blockstream.libwally.Wally.BIP32_VER_MAIN_PRIVATE;
import static com.blockstream.libwally.Wally.BIP32_VER_MAIN_PUBLIC;
import static com.blockstream.libwally.Wally.BIP32_VER_TEST_PRIVATE;
import static com.blockstream.libwally.Wally.BIP32_VER_TEST_PUBLIC;
public class HDKey {
private final static int VER_PUBLIC = isMain() ? BIP32_VER_MAIN_PUBLIC : BIP32_VER_TEST_PUBLIC;
private final static int VER_PRIVATE = isMain() ? BIP32_VER_MAIN_PRIVATE : BIP32_VER_TEST_PRIVATE;
public static final int BRANCH_REGULAR = 1;
public static final int BRANCH_BLINDED = 5;
private static final SparseArray<DeterministicKey> mServerKeys = new SparseArray<>();
private static int[] mGaUserPath;
private static boolean isMain() {
return NetworkParameters.fromID(NetworkParameters.ID_MAINNET).equals(Network.NETWORK);
}
//
// Temporary methods for use while converting from DeterministicKey
public static DeterministicKey deriveChildKey(final DeterministicKey parent, final Integer childNum) {
return HDKeyDerivation.deriveChildKey(parent, new ChildNumber(childNum));
}
public static DeterministicKey createMasterKeyFromSeed(final byte[] seed) {
return HDKeyDerivation.createMasterPrivateKey(seed);
}
public static DeterministicKey createMasterKey(final byte[] chainCode, final byte[] publicKey) {
final ECKey pub = ECKey.fromPublicOnly(publicKey);
return new DeterministicKey(new ImmutableList.Builder<ChildNumber>().build(),
chainCode, pub.getPubKeyPoint(), null, null);
}
public static DeterministicKey createMasterKey(final String chainCode, final String publicKey) {
return createMasterKey(h(chainCode), h(publicKey));
}
// Get the 2of3 backup key (plus parent)
// This is the users key to reedeem 2of3 funds in the event that GA becomes unavailable
public static DeterministicKey[] getRecoveryKeys(final byte[] chainCode, final byte[] publicKey, final Integer pointer) {
final DeterministicKey[] ret = new DeterministicKey[2];
ret[0] = deriveChildKey(createMasterKey(chainCode, publicKey), 1); // Parent
ret[1] = deriveChildKey(ret[0], pointer); // Child
return ret;
}
public static DeterministicKey[] getRecoveryKeys(final String chainCode, final String publicKey, final Integer pointer) {
return getRecoveryKeys(h(chainCode), h(publicKey), pointer);
}
// Get the key derived from the servers public key/chaincode plus the users path (plus parent).
// This is the key used on the servers side of 2of2/2of3 transactions.
public static DeterministicKey[] getGAPublicKeys(final int subAccount, final Integer pointer) {
final DeterministicKey[] ret = new DeterministicKey[2];
synchronized (mServerKeys) {
// Fetch the parent key. This is expensive so we cache it
if ((ret[0] = mServerKeys.get(subAccount)) == null)
mServerKeys.put(subAccount, ret[0] = getServerKeyImpl(subAccount));
}
// Compute the child key if we were asked for it
if (pointer != null)
ret[1] = deriveChildKey(ret[0], pointer); // Child
return ret;
}
public static void resetCache(final int[] gaUserPath) {
synchronized (mServerKeys) {
mServerKeys.clear();
mGaUserPath = gaUserPath == null ? null : gaUserPath.clone();
}
}
private static DeterministicKey getServerKeyImpl(final int subAccount) {
final boolean reconcile = BuildConfig.DEBUG;
DeterministicKey k = null;
if (reconcile) {
k = createMasterKey(Network.depositChainCode, Network.depositPubkey);
k = deriveChildKey(k, subAccount == 0 ? 1 : 3);
for (final int i : mGaUserPath)
k = deriveChildKey(k, i);
if (subAccount != 0)
k = deriveChildKey(k, subAccount);
}
final Object master = Wally.bip32_key_init(VER_PUBLIC, 0, 0,
h(Network.depositChainCode), h(Network.depositPubkey),
null, null, null);
final int[] path = new int[mGaUserPath.length + (subAccount == 0 ? 1 : 2)];
path[0] = subAccount == 0 ? 1 : 3;
System.arraycopy(mGaUserPath, 0, path, 1, mGaUserPath.length);
if (subAccount != 0)
path[mGaUserPath.length + 1] = subAccount;
final int flags = BIP32_FLAG_KEY_PUBLIC | BIP32_FLAG_SKIP_HASH;
final Object derived = Wally.bip32_key_from_parent_path(master, path, flags);
final DeterministicKey key;
final ArrayList<ChildNumber> childNumbers = new ArrayList<>(path.length);
for (final int i : path)
childNumbers.add(new ChildNumber(i));
key = new DeterministicKey(ImmutableList.<ChildNumber>builder().addAll(childNumbers).build(),
Wally.bip32_key_get_chain_code(derived),
new LazyECPoint(ECKey.CURVE.getCurve(),
Wally.bip32_key_get_pub_key(derived)),
/* parent */ null, childNumbers.size(), 0);
final boolean matched = !reconcile || k.equals(key);
Wally.bip32_key_free(master);
Wally.bip32_key_free(derived);
if (!matched)
throw new RuntimeException("Derivation mismatch");
return key;
}
// FIXME: Remove
private static byte[] h(final String hex) { return Wally.hex_to_bytes(hex); }
}