us.eharning.atomun.core.ec.internal.BouncyCastleECKeyPair.java Source code

Java tutorial

Introduction

Here is the source code for us.eharning.atomun.core.ec.internal.BouncyCastleECKeyPair.java

Source

/*
 * Copyright 2015 Thomas Harning Jr. <harningt@gmail.com>
 *
 * 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 us.eharning.atomun.core.ec.internal;

import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
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 us.eharning.atomun.core.ValidationException;
import us.eharning.atomun.core.ec.ECKey;
import us.eharning.atomun.core.encoding.Base58;
import us.eharning.atomun.core.utility.Hash;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;

/**
 * ECKey implementation wrapping a full keypair using BouncyCastle.
 */
@Immutable
public class BouncyCastleECKeyPair extends BouncyCastleECPublicKey {
    /**
     * Canonicalization flag - default true, but can be disabled in unit tests.
     * @TODO: Drop this in place of an alternate signing mechanism that can handle this.
     */
    @SuppressFBWarnings("JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS")
    static boolean CANONICALIZE = true;
    private static final BigInteger HALF_CURVE_ORDER = curve.getN().shiftRight(1);

    @Nonnull
    private final BigInteger privateExponent;

    /**
     * Construct a new EC keypair given the private exponent and whether or not to use compressed point form.
     *
     * @param privateExponent
     *         value defining the private key.
     * @param compressed
     *         whether or not to use compressed point form.
     */
    public BouncyCastleECKeyPair(@Nonnull BigInteger privateExponent, boolean compressed) {
        this(privateExponent, curve.getG().multiply(privateExponent).getEncoded(compressed), compressed);
    }

    /**
     * Construct a new EC keypair given the private exponent, its public point, and whether or not to use compressed point form.
     *
     * @param privateExponent
     *         value defining the private key.
     * @param encodedPublicKey
     *         DER-encoded public point associated with the given private key.
     * @param compressed
     *         whether or not to use compressed point form.
     */
    public BouncyCastleECKeyPair(@Nonnull BigInteger privateExponent, @Nonnull byte[] encodedPublicKey,
            boolean compressed) {
        super(encodedPublicKey, compressed);
        Preconditions.checkNotNull(privateExponent);
        this.privateExponent = privateExponent;
    }

