All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bloxbean.cardano.client.crypto.bip32.HdKeyGenerator Maven / Gradle / Ivy

There is a newer version: 0.6.2
Show newest version
package com.bloxbean.cardano.client.crypto.bip32;

import com.bloxbean.cardano.client.crypto.bip32.key.HdPrivateKey;
import com.bloxbean.cardano.client.crypto.bip32.key.HdPublicKey;
import com.bloxbean.cardano.client.crypto.bip32.util.BytesUtil;
import com.bloxbean.cardano.client.crypto.bip32.util.Hmac;
import com.bloxbean.cardano.client.crypto.cip1852.DerivationPath;
import com.bloxbean.cardano.client.util.HexUtil;
import com.bloxbean.cardano.client.util.OSUtil;

import net.i2p.crypto.eddsa.math.GroupElement;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;

//This file is originally from https://github.com/semuxproject/semux-core
//Updated according to Cardano's requirement
public class HdKeyGenerator {

    private static final Logger logger = LoggerFactory.getLogger(HdKeyGenerator.class);

    private static final EdDSAParameterSpec ED25519SPEC = EdDSANamedCurveTable.getByName("ed25519");

    public static final String MASTER_PATH = "m";

    public HdKeyPair getRootKeyPairFromEntropy(byte[] entropy) {
        byte[] xprv = pbkdf2HmacSha512("".toCharArray(), entropy, 4096, 768);
        xprv[0] &= 248;
        xprv[31] &= 31;
        xprv[31] |= 64;

//        xprv[0] &= 0b1111_1000;
//        xprv[31] &= 0b0001_1111;
//        xprv[31] |= 0b0100_0000;

        return getKeyPairFromSecretKey(xprv, MASTER_PATH);
    }

    public HdKeyPair getAccountKeyPairFromSecretKey(byte[] xprv, DerivationPath derivationPath) {
        String accountPath = getPath(MASTER_PATH, derivationPath.getPurpose().getValue(), derivationPath.getPurpose().isHarden());
        accountPath = getPath(accountPath, derivationPath.getCoinType().getValue(), derivationPath.getCoinType().isHarden());
        accountPath = getPath(accountPath, derivationPath.getAccount().getValue(), derivationPath.getAccount().isHarden());
        return getKeyPairFromSecretKey(xprv, accountPath);
    }

    private HdKeyPair getKeyPairFromSecretKey(byte[] xprv, String path) {
        byte[] IL = Arrays.copyOfRange(xprv, 0, 64);
        byte[] IR = Arrays.copyOfRange(xprv, 64, 96);

        byte[] A = ED25519SPEC.getB().scalarMultiply(IL).toByteArray();

        HdPublicKey publicKey = new HdPublicKey();
        HdPrivateKey privateKey = new HdPrivateKey();

        privateKey.setKeyData(IL);
        privateKey.setChainCode(IR);

        publicKey.setKeyData(A);
        publicKey.setChainCode(IR);

        return new HdKeyPair(privateKey, publicKey, path);
    }

