org.hyperledger.common.PublicKey.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperledger.common.PublicKey.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.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;

import java.io.IOException;
import java.math.BigInteger;

/**
 * An EC public Key suitable for verifying a signature created with the corresponding EC PrivateKey
 *
 * @see PrivateKey
 */
public class PublicKey implements Key {
    private final byte[] pub;
    private final boolean compressed;

    public static PublicKey decode(byte[] bytes) throws HyperLedgerException {
        if (bytes.length == 0)
            throw new HyperLedgerException("Invalid public key");

        try {
            byte type = bytes[0];
            boolean compressed = type == 0x02 || type == 0x03;

            ECPoint p = curve.getCurve().decodePoint(bytes);
            return new PublicKey(p.getEncoded(compressed), compressed);
        } catch (IllegalArgumentException e) {
            throw new HyperLedgerException("Invalid public key", e);
        }
    }

    /**
     * Create a public key from its binary representation and telling if it is compressed
     *
     * @param pub        EC point coordinates encoded in a byte array
     * @param compressed true for modern, false for legacy encoding
     */
    public PublicKey(byte[] pub, boolean compressed) {
        this.pub = pub;
        this.compressed = compressed;
    }

    @Override
    public boolean isCompressed() {
        return compressed;
    }

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

    /**
     * Get the TransactionOutput script for legacy pay-to-key. Use COMMON or P2SH addresses instead.
     *
     * @return a transaction output script spendable with the private key for this key
     * @see Address
     */
    public Script getP2KeyScript() {
        return Script.create().payToPublicKey(new PublicKey(pub, compressed)).build();
    }

    /**
     * Get the Address using legacy pay-to-key format. Use COMMON or P2SH addresses instead.
     *
     * @return an address spendable with the private key for this key
     */
    public Address getP2KeyAddress() {
        try {
            return new LegacyAddress(this);
        } catch (HyperLedgerException e) {
            return null;
        }
    }

    @Override
    public byte[] toByteArray() {
        return Arrays.clone(pub);
    }

    /**
     * verify a signature created with the private counterpart of this key
     *
     * @param hash      arbitrary data
     * @param signature signature
     * @return true if valid
     */
    public boolean verify(byte[] hash, byte[] signature) {
        return verify(hash, signature, pub);
    }

    /**
     * verify a signature
     *
     * @param hash      arbitrary data
     * @param signature signature
     * @param pub       public key in binary representation
     * @return true if signature is valid for the key and data
     */
    public static boolean verify(byte[] hash, byte[] signature, byte[] pub) {
        ASN1InputStream asn1 = new ASN1InputStream(signature);
        try {
            ECDSASigner signer = new ECDSASigner();
            signer.init(false, new ECPublicKeyParameters(curve.getCurve().decodePoint(pub), domain));

            DLSequence seq = (DLSequence) asn1.readObject();
            BigInteger r = ((ASN1Integer) seq.getObjectAt(0)).getPositiveValue();
            BigInteger s = ((ASN1Integer) seq.getObjectAt(1)).getPositiveValue();
            return signer.verifySignature(hash, r, s);
        } catch (Exception e) {
            // treat format errors as invalid signatures
            return false;
        } finally {
            try {
                asn1.close();
            } catch (IOException e) {
            }
        }
    }

    public PublicKey offsetKey(BigInteger offset) throws HyperLedgerException {
        boolean invert = false;
        if (offset.compareTo(BigInteger.ZERO) < 0) {
            invert = true;
            offset = offset.abs();
        }
        ECPoint oG = curve.getG().multiply(offset);
        if (invert) {
            oG = oG.negate();
        }
        ECPoint q = oG.add(curve.getCurve().decodePoint(pub));
        if (q.isInfinity()) {
            throw new HyperLedgerException("This is rather unlikely, but it did just happen");
        }
        return new PublicKey(q.getEncoded(compressed), compressed);
    }

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

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof PublicKey) {
            return Arrays.areEqual(pub, ((PublicKey) obj).pub) && compressed == ((PublicKey) obj).compressed;
        }
        return false;
    }

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