    /**
     * Utility method to create a new random EC keypair.
     *
     * @param compressed
     *         whether or not to use compressed point form.
     *
     * @return random EC keypair.
     */
    @Nonnull
    public static BouncyCastleECKeyPair 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 BouncyCastleECKeyPair(privParams.getD(), pubParams.getQ().getEncoded(compressed), compressed);
    }

    /**
     * Import the serialized EC private key given its private exponent as a byte array.
     *
     * @param serializedPrivateExponent
     *         byte array containing value defining the private key.
     * @param compressed
     *         whether or not to use compressed point form.
     *
     * @return the decoded EC private key.
     *
     * @throws ValidationException
     *         if the key is invalid.
     */
    @Nonnull
    public static BouncyCastleECKeyPair importSerialized(@Nonnull byte[] serializedPrivateExponent,
            boolean compressed) throws ValidationException {
        Preconditions.checkNotNull(serializedPrivateExponent);
        if (serializedPrivateExponent.length != 32) {
            throw new ValidationException("Invalid private key");
        }
        return new BouncyCastleECKeyPair(new BigInteger(1, serializedPrivateExponent).mod(curve.getN()),
                compressed);
    }

    /**
     * Serialize the EC keypair in WIF Base58-encoded form.
     *
     * @param key
     *         instance to seralize.
     *
     * @return serialized EC keypair.
     */
    @Nonnull
    public static String serializeWIF(@Nonnull BouncyCastleECKeyPair key) {
        return Base58.encode(bytesWIF(key));
    }

    /**
     * Serialize the EC keypair as a WIF byte array.
     *
     * @param key
     *         instance to serialize.
     *
     * @return serialized EC keypair.
     */
    @SuppressWarnings("checkstyle:localvariablename")
    @Nonnull
    private static byte[] bytesWIF(@Nonnull BouncyCastleECKeyPair key) {
        byte[] k = key.exportPrivate();
        if (key.compressed) {
            final byte[] encoded = new byte[k.length + 6];
            final byte[] ek = new byte[k.length + 2];
            ek[0] = (byte) 0x80;
            System.arraycopy(k, 0, ek, 1, k.length);
            ek[k.length + 1] = 0x01;
            final byte[] hash = Hash.doubleHash(ek);
            System.arraycopy(ek, 0, encoded, 0, ek.length);
            System.arraycopy(hash, 0, encoded, ek.length, 4);
            return encoded;
        } else {
            final byte[] encoded = new byte[k.length + 5];
            final byte[] ek = new byte[k.length + 1];
            ek[0] = (byte) 0x80;
            System.arraycopy(k, 0, ek, 1, k.length);
            final byte[] hash = Hash.doubleHash(ek);
            System.arraycopy(ek, 0, encoded, 0, ek.length);
            System.arraycopy(hash, 0, encoded, ek.length, 4);
            return encoded;
        }
    }

    /**
     * Parse a key in WIF base64-encoded form.
     *
     * @param serialized
     *         base64-encoded WIF-encoded EC key
     *
     * @return decoded key
     *
     * @throws ValidationException
     *         if the key is invalid.
     */
    @Nonnull
    public static BouncyCastleECKeyPair parseWIF(@Nonnull String serialized) throws ValidationException {
        byte[] store = Base58.decode(serialized);
        return parseBytesWIF(store);
    }

    /**
     * Parse a key in WIF byte-data form.
     *
     * @param store
     *         WIF-encoded EC key
     *
     * @return decoded key
     *
     * @throws ValidationException
     *         if the key is invalid.
     */
    @Nonnull
    public static BouncyCastleECKeyPair parseBytesWIF(@Nonnull byte[] store) throws ValidationException {
        if (store.length == 37) {
            verifyChecksum(store);
            byte[] key = new byte[store.length - 5];
            System.arraycopy(store, 1, key, 0, store.length - 5);
            return importSerialized(key, false);
        } else if (store.length == 38) {
            verifyChecksum(store);
            byte[] key = new byte[store.length - 6];
            System.arraycopy(store, 1, key, 0, store.length - 6);
            return importSerialized(key, true);
        }
        throw new ValidationException("Invalid key length");
    }

    private static void verifyChecksum(@Nonnull byte[] store) throws ValidationException {
        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.doubleHash(ekey);
        for (int i = 0; i < 4; ++i) {
            if (hash[i] != checksum[i]) {
                throw new ValidationException("Checksum mismatch");
            }
        }
    }

    /**
     * Export the private key in bitcoin 'standard' form - exactly 32-bytes.
     *
     * @return exported 32-byte private key.
     */
    @Nonnull
    @Override
    public byte[] exportPrivate() {
        byte[] privateBytes = privateExponent.toByteArray();
        if (privateBytes.length != 32) {
            byte[] tmp = new byte[32];
            System.arraycopy(privateBytes, Math.max(0, privateBytes.length - 32), tmp,
                    Math.max(0, 32 - privateBytes.length), Math.min(32, privateBytes.length));
            privateBytes = tmp;
        }
        return privateBytes;
    }

    /**
     * Returns whether or not this keypair is populated with the private key.
     *
     * @return true - the private key is present.
     */
    @Override
    public boolean hasPrivate() {
        return true;
    }

    /**
     * Obtain a reference to this key, just including public pieces.
     *
     * @return instance with just public data present.
     */
    @Nonnull
    @Override
    public ECKey getPublic() {
        return new BouncyCastleECPublicKey(encodedPublicKey, compressed);
    }

    /**
     * Perform an ECDSA signature using the private key.
     *
     * @param hash
     *         byte array to sign.
     *
     * @return ASN.1 representation of the signature.
     */
    @Nonnull
    @Override
    public byte[] sign(@Nonnull byte[] hash) {
        /* The HMacDSAKCalculator is what makes this signer RFC 6979 compliant. */
        ECDSASigner signer = new ECDSASigner(new RFC6979KCalculator(new SHA256Digest()));
        signer.init(true, new ECPrivateKeyParameters(privateExponent, domain));
        BigInteger[] signature = signer.generateSignature(hash);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        /* Need to canonicalize signature up front ... */
        if (CANONICALIZE && signature[1].compareTo(HALF_CURVE_ORDER) > 0) {
            /* BOP does not do this */
            signature[1] = curve.getN().subtract(signature[1]);
        }
        try {
            DERSequenceGenerator seq = new DERSequenceGenerator(stream);
            seq.addObject(new ASN1Integer(signature[0]));
            seq.addObject(new ASN1Integer(signature[1]));
            seq.close();
            return stream.toByteArray();
        } catch (IOException e) {
            throw new IllegalStateException("IOException should not be thrown", e);
        }
    }

    /**
     * Convert this instance to a string form - which happens to be the serialized WIF form.
     *
     * @return display string.
     */
    @Override
    public String toString() {
        return serializeWIF(this);
    }

    /**
     * Return true if this is equivalent to the passed in object (same type and same properties).
     *
     * @param obj
     *         instance to compare against.
     *
     * @return true if the values are equivalent, else false.
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        BouncyCastleECKeyPair that = (BouncyCastleECKeyPair) obj;
        return Objects.equal(compressed, that.compressed) && Arrays.equals(encodedPublicKey, that.encodedPublicKey)
                && Objects.equal(privateExponent, that.privateExponent);
    }

    /**
     * Returns a hash code value for the object.
     *
     * @return a hash code value for this object.
     */
    @Override
    public int hashCode() {
        return Objects.hashCode(compressed, Arrays.hashCode(encodedPublicKey), privateExponent);
    }
}