org.hyperledger.common.PrivateKey.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperledger.common.PrivateKey.java

Source

/**
 * 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.hyperledger.common;

import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DERSequenceGenerator;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;

/**
 * An EC Private Key, technically a big positive integer capable of signing such that the signature can be verified
 * with the corresponding EC Public Key
 *
 * @see PublicKey
 */
public class PrivateKey implements Key {
    static final SecureRandom secureRandom = new SecureRandom();
    static final BigInteger HALF_CURVE_ORDER = curve.getN().shiftRight(1);

    private final BigInteger priv;
    private final byte[] pub;
    private final boolean compressed;

    /**
     * Older Bitcoin transactions used uncompressed Public keys. Forget them.
     *
     * @return true for compressed
     */
    @Override
    public boolean isCompressed() {
        return compressed;
    }

    /**
     * Create a new PrivateKey using the platform provided secure random source.
     *
     * @return new PrivateKey
     */
    public static PrivateKey createNew() {
        return createNew(true);
    }

    /**
     * Create a new PrivateKey using the platform provided secure random source.
     *
     * @param compressed set to false if you relly want legacy format
     * @return new PrivateKey
     */
    public static PrivateKey createNew(boolean compressed) {
        ECKeyPairGenerator generator = new ECKeyPairGenerator();
        ECKeyGenerationParameters keygenParams = new ECKeyGenerationParameters(domain, secureRandom);
        generator.init(keygenParams);
        AsymmetricCipherKeyPair keypair = generator.generateKeyPair();
        ECPrivateKeyParameters privParams = (ECPrivateKeyParameters) keypair.getPrivate();
        ECPublicKeyParameters pubParams = (ECPublicKeyParameters) keypair.getPublic();
        return new PrivateKey(privParams.getD(), compressed, pubParams.getQ().getEncoded(compressed));
    }

    /**
     * Return the private key as big positive integer.
     *
     * @return an integer
     */
    public BigInteger asBigInteger() {
        return priv;
    }

    /**
     * Return the private key as a 32byte array aligned to the right.
     *
     * @return private key as byte array
     */
    @Override
    public byte[] toByteArray() {
        byte[] p = priv.toByteArray();

        if (p.length != 32) {
            byte[] tmp = new byte[32];
            System.arraycopy(p, Math.max(0, p.length - 32), tmp, Math.max(0, 32 - p.length),
                    Math.min(32, p.length));
            p = tmp;
        }

        return p;
    }

    /**
     * Return the corresponding public key. Computing Public from Private is easy, the other direction is supposedly
     * practically impossible. Bitcoin was dead if it was not.
     *
     * @return
     */
    public PublicKey getPublic() {
        return new PublicKey(pub, compressed);
    }

    @Override
    public Address getAddress() {
        try {
            return new Address(Address.Type.COMMON, Hash.keyHash(pub));
        } catch (HyperLedgerException e) {
            return null;
        }
    }

    /**
     * (Re-)create a private key from a byte array and telling if its public counterpart should be compressed.
     *
     * @param p          32 bytes of data
     * @param compressed true for modern, false for legacy keys
     * @throws HyperLedgerException
     */
    public PrivateKey(byte[] p, boolean compressed) throws HyperLedgerException {
        if (p.length != 32) {
            throw new HyperLedgerException("Invalid private key");
        }
        this.priv = new BigInteger(1, p).mod(curve.getN());
        this.compressed = compressed;
        pub = curve.getG().multiply(priv).getEncoded(compressed);
    }

    /**
     * (Re-)create a private key from a big positive integer and telling if its public counterpart should be compressed.
     *
     * @param priv       key as number
     * @param compressed true for modern, false for legacy keys
     * @throws HyperLedgerException if priv is not positive or greater than curve modulo
     */
    public PrivateKey(BigInteger priv, boolean compressed) throws HyperLedgerException {
        if (priv.compareTo(BigInteger.ZERO) <= 0 || priv.compareTo(curve.getN()) >= 0) {
            throw new HyperLedgerException("invalid key");
        }
        this.priv = priv;
        this.compressed = compressed;
        pub = curve.getG().multiply(priv).getEncoded(compressed);
    }

    private PrivateKey(BigInteger priv, boolean compressed, byte[] pub) {
        this.priv = priv;
        this.compressed = compressed;
        this.pub = pub;
    }

    /**
     * Sign a digest with this key.
     *
     * @param hash arbitrary data
     * @return signature
     */
    public byte[] sign(byte[] hash) {
        ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
        signer.init(true, new ECPrivateKeyParameters(priv, domain));
        BigInteger[] signature = signer.generateSignature(hash);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            DERSequenceGenerator seq = new DERSequenceGenerator(baos);
            seq.addObject(new ASN1Integer(signature[0]));
            seq.addObject(new ASN1Integer(toCanonicalS(signature[1])));
            seq.close();
            return baos.toByteArray();
        } catch (IOException e) {
        }
        return null;
    }

    private BigInteger toCanonicalS(BigInteger s) {
        if (s.compareTo(HALF_CURVE_ORDER) <= 0) {
            return s;
        } else {
            return curve.getN().subtract(s);
        }
    }

    @Override
    public String toString() {
        return "private key of " + getAddress();
    }

    /**
     * Serialize a private key into Base58 used by Bitcoin
     *
     * @param key private key
     * @return Base58 string
     */
    public static String serializeWIF(PrivateKey key) {
        return ByteUtils.toBase58(bytesWIF(key));
    }

    private static byte[] bytesWIF(PrivateKey key) {
        byte[] k = key.toByteArray();
        if (key.isCompressed()) {
            byte[] encoded = new byte[k.length + 6];
            byte[] ek = new byte[k.length + 2];
            ek[0] = (byte) 0x80;
            System.arraycopy(k, 0, ek, 1, k.length);
            ek[k.length + 1] = 0x01;
            byte[] hash = Hash.hash(ek);
            System.arraycopy(ek, 0, encoded, 0, ek.length);
            System.arraycopy(hash, 0, encoded, ek.length, 4);
            return encoded;
        } else {
            byte[] encoded = new byte[k.length + 5];
            byte[] ek = new byte[k.length + 1];
            ek[0] = (byte) 0x80;
            System.arraycopy(k, 0, ek, 1, k.length);
            byte[] hash = Hash.hash(ek);
            System.arraycopy(ek, 0, encoded, 0, ek.length);
            System.arraycopy(hash, 0, encoded, ek.length, 4);
            return encoded;
        }
    }

    /**
     * Recreate a private key form its Base58 serialization
     *
     * @param serialized private key in text
     * @return Base58 string
     */
    public static PrivateKey parseWIF(String serialized) throws HyperLedgerException {
        byte[] store = ByteUtils.fromBase58(serialized);
        return parseBytesWIF(store);
    }

    /**
     * Recreate a private key form its binary serialization
     *
     * @param store private key in text
     * @return Base58 string
     */
    public static PrivateKey parseBytesWIF(byte[] store) throws HyperLedgerException {
        if (store.length == 37) {
            checkChecksum(store);
            byte[] key = new byte[store.length - 5];
            System.arraycopy(store, 1, key, 0, store.length - 5);
            return new PrivateKey(key, false);
        } else if (store.length == 38) {
            checkChecksum(store);
            byte[] key = new byte[store.length - 6];
            System.arraycopy(store, 1, key, 0, store.length - 6);
            return new PrivateKey(key, true);
        }
        throw new HyperLedgerException("Invalid key length");
    }

    private static void checkChecksum(byte[] store) throws HyperLedgerException {
        byte[] checksum = new byte[4];
        System.arraycopy(store, store.length - 4, checksum, 0, 4);
        byte[] ekey = new byte[store.length - 4];
        System.arraycopy(store, 0, ekey, 0, store.length - 4);
        byte[] hash = Hash.hash(ekey);
        for (int i = 0; i < 4; ++i) {
            if (hash[i] != checksum[i]) {
                throw new HyperLedgerException("checksum mismatch");
            }
        }
    }

    public PrivateKey offsetKey(BigInteger offset) throws HyperLedgerException {
        if (offset.compareTo(curve.getN()) >= 0) {
            throw new HyperLedgerException("This is rather unlikely, but it did just happen");
        }
        BigInteger k = offset.add(priv).mod(curve.getN());
        if (k.compareTo(BigInteger.ZERO) == 0) {
            throw new HyperLedgerException("This is rather unlikely, but it did just happen");
        }
        return new PrivateKey(k, compressed);
    }

    @Override
    public int hashCode() {
        return priv.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof PrivateKey) {
            return priv.compareTo(((PrivateKey) obj).priv) == 0 && compressed == ((PrivateKey) obj).compressed;
        }
        return false;
    }
}