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

software.crldev.elrondspringbootstarterreactive.util.MnemonicsUtils Maven / Gradle / Ivy

Go to download

A SpringBoot Starter solution designed to ensure easy and efficient integration with the Elrond Network using a Reactive API layer.

The newest version!
package software.crldev.elrondspringbootstarterreactive.util;

import software.crldev.elrondspringbootstarterreactive.error.exception.CannotDeriveKeysException;
import software.crldev.elrondspringbootstarterreactive.error.exception.CannotGenerateMnemonicException;
import org.bitcoinj.crypto.MnemonicCode;
import org.bitcoinj.crypto.MnemonicException;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static software.crldev.elrondspringbootstarterreactive.config.constants.WalletConstants.*;

/**
 * Utility class for mnemonics operations
 *
 * @author carlo_stanciu
 *
 *  source from
 */
public class MnemonicsUtils {

    /**
     * Method used to generate mnemonics which can be used on Wallet creation
     *
     * @return - mnemonic phrase as List of Strings
     */
    public static List generateMnemonic() {
        try {
            var entropy = generateEntropy();
            var mnemonicCode = new MnemonicCode();
            return mnemonicCode.toMnemonic(entropy);
        } catch (IOException | MnemonicException.MnemonicLengthException error) {
            throw new CannotGenerateMnemonicException();
        }
    }

    /**
     * Method used to extract private key buffer from a list of mnemonics
     *
     * @param mnemonics - a mnemonic phrase as list of String objects
     * @param accountIndex - accountIndex
     * @return - buffer of the private key
     */
    public static byte[] privateKeyFromMnemonic(List mnemonics, long accountIndex) {
        try {
            var mnemonicsAsString = mnemonics.stream().map(String::trim).collect(Collectors.joining(" "));
            var seed = mnemonicToBip39Seed(mnemonicsAsString);
            return bip39SeedToPrivateKey(seed, accountIndex);
        } catch (IOException error) {
            throw new CannotDeriveKeysException();
        }
    }

    private static byte[] mnemonicToBip39Seed(String mnemonic) {
        var mnemonicBytes = mnemonic.getBytes();
        var passphrase = BIP39_SALT_MODIFIER.getBytes();
        var generator = new PKCS5S2ParametersGenerator(new SHA512Digest());

        generator.init(mnemonicBytes, passphrase, BIP39_PBKDF2_ROUNDS);
        return ((KeyParameter) generator.generateDerivedParameters(512)).getKey();
    }

    private static byte[] generateEntropy() {
        var random = new SecureRandom();
        var entropy = new byte[DEFAULT_ENTROPY_BITS / 8];
        random.nextBytes(entropy);
        return entropy;
    }

    private static byte[] bip39SeedToPrivateKey(byte[] seed, long accountIndex) throws IOException {
        var keyAndChainCode = bip39SeedToMasterKey(seed);
        var key = keyAndChainCode.key;
        var chainCode = keyAndChainCode.chainCode;
        var derivationPath = Arrays.copyOf(ELROND_DERIVATION_PATH, ELROND_DERIVATION_PATH.length);

        derivationPath[derivationPath.length - 1] = accountIndex;

        for (var segment : derivationPath) {
            keyAndChainCode = ckdPriv(key, chainCode, segment + HARDENED_OFFSET);
            key = keyAndChainCode.key;
            chainCode = keyAndChainCode.chainCode;
        }

        return key;
    }

    private static KeyAndChainCode bip39SeedToMasterKey(byte[] seed) {
        var result = hmacSHA512(BIP32_SEED_MODIFIER.getBytes(), seed);
        var masterKey = Arrays.copyOfRange(result, 0, 32);
        var chainCode = Arrays.copyOfRange(result, 32, 64);

        return new KeyAndChainCode(masterKey, chainCode);
    }

    private static KeyAndChainCode ckdPriv(byte[] key, byte[] chainCode, long index) throws IOException {
        var indexBuffer = ByteBuffer.allocate(4);
        indexBuffer.order(ByteOrder.BIG_ENDIAN);
        indexBuffer.putInt((int) (index & 0xffffffffL));
        var indexBytes = indexBuffer.array();

        var dataStream = new ByteArrayOutputStream();
        dataStream.write(new byte[]{0});
        dataStream.write(key);
        dataStream.write(indexBytes);

        var data = dataStream.toByteArray();
        var result = hmacSHA512(chainCode, data);

        return new KeyAndChainCode(Arrays.copyOfRange(result, 0, 32), Arrays.copyOfRange(result, 32, 64));
    }

    private static byte[] hmacSHA512(byte[] key, byte[] message) {
        var result = new byte[64];
        var hmac = new HMac(new SHA512Digest());

        hmac.init(new KeyParameter(key));
        hmac.update(message, 0, message.length);
        hmac.doFinal(result, 0);

        return result;
    }

    private static class KeyAndChainCode {
        public final byte[] key;
        public final byte[] chainCode;

        private KeyAndChainCode(byte[] key, byte[] chainCode) {
            this.key = key;
            this.chainCode = chainCode;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            var that = (KeyAndChainCode) o;

            if (!Arrays.equals(key, that.key)) return false;
            return Arrays.equals(chainCode, that.chainCode);
        }

        @Override
        public int hashCode() {
            var result = Arrays.hashCode(key);
            result = 31 * result + Arrays.hashCode(chainCode);

            return result;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy