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

org.tron.trident.crypto.SECP256K1 Maven / Gradle / Ivy

/*
 * Copyright ConsenSys AG.
 *
 * 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.
 *
 * SPDX-License-Identifier: Apache-2.0
 */
package org.tron.trident.crypto;

import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPairGenerator;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.function.UnaryOperator;

import org.tron.trident.crypto.tuwenitypes.MutableBytes;
import org.tron.trident.crypto.tuwenitypes.Bytes;
import org.tron.trident.crypto.tuwenitypes.Bytes32;
import org.tron.trident.crypto.tuwenitypes.UInt256;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9IntegerConverter;
import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
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 org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.FixedPointCombMultiplier;
import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;

/*
 * Adapted from the BitcoinJ ECKey (Apache 2 License) implementation:
 * https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/core/ECKey.java
 *
 *
 * Adapted from the web3j (Apache 2 License) implementations:
 * https://github.com/web3j/web3j/crypto/src/main/java/org/web3j/crypto/*.java
 */
public class SECP256K1 {
    public static final String ALGORITHM = "ECDSA";
    public static final String CURVE_NAME = "secp256k1";
    public static final String PROVIDER = "BC";

    public static final ECDomainParameters CURVE;
    public static final BigInteger HALF_CURVE_ORDER;

    private static final KeyPairGenerator KEY_PAIR_GENERATOR;
    private static final BigInteger CURVE_ORDER;

    static {
        //support android platform
        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
        Security.insertProviderAt(new BouncyCastleProvider(), 1);

        final X9ECParameters params = SECNamedCurves.getByName(CURVE_NAME);
        CURVE = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
        CURVE_ORDER = CURVE.getN();
        HALF_CURVE_ORDER = CURVE_ORDER.shiftRight(1);
        try {
            KEY_PAIR_GENERATOR = KeyPairGenerator.getInstance(ALGORITHM, PROVIDER);
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
        final ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(CURVE_NAME);
        try {
            KEY_PAIR_GENERATOR.initialize(ecGenParameterSpec);
        } catch (final InvalidAlgorithmParameterException e) {
            throw new RuntimeException(e);
        }
    }

    public static Signature sign(final Bytes32 dataHash, final KeyPair keyPair) {
        return signDefault(dataHash, keyPair);
    }

    /**
     * Verifies the given ECDSA signature against the message bytes using the public key bytes.
     *
     * 

When using native ECDSA verification, data must be 32 bytes, and no element may be larger * than 520 bytes. * * @param data Hash of the data to verify. * @param signature ASN.1 encoded signature. * @param pub The public key bytes to use. * @return True if the verification is successful. */ public static boolean verify(final Bytes data, final Signature signature, final PublicKey pub) { return verifyDefault(data, signature, pub); } /** Decompress a compressed public key (x co-ord and low-bit of y-coord). */ private static ECPoint decompressKey(final BigInteger xBN, final boolean yBit) { final X9IntegerConverter x9 = new X9IntegerConverter(); final byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(CURVE.getCurve())); compEnc[0] = (byte) (yBit ? 0x03 : 0x02); // TODO: Find a better way to handle an invalid point compression here. // Currently ECCurve#decodePoint throws an IllegalArgumentException. return CURVE.getCurve().decodePoint(compEnc); } /** * Given the components of a signature and a selector value, recover and return the public key * that generated the signature according to the algorithm in SEC1v2 section 4.1.6. * *

If this method returns null it means recovery was not possible and recId should be iterated. * *

Given the above two points, a correct usage of this method is inside a for loop from 0 to 3, * and if the output is null OR a key that is not the one you expect, you try again with the next * recId. * * @param recId Which possible key to recover. * @param r The R component of the signature. * @param s The S component of the signature. * @param dataHash Hash of the data that was signed. * @return An ECKey containing only the public part, or null if recovery wasn't possible. */ private static BigInteger recoverFromSignature( final int recId, final BigInteger r, final BigInteger s, final Bytes32 dataHash) { assert (recId >= 0); assert (r.signum() >= 0); assert (s.signum() >= 0); assert (dataHash != null); // 1.0 For j from 0 to h (h == recId here and the loop is outside this function) // 1.1 Let x = r + jn final BigInteger n = CURVE.getN(); // Curve order. final BigInteger i = BigInteger.valueOf((long) recId / 2); final BigInteger x = r.add(i.multiply(n)); // 1.2. Convert the integer x to an octet string X of length mlen using the conversion // routine specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉. // 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R // using the conversion routine specified in Section 2.3.4. If this conversion // routine outputs "invalid", then do another iteration of Step 1. // // More concisely, what these points mean is to use X as a compressed public key. final BigInteger prime = SecP256K1Curve.q; if (x.compareTo(prime) >= 0) { // Cannot have point co-ordinates larger than this as everything takes place modulo Q. return null; } // Compressed keys require you to know an extra bit of data about the y-coord as there are // two possibilities. So it's encoded in the recId. final ECPoint R = decompressKey(x, (recId & 1) == 1); // 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers // responsibility). if (!R.multiply(n).isInfinity()) { return null; } // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification. final BigInteger e = dataHash.toUnsignedBigInteger(); // 1.6. For k from 1 to 2 do the following. (loop is outside this function via // iterating recId) // 1.6.1. Compute a candidate public key as: // Q = mi(r) * (sR - eG) // // Where mi(x) is the modular multiplicative inverse. We transform this into the following: // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). // In the above equation ** is point multiplication and + is point addition (the EC group // operator). // // We can find the additive inverse by subtracting e from zero then taking the mod. For // example the additive inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and // -3 mod 11 = 8. final BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); final BigInteger rInv = r.modInverse(n); final BigInteger srInv = rInv.multiply(s).mod(n); final BigInteger eInvrInv = rInv.multiply(eInv).mod(n); final ECPoint q = ECAlgorithms.sumOfTwoMultiplies(CURVE.getG(), eInvrInv, R, srInv); if (q.isInfinity()) { return null; } final byte[] qBytes = q.getEncoded(false); // We remove the prefix return new BigInteger(1, Arrays.copyOfRange(qBytes, 1, qBytes.length)); } private static Signature signDefault(final Bytes32 dataHash, final KeyPair keyPair) { final ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); final ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(keyPair.getPrivateKey().getEncodedBytes().toUnsignedBigInteger(), CURVE); signer.init(true, privKey); final BigInteger[] components = signer.generateSignature(dataHash.toArrayUnsafe()); return normaliseSignature(components[0], components[1], keyPair.getPublicKey(), dataHash); } public static Signature normaliseSignature( final BigInteger nativeR, final BigInteger nativeS, final PublicKey publicKey, final Bytes32 dataHash) { BigInteger s = nativeS; // Automatically adjust the S component to be less than or equal to half the curve // order, if necessary. This is required because for every signature (r,s) the signature // (r, -s (mod N)) is a valid signature of the same message. However, we dislike the // ability to modify the bits of a Bitcoin transaction after it's been signed, as that // violates various assumed invariants. Thus in future only one of those forms will be // considered legal and the other will be banned. if (s.compareTo(HALF_CURVE_ORDER) > 0) { // The order of the curve is the number of valid points that exist on that curve. // If S is in the upper half of the number of valid points, then bring it back to // the lower half. Otherwise, imagine that // N = 10 // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions. // 10 - 8 == 2, giving us always the latter solution, which is canonical. s = CURVE.getN().subtract(s); } // Now we have to work backwards to figure out the recId needed to recover the signature. int recId = -1; final BigInteger publicKeyBI = publicKey.getEncodedBytes().toUnsignedBigInteger(); for (int i = 0; i < 4; i++) { final BigInteger k = recoverFromSignature(i, nativeR, s, dataHash); if (k != null && k.equals(publicKeyBI)) { recId = i; break; } } if (recId == -1) { throw new RuntimeException("Could not construct a recoverable key. This should never happen."); } return new Signature(nativeR, s, (byte) recId); } private static boolean verifyDefault(final Bytes data, final Signature signature, final PublicKey pub) { final ECDSASigner signer = new ECDSASigner(); final Bytes toDecode = Bytes.wrap(Bytes.of((byte) 4), pub.getEncodedBytes()); final ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(toDecode.toArrayUnsafe()), CURVE); signer.init(false, params); try { return signer.verifySignature(data.toArrayUnsafe(), signature.r, signature.s); } catch (final NullPointerException e) { // Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. Those // signatures // are inherently invalid/attack sigs so we just fail them here rather than crash the thread. return false; } } /** * Verifies the given ECDSA signature using the public key bytes against the message bytes, * previously passed through a preprocessor function, which is normally a hashing function. * * @param data The data to verify. * @param signature ASN.1 encoded signature. * @param pub The public key bytes to use. * @param preprocessor The function to apply to the data before verifying the signature, normally * a hashing function. * @return True if the verification is successful. */ public static boolean verify( final Bytes data, final Signature signature, final PublicKey pub, final UnaryOperator preprocessor) { assert preprocessor != null : "preprocessor must not be null"; return verify(preprocessor.apply(data), signature, pub); } /** * Calculates an ECDH key agreement between the private and the public key. * * @param privKey The private key. * @param theirPubKey The public key. * @return The agreed secret. */ public static Bytes32 calculateECDHKeyAgreement(final PrivateKey privKey, final PublicKey theirPubKey) { assert privKey != null : "missing private key"; assert theirPubKey != null : "missing remote public key"; final ECPrivateKeyParameters privKeyP = new ECPrivateKeyParameters(privKey.getD(), CURVE); final ECPublicKeyParameters pubKeyP = new ECPublicKeyParameters(theirPubKey.asEcPoint(), CURVE); final ECDHBasicAgreement agreement = new ECDHBasicAgreement(); agreement.init(privKeyP); final BigInteger agreed = agreement.calculateAgreement(pubKeyP); return UInt256.valueOf(agreed).toBytes(); } public static class PrivateKey implements java.security.PrivateKey { private final Bytes32 encoded; private PrivateKey(final Bytes32 encoded) { assert encoded != null; this.encoded = encoded; } public static PrivateKey create(final BigInteger key) { assert key != null; return create(UInt256.valueOf(key).toBytes()); } public static PrivateKey create(final Bytes32 key) { return new PrivateKey(key); } public static PrivateKey create(final String hexKey) { assert hexKey.length() == 64; return create(Bytes32.fromHexString(hexKey)); } @Override public boolean equals(final Object other) { if (!(other instanceof PrivateKey)) { return false; } final PrivateKey that = (PrivateKey) other; return this.encoded.equals(that.encoded); } @Override public byte[] getEncoded() { return encoded.toArrayUnsafe(); } public Bytes32 getEncodedBytes() { return encoded; } public BigInteger getD() { return encoded.toUnsignedBigInteger(); } @Override public String getAlgorithm() { return ALGORITHM; } @Override public String getFormat() { return null; } @Override public int hashCode() { return encoded.hashCode(); } @Override public String toString() { return encoded.toString(); } } public static class PublicKey implements java.security.PublicKey { private static final int BYTE_LENGTH = 64; private final Bytes encoded; public static PublicKey create(final PrivateKey privateKey) { BigInteger privKey = privateKey.getEncodedBytes().toUnsignedBigInteger(); /* * TODO: FixedPointCombMultiplier currently doesn't support scalars longer than the group * order, but that could change in future versions. */ if (privKey.bitLength() > CURVE.getN().bitLength()) { privKey = privKey.mod(CURVE.getN()); } final ECPoint point = new FixedPointCombMultiplier().multiply(CURVE.getG(), privKey); return PublicKey.create(Bytes.wrap(Arrays.copyOfRange(point.getEncoded(false), 1, 65))); } private static Bytes toBytes64(final byte[] backing) { if (backing.length == BYTE_LENGTH) { return Bytes.wrap(backing); } else if (backing.length > BYTE_LENGTH) { return Bytes.wrap(backing, backing.length - BYTE_LENGTH, BYTE_LENGTH); } else { final MutableBytes res = MutableBytes.create(BYTE_LENGTH); Bytes.wrap(backing).copyTo(res, BYTE_LENGTH - backing.length); return res; } } public static PublicKey create(final BigInteger key) { assert key != null; return create(toBytes64(key.toByteArray())); } public static PublicKey create(final Bytes encoded) { return new PublicKey(encoded); } public static Optional recoverFromSignature(final Bytes32 dataHash, final Signature signature) { final BigInteger publicKeyBI = SECP256K1.recoverFromSignature(signature.getRecId(), signature.getR(), signature.getS(), dataHash); return Optional.ofNullable(publicKeyBI).map(PublicKey::create); } private PublicKey(final Bytes encoded) { assert encoded != null; assert encoded.size() == BYTE_LENGTH; this.encoded = encoded; } /** * Returns this public key as an {@link ECPoint} of Bouncy Castle, to facilitate cryptographic * operations. * * @return This public key represented as an Elliptic Curve point. */ public ECPoint asEcPoint() { // 0x04 is the prefix for uncompressed keys. final Bytes val = Bytes.concatenate(Bytes.of(0x04), encoded); return CURVE.getCurve().decodePoint(val.toArrayUnsafe()); } @Override public boolean equals(final Object other) { if (!(other instanceof PublicKey)) { return false; } final PublicKey that = (PublicKey) other; return this.encoded.equals(that.encoded); } @Override public byte[] getEncoded() { return encoded.toArrayUnsafe(); } public Bytes getEncodedBytes() { return encoded; } @Override public String getAlgorithm() { return ALGORITHM; } @Override public String getFormat() { return null; } @Override public int hashCode() { return encoded.hashCode(); } @Override public String toString() { return encoded.toString(); } } public static class KeyPair { private final PrivateKey privateKey; private final PublicKey publicKey; public KeyPair(final PrivateKey privateKey, final PublicKey publicKey) { assert privateKey != null; assert publicKey != null; this.privateKey = privateKey; this.publicKey = publicKey; } public static KeyPair create(final PrivateKey privateKey) { return new KeyPair(privateKey, PublicKey.create(privateKey)); } public static KeyPair generate() { final java.security.KeyPair rawKeyPair = KEY_PAIR_GENERATOR.generateKeyPair(); final BCECPrivateKey privateKey = (BCECPrivateKey) rawKeyPair.getPrivate(); final BCECPublicKey publicKey = (BCECPublicKey) rawKeyPair.getPublic(); final BigInteger privateKeyValue = privateKey.getD(); // Ethereum does not use encoded public keys like bitcoin - see // https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm for details // Additionally, as the first bit is a constant prefix (0x04) we ignore this value final byte[] publicKeyBytes = publicKey.getQ().getEncoded(false); final BigInteger publicKeyValue = new BigInteger(1, Arrays.copyOfRange(publicKeyBytes, 1, publicKeyBytes.length)); return new KeyPair(PrivateKey.create(privateKeyValue), PublicKey.create(publicKeyValue)); } @Override public int hashCode() { return Objects.hash(privateKey, publicKey); } @Override public boolean equals(final Object other) { if (!(other instanceof KeyPair)) { return false; } final KeyPair that = (KeyPair) other; return this.privateKey.equals(that.privateKey) && this.publicKey.equals(that.publicKey); } public PrivateKey getPrivateKey() { return privateKey; } public PublicKey getPublicKey() { return publicKey; } } public static class Signature { public static final int BYTES_REQUIRED = 65; /** * The recovery id to reconstruct the public key used to create the signature. * *

The recId is an index from 0 to 3 which indicates which of the 4 possible keys is the * correct one. Because the key recovery operation yields multiple potential keys, the correct * key must either be stored alongside the signature, or you must be willing to try each recId * in turn until you find one that outputs the key you are expecting. */ private final byte recId; private final BigInteger r; private final BigInteger s; Signature(final BigInteger r, final BigInteger s, final byte recId) { this.r = r; this.s = s; this.recId = recId; } /** * Creates a new signature object given its parameters. * * @param r the 'r' part of the signature. * @param s the 's' part of the signature. * @param recId the recovery id part of the signature. * @return the created {@link Signature} object. * @throws NullPointerException if {@code r} or {@code s} are {@code null}. * @throws IllegalArgumentException if any argument is invalid (for instance, {@code v} is * neither 27 or 28). */ public static Signature create(final BigInteger r, final BigInteger s, final byte recId) { assert r != null; assert s != null; checkInBounds("r", r); checkInBounds("s", s); if (recId != 0 && recId != 1) { throw new IllegalArgumentException("Invalid 'recId' value, should be 0 or 1 but got " + recId); } return new Signature(r, s, recId); } private static void checkInBounds(final String name, final BigInteger i) { if (i.compareTo(BigInteger.ONE) < 0) { throw new IllegalArgumentException( String.format("Invalid '%s' value, should be >= 1 but got %s", name, i)); } if (i.compareTo(CURVE_ORDER) >= 0) { throw new IllegalArgumentException( String.format("Invalid '%s' value, should be < %s but got %s", CURVE_ORDER, name, i)); } } public static Signature decode(final Bytes bytes) { assert bytes.size() == BYTES_REQUIRED : "encoded SECP256K1 signature must be 65 bytes long"; final BigInteger r = bytes.slice(0, 32).toUnsignedBigInteger(); final BigInteger s = bytes.slice(32, 32).toUnsignedBigInteger(); final byte recId = bytes.get(64); return SECP256K1.Signature.create(r, s, recId); } public Bytes encodedBytes() { final MutableBytes bytes = MutableBytes.create(BYTES_REQUIRED); UInt256.valueOf(r).toBytes().copyTo(bytes, 0); UInt256.valueOf(s).toBytes().copyTo(bytes, 32); bytes.set(64, recId); return bytes; } @Override public boolean equals(final Object other) { if (!(other instanceof Signature)) { return false; } final Signature that = (Signature) other; return this.r.equals(that.r) && this.s.equals(that.s) && this.recId == that.recId; } @Override public int hashCode() { return Objects.hash(r, s, recId); } public byte getRecId() { return recId; } public BigInteger getR() { return r; } public BigInteger getS() { return s; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("SECP256K1.Signature").append("{"); sb.append("r=").append(r).append(", "); sb.append("s=").append(s).append(", "); sb.append("recId=").append(recId); return sb.append("}").toString(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy