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

com.github.DNAProject.crypto.bip32.HdPrivateKey Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2018 The DNA Authors
 * This file is part of The DNA library.
 *
 *  The DNA is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  The DNA is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with The DNA.  If not, see .
 *
 */
package com.github.DNAProject.crypto.bip32;

import com.github.DNAProject.common.Address;
import com.github.DNAProject.common.ErrorCode;
import com.github.DNAProject.common.Helper;
import com.github.DNAProject.crypto.Base58;
import com.github.DNAProject.crypto.Curve;
import com.github.DNAProject.crypto.Digest;
import com.github.DNAProject.crypto.bip32.derivation.CkdFunction;
import com.github.DNAProject.crypto.bip32.derivation.CkdFunctionDerive;
import com.github.DNAProject.crypto.bip32.derivation.Derive;
import com.github.DNAProject.crypto.bip32.derivation.Derivation;

import com.github.DNAProject.sdk.exception.SDKException;
import io.github.novacrypto.bip39.SeedCalculator;
import io.github.novacrypto.bip39.wordlists.English;
import io.github.novacrypto.toruntime.CheckedExceptionToRuntime;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

import static com.github.DNAProject.crypto.bip32.ByteArrayWriter.head32;
import static com.github.DNAProject.crypto.bip32.ByteArrayWriter.tail32;
import static com.github.DNAProject.crypto.bip32.HdKey.parse256;
import static com.github.DNAProject.crypto.bip32.HdKey.ser256;
import static com.github.DNAProject.crypto.bip32.Secp256r1SC.n;
import static com.github.DNAProject.crypto.bip32.derivation.CharSequenceDerivation.isHardened;

import static io.github.novacrypto.toruntime.CheckedExceptionToRuntime.toRuntime;

public class HdPrivateKey implements
        Derive,
        CKDpriv,
        CKDpub {

    private static final byte[] BITCOIN_SEED = "Bitcoin seed".getBytes(StandardCharsets.UTF_8);

    private static final byte[] SEED_NAME = "Nist256p1 seed".getBytes(StandardCharsets.UTF_8);

    private static Deserializer deserializer() {
        return HdPrivateKeyDeserializer.DEFAULT;
    }

    public static Deserializer deserializer(final Network network) {
        return new HdPrivateKeyDeserializer(network);
    }

    private static final CkdFunction CKD_FUNCTION = HdPrivateKey::cKDpriv;

    public static HdPrivateKey masterKeyFromMnemonic(String code, String passphrase) {
        byte[] seed = new SeedCalculator()
                .withWordsFromWordList(English.INSTANCE)
                .calculateSeed(Arrays.asList(code.split(" ")), passphrase);
        return HdPrivateKey.fromSeed(seed, SEED_NAME, Bitcoin.MAIN_NET);
    }

    public static HdPrivateKey masterKeyFromMnemonic(String code) {
        return masterKeyFromMnemonic(code, "");
    }

    public String toHexString() {
        return Helper.toHexString(hdKey.getKey());
    }

    public byte[] getPrivateKey() {
        return hdKey.getKey();
    }

    public Address getAddress() throws Exception {
        return getHdPublicKey().getAddress();
    }

    public HdPublicKey getHdPublicKey() throws Exception {
        ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec((String) new Object[]{Curve.P256.toString()}[0]);
        ECPoint Q = spec.getG().multiply(new BigInteger(1, getPrivateKey())).normalize();
        if (Q == null || Q.getAffineXCoord() == null || Q.getAffineYCoord() == null) {
            throw new SDKException(ErrorCode.OtherError("normalize error"));
        }
        return new HdPublicKey(new HdKey.Builder()
                .network(hdKey.getNetwork())
                .neutered(true)
                .key(Q.getEncoded(true))
                .parentFingerprint(hdKey.getParentFingerprint())
                .depth(hdKey.depth())
                .childNumber(hdKey.getChildNumber())
                .chainCode(hdKey.getChainCode())
                .build());
    }

    private final HdKey hdKey;

    private HdPrivateKey(final Network network, final byte[] key, final byte[] chainCode) {
        this(new HdKey.Builder()
                .network(network)
                .neutered(false)
                .key(key)
                .chainCode(chainCode)
                .depth(0)
                .childNumber(0)
                .parentFingerprint(0)
                .build());
    }

    public HdPrivateKey(final HdKey hdKey) {
        this.hdKey = hdKey;
    }

    public static HdPrivateKey fromSeed(final byte[] seed, final Network network) {
        final byte[] I = Digest.hmacSha512(BITCOIN_SEED, seed);

        final byte[] Il = head32(I);
        final byte[] Ir = tail32(I);

        return new HdPrivateKey(network, Il, Ir);
    }

    public static HdPrivateKey fromSeed(final byte[] seed, byte[] byteKey, final Network network) {
        final byte[] I = Digest.hmacSha512(byteKey, seed);

        final byte[] Il = head32(I);
        final byte[] Ir = tail32(I);

        return new HdPrivateKey(network, Il, Ir);
    }

    private static byte[] getBytes(final String seed) {
        return toRuntime(new CheckedExceptionToRuntime.Func() {
            @Override
            public byte[] run() throws Exception {
                return seed.getBytes(StandardCharsets.UTF_8);
            }
        });
    }

    public static HdPrivateKey base58Decode(String key) throws SDKException {
        return HdPrivateKey.deserializer().deserialize(Base58.decode(key));
    }

    @Override
    public HdPrivateKey cKDpriv(final int index) {
        final byte[] data = new byte[37];
        final ByteArrayWriter writer = new ByteArrayWriter(data);
        final HdKey parent = this.hdKey;
        if (isHardened(index)) {
            writer.concat((byte) 0);
            writer.concat(parent.getKey(), 32);
        } else {
            writer.concat(parent.getPoint());
        }
        writer.concatSer32(index);

        final byte[] I = Digest.hmacSha512(parent.getChainCode(), data);
        Arrays.fill(data, (byte) 0);

        final byte[] Il = head32(I);
        final byte[] Ir = tail32(I);

        final byte[] key = parent.getKey();
        final BigInteger parse256_Il = parse256(Il);
        final BigInteger ki = parse256_Il.add(parse256(key)).mod(n());

        if (parse256_Il.compareTo(n()) >= 0 || ki.equals(BigInteger.ZERO)) {
            return cKDpriv(index + 1);
        }

        ser256(Il, ki);

        return new HdPrivateKey(new HdKey.Builder()
                .network(parent.getNetwork())
                .neutered(false)
                .key(Il)
                .chainCode(Ir)
                .depth(parent.depth() + 1)
                .childNumber(index)
                .parentFingerprint(parent.calculateFingerPrint())
                .build());
    }

    @Override
    public HdPublicKey cKDpub(final int index) {
        return cKDpriv(index).neuter();
    }

    public HdPublicKey neuter() {
        return HdPublicKey.from(hdKey);
    }

    private Derive derive() {
        return new CkdFunctionDerive<>(CKD_FUNCTION, this);
    }

    @Override
    public HdPrivateKey fromPath(final CharSequence derivationPath) {
        final int length = derivationPath.length();
        if (length == 0)
            throw new IllegalArgumentException("Path cannot be empty");
        if (length == 1)
            return this;
        if (derivationPath.charAt(0) == 'm' && depth() == 0) {
            if (derivationPath.charAt(1) != '/')
                throw new IllegalArgumentException("Root key must be a master key if the path start with m/");
            return derive().fromPath(derivationPath.subSequence(2, derivationPath.length()));
        }
        return derive().fromPath(derivationPath);
    }

    public HdPrivateKey fromPath() {
        return fromPath("m/44'/1024'/0'");
    }

    @Override
    public  HdPrivateKey fromPath(Path derivationPath, Derivation derivation) {
        return derive().fromPath(derivationPath, derivation);
    }

    public byte[] toByteArray() {
        return hdKey.serialize();
    }

    public Network network() {
        return hdKey.getNetwork();
    }

    public int depth() {
        return hdKey.depth();
    }

    public int childNumber() {
        return hdKey.getChildNumber();
    }

    public String base58Encode() {
        return Base58.encode(toByteArray());
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy