eu.bittrade.libs.steemj.protocol.PublicKey.java Source code

Java tutorial

Introduction

Here is the source code for eu.bittrade.libs.steemj.protocol.PublicKey.java

Source

/*
 *     This file is part of SteemJ (formerly known as 'Steem-Java-Api-Wrapper')
 * 
 *     SteemJ is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     SteemJ 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 General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */
package eu.bittrade.libs.steemj.protocol;

import java.util.Arrays;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.digests.RIPEMD160Digest;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.primitives.Bytes;

import eu.bittrade.crypto.core.AddressFormatException;
import eu.bittrade.crypto.core.ECKey;
import eu.bittrade.crypto.core.base58.Base58;
import eu.bittrade.libs.steemj.base.models.serializer.PublicKeySerializer;
import eu.bittrade.libs.steemj.configuration.SteemJConfig;
import eu.bittrade.libs.steemj.exceptions.SteemInvalidTransactionException;
import eu.bittrade.libs.steemj.interfaces.ByteTransformable;

/**
 * This class is the java implementation of the <a href=
 * "https://github.com/steemit/steem/blob/master/libraries/protocol/include/steemit/protocol/types.hpp">Steem
 * public_key object</a>.
 * 
 * @author <a href="http://steemit.com/@dez1337">dez1337</a>
 */
@JsonSerialize(using = PublicKeySerializer.class)
public class PublicKey implements ByteTransformable {
    private static final Logger LOGGER = LoggerFactory.getLogger(PublicKey.class);

    private static final int CHECKSUM_BYTES = 4;

    private ECKey publicKey;
    private String prefix;

    /**
     * Create a new public key by providing an address as String.
     * 
     * @param address
     *            The address in its String representation.
     *            <p>
     *            Example: <br>
     *            STM5jYVokmZHdEpwo5oCG3ES2Ca4VYzy6tM8pWWkGdgVnwo2mFLFq
     *            </p>
     * @throws AddressFormatException
     *             If the input is not base 58 or the checksum does not
     *             validate.
     */
    @JsonCreator
    public PublicKey(String address) {
        // As this method is also used for parsing different operations where
        // the field could be empty we sadly have to handle "null" cases here.
        if (address != null && !"".equals(address)) {
            if (address.length() != 53) {
                LOGGER.warn("The provided address '{}' has an invalid length and will not be set.", address);
                this.setPublicKey(null);
            } else {
                // We expect the first three chars to be the prefix (STM). The
                // rest
                // of the String contains the base58 encoded public key and its
                // checksum.
                this.prefix = address.substring(0, 3);
                byte[] decodedAddress = Base58.decode(address.substring(3, address.length()));
                // As sha256 is used for Bitcoin and ripemd160 for Steem, we
                // can't
                // use Bitcoinjs Base58.decodeChecked here and have to do all
                // stuff
                // on our own.
                byte[] potentialPublicKey = Arrays.copyOfRange(decodedAddress, 0,
                        decodedAddress.length - CHECKSUM_BYTES);
                byte[] expectedChecksum = Arrays.copyOfRange(decodedAddress, decodedAddress.length - CHECKSUM_BYTES,
                        decodedAddress.length);

                byte[] actualChecksum = calculateChecksum(potentialPublicKey);

                // And compare them.
                for (int i = 0; i < expectedChecksum.length; i++) {
                    if (expectedChecksum[i] != actualChecksum[i]) {
                        throw new AddressFormatException("Checksum does not match.");
                    }
                }

                this.setPublicKey(ECKey.fromPublicOnly(potentialPublicKey));
            }
        } else {
            LOGGER.warn(
                    "An empty address has been provided. This can cause some problems if you plan to broadcast this key.");
            this.setPublicKey(null);
        }
    }

    /**
     * Generate the actual checksum of a Steem public key.
     * 
     * @param publicKey
     *            The public key.
     * @return The actual checksum of a Steem public key.
     */
    private byte[] calculateChecksum(byte[] publicKey) {
        RIPEMD160Digest ripemd160Digest = new RIPEMD160Digest();
        ripemd160Digest.update(publicKey, 0, publicKey.length);
        byte[] actualChecksum = new byte[ripemd160Digest.getDigestSize()];
        ripemd160Digest.doFinal(actualChecksum, 0);
        return actualChecksum;
    }

    /**
     * Create a new public key by providing a ECKey object containing the public
     * key.
     * 
     * @param publicKey
     *            The public key.
     */
    public PublicKey(ECKey publicKey) {
        this.setPublicKey(publicKey);
        this.prefix = SteemJConfig.getInstance().getAddressPrefix().toString().toUpperCase();
    }

    /**
     * Recreate the address from the public key.
     * 
     * @return The address.
     */
    @JsonIgnore
    public String getAddressFromPublicKey() {
        try {
            // Recreate the address from the public key.
            return this.prefix + Base58.encode(Bytes.concat(this.toByteArray(),
                    Arrays.copyOfRange(calculateChecksum(this.toByteArray()), 0, CHECKSUM_BYTES)));
        } catch (SteemInvalidTransactionException | NullPointerException e) {
            LOGGER.debug("An error occured while generating an address from a public key.", e);
            return "";
        }
    }

    /**
     * Get the public key stored in this object.
     * 
     * @return The public key.
     */
    @JsonIgnore()
    public ECKey getPublicKey() {
        return publicKey;
    }

    /**
     * Set the public key that should be stored in this object.
     * 
     * @param publicKey
     *            The public key.
     */
    private void setPublicKey(ECKey publicKey) {
        this.publicKey = publicKey;
    }

    @Override
    public byte[] toByteArray() throws SteemInvalidTransactionException {
        if (this.getPublicKey().isCompressed()) {
            return this.getPublicKey().getPubKey();
        } else {
            return ECKey.fromPublicOnly(ECKey.compressPoint(this.getPublicKey().getPubKeyPoint())).getPubKey();
        }
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

    @Override
    public boolean equals(Object otherPublicKey) {
        if (this == otherPublicKey)
            return true;
        if (otherPublicKey == null || !(otherPublicKey instanceof PublicKey))
            return false;
        PublicKey otherKey = (PublicKey) otherPublicKey;
        return this.getPublicKey().equals(otherKey.getPublicKey());
    }

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