    private byte[] pbkdf2HmacSha512(final char[] password, final byte[] salt, final int iterations,
                                    final int keyLength) {

        try {
            if (OSUtil.isAndroid()) {
                PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA512Digest());
                gen.init(new String(password).getBytes(StandardCharsets.UTF_8), salt, iterations);
                byte[] dk = ((KeyParameter) gen.generateDerivedParameters(keyLength)).getKey();
                return dk;
            } else {
                SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
                PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
                SecretKey key = skf.generateSecret(spec);
                return key.getEncoded();
            }
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Derive the child key pair (public + private) from the parent key pair.
     *
     * @param parent     the parent key
     * @param child      the child index
     * @param isHardened whether is child index is hardened
     * @return
     */
    public HdKeyPair getChildKeyPair(HdKeyPair parent, long child, boolean isHardened) {
        HdPrivateKey privateKey = new HdPrivateKey();
        HdPublicKey publicKey = new HdPublicKey();
        HdKeyPair key = new HdKeyPair(privateKey, publicKey,
                getPath(parent.getPath(), child, isHardened));

        if (isHardened) {
            child += 0x80000000;
        }

        byte[] xChain = parent.getPrivateKey().getChainCode();
        /// backwards hmac order in method?
        byte[] I;
        if (isHardened) {
            // If so (hardened child): let I = HMAC-SHA512(Key = cpar, Data = 0x00 ||
            // ser256(kpar) || ser32(i)). (Note: The 0x00 pads the private key to make it 33
            // bytes long.)
            BigInteger kpar = BytesUtil.parse256(parent.getPrivateKey().getKeyData());
            byte[] data = BytesUtil.merge(new byte[]{0}, BytesUtil.ser256(kpar), BytesUtil.ser32(child));
            I = Hmac.hmac512(data, xChain);
        } else {
            // I = HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i))
            // just use public key
            byte[] data = BytesUtil.merge(parent.getPublicKey().getKeyData(), BytesUtil.ser32(child));
            I = Hmac.hmac512(data, xChain);
        }
        // split into left/right
        byte[] IL = Arrays.copyOfRange(I, 0, 32);
        byte[] IR = Arrays.copyOfRange(I, 32, 64);

        byte[] childNumber = BytesUtil.ser32(child);

        privateKey.setVersion(parent.getPrivateKey().getVersion());
        privateKey.setDepth(parent.getPrivateKey().getDepth() + 1);
        privateKey.setChildNumber(childNumber);
        privateKey.setChainCode(IR);

        publicKey.setVersion(parent.getPublicKey().getVersion());
        publicKey.setDepth(parent.getPublicKey().getDepth() + 1);
        publicKey.setChildNumber(childNumber);
        publicKey.setChainCode(IR);

        //If derivation V2 Shelley
        byte[] kP = parent.getPrivateKey().getKeyData();
        byte[] kLP = Arrays.copyOfRange(kP, 0, 32);
        byte[] kRP = Arrays.copyOfRange(kP, 32, 64);
        byte[] AP = parent.getPublicKey().getKeyData();
        byte[] cP = parent.getPublicKey().getChainCode();

        byte[] Z, c;
        if (isHardened) {
            byte[] data = BytesUtil.merge(new byte[]{0}, kLP, kRP, BytesUtil.ser32LE(child));
            Z = Hmac.hmac512(data, cP);
            data[0] = 1;
            c = Hmac.hmac512(data, cP);
        } else {
            byte[] data = BytesUtil.merge(new byte[]{2}, AP, BytesUtil.ser32LE(child));
            Z = Hmac.hmac512(data, cP);
            data[0] = 3;
            c = Hmac.hmac512(data, cP);
        }
        c = Arrays.copyOfRange(c, 32, 64);
        byte[] ZL = Arrays.copyOfRange(Z, 0, 28);
        byte[] ZR = Arrays.copyOfRange(Z, 32, 64);

        if (logger.isTraceEnabled()) {
            logger.trace("parent, kLP = " + HexUtil.encodeHexString(kLP));
            logger.trace("parent, kRP = " + HexUtil.encodeHexString(kRP));
            logger.trace("parent,  AP = " + HexUtil.encodeHexString(AP));
            logger.trace("parent,  cP = " + HexUtil.encodeHexString(cP));
        }

        BigInteger kLiBI = parseUnsignedLE(ZL)
                .multiply(BigInteger.valueOf(8))
                .add(parseUnsignedLE(kLP));
        BigInteger order = BigInteger.valueOf(2).pow(252)
                .add(new BigInteger("27742317777372353535851937790883648493"));
        if (kLiBI.mod(order).equals(BigInteger.ZERO)) {
            return null;
        }
        IL = serializeUnsignedLE256(kLiBI);

        BigInteger kRiBI = parseUnsignedLE(ZR)
                .add(parseUnsignedLE(kRP))
                .mod(BigInteger.valueOf(2).pow(256));
        IR = serializeUnsignedLE256(kRiBI);

        I = BytesUtil.merge(IL, IR);
        byte[] A = ED25519SPEC.getB().scalarMultiply(IL).toByteArray();

        privateKey.setKeyData(I);
        publicKey.setKeyData(A);

        privateKey.setChainCode(c);
        publicKey.setChainCode(c);

        if (logger.isTraceEnabled()) {
            logger.trace("child, IL = " + HexUtil.encodeHexString(IL));
            logger.trace("child, IR = " + HexUtil.encodeHexString(IR));
            logger.trace("child,  A = " + HexUtil.encodeHexString(A));
            logger.trace("child,  c = " + HexUtil.encodeHexString(c));
        }

        return key;
    }

    /**
     * Derive the public child key from HD parent public key
     *
     * @param parent     the parent key
     * @param child      the child index
     * @return
     */
    public HdPublicKey getChildPublicKey(HdPublicKey parent, int child) {
        HdPublicKey publicKey = new HdPublicKey();
        byte[] AP = parent.getKeyData();

        byte[] pChain = parent.getChainCode();
        byte[] childNumber = BytesUtil.ser32(child);

        //prefix 0x02 for child public key
        byte[] ApLE = serializeUnsignedLE256(parseUnsignedLE(AP));
        byte[] data = BytesUtil.merge(new byte[]{2}, ApLE, BytesUtil.ser32LE(child));
        byte[] Z = Hmac.hmac512(data, pChain);

        //prefix 0x03 for child chain code
        data[0] = 3;
        byte[] c = Hmac.hmac512(data, parent.getChainCode());

        //truncate to right 32 bytes for child chain code
        c = Arrays.copyOfRange(c, 32, 64);

        // split into left (28 bytes) /right (for child public key)
        byte[] ZL = Arrays.copyOfRange(Z, 0, 28);
//      byte[] ZR = Arrays.copyOfRange(Z, 32, 64);

        //Ai <- AP + [8ZL]B,
        BigInteger kLiBI = parseUnsignedLE(ZL)
                .multiply(BigInteger.valueOf(8));

        byte[] kLi = serializeUnsignedLE256(kLiBI);

        GroupElement gp1 = new GroupElement(ED25519SPEC.getCurve(), AP);
        gp1 = gp1.toCached();
        GroupElement groupElement = ED25519SPEC.getB().scalarMultiply(kLi).add(gp1);
        //TODO -- If Ai is the identity point (0, 1), discard the child

        byte[] Ai = groupElement.toByteArray(); //child public key

        publicKey.setVersion(parent.getVersion());
        publicKey.setDepth(parent.getDepth() + 1);
        publicKey.setChildNumber(childNumber);
        publicKey.setChainCode(c);
        publicKey.setKeyData(Ai);

        return publicKey;
    }

    private String getPath(String parentPath, long child, boolean isHardened) {
        if (parentPath == null) {
            parentPath = MASTER_PATH;
        }
        return parentPath + "/" + child + (isHardened ? "'" : "");
    }

    private void reverse(byte[] input) {
        for (int i = 0; i < input.length / 2; i++) {
            byte temp = input[i];
            input[i] = input[input.length - 1 - i];
            input[input.length - 1 - i] = temp;
        }
    }

    private BigInteger parseUnsignedLE(byte[] bytes) {
        byte[] temp = bytes.clone();
        reverse(temp);
        return new BigInteger(1, temp);
    }

    private byte[] serializeUnsignedLE256(BigInteger bi) {
        byte[] temp = bi.toByteArray();
        if (temp.length > 32) {
            temp = Arrays.copyOfRange(temp, temp.length - 32, temp.length);
        }

        reverse(temp);

        if (temp.length < 32) {
            return Arrays.copyOf(temp, 32);
        } else {
            return temp;
        }
    }

    public static byte[] getPublicKey(byte[] privateKey) {
        byte[] IL = Arrays.copyOfRange(privateKey, 0, 32);
        byte[] A = ED25519SPEC.getB().scalarMultiply(IL).toByteArray();
        return A;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy