org.ethereum.vm.PrecompiledContracts Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ethereumj-core Show documentation
Show all versions of ethereumj-core Show documentation
Java implementation of the Ethereum protocol adapted to use for Hedera Smart Contract Service
The newest version!
/*
* Copyright (c) [2016] [ ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ethereumJ library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ethereumJ library. If not, see .
*/
package org.ethereum.vm;
import org.apache.commons.lang3.tuple.Pair;
import org.ethereum.config.BlockchainConfig;
import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
import org.ethereum.crypto.zksnark.*;
import org.ethereum.util.BIUtil;
import java.math.BigInteger;
import static org.ethereum.util.BIUtil.addSafely;
import static org.ethereum.util.BIUtil.isLessThan;
import static org.ethereum.util.BIUtil.isZero;
import static org.ethereum.util.ByteUtil.*;
import static org.ethereum.vm.VMUtils.getSizeInWords;
/**
* @author Roman Mandeleil
* @since 09.01.2015
*/
public class PrecompiledContracts {
private static final ECRecover ecRecover = new ECRecover();
private static final Sha256 sha256 = new Sha256();
private static final Ripempd160 ripempd160 = new Ripempd160();
private static final Identity identity = new Identity();
private static final ModExp modExp = new ModExp();
private static final BN128Addition altBN128Add = new BN128Addition();
private static final BN128Multiplication altBN128Mul = new BN128Multiplication();
private static final BN128Pairing altBN128Pairing = new BN128Pairing();
private static final DataWord ecRecoverAddr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000001");
private static final DataWord sha256Addr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000002");
private static final DataWord ripempd160Addr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000003");
private static final DataWord identityAddr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000004");
private static final DataWord modExpAddr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000005");
private static final DataWord altBN128AddAddr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000006");
private static final DataWord altBN128MulAddr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000007");
private static final DataWord altBN128PairingAddr = DataWord.of("0000000000000000000000000000000000000000000000000000000000000008");
public static PrecompiledContract getContractForAddress(DataWord address, BlockchainConfig config) {
if (address == null) return identity;
if (address.equals(ecRecoverAddr)) return ecRecover;
if (address.equals(sha256Addr)) return sha256;
if (address.equals(ripempd160Addr)) return ripempd160;
if (address.equals(identityAddr)) return identity;
// Byzantium precompiles
if (address.equals(modExpAddr) && config.eip198()) return modExp;
if (address.equals(altBN128AddAddr) && config.eip213()) return altBN128Add;
if (address.equals(altBN128MulAddr) && config.eip213()) return altBN128Mul;
if (address.equals(altBN128PairingAddr) && config.eip212()) return altBN128Pairing;
return null;
}
private static byte[] encodeRes(byte[] w1, byte[] w2) {
byte[] res = new byte[64];
w1 = stripLeadingZeroes(w1);
w2 = stripLeadingZeroes(w2);
System.arraycopy(w1, 0, res, 32 - w1.length, w1.length);
System.arraycopy(w2, 0, res, 64 - w2.length, w2.length);
return res;
}
public static abstract class PrecompiledContract {
public abstract long getGasForData(byte[] data);
public abstract Pair execute(byte[] data);
}
public static class Identity extends PrecompiledContract {
public Identity() {
}
@Override
public long getGasForData(byte[] data) {
// gas charge for the execution:
// minimum 1 and additional 1 for each 32 bytes word (round up)
if (data == null) return 15;
return 15 + getSizeInWords(data.length) * 3;
}
@Override
public Pair execute(byte[] data) {
return Pair.of(true, data);
}
}
public static class Sha256 extends PrecompiledContract {
@Override
public long getGasForData(byte[] data) {
// gas charge for the execution:
// minimum 50 and additional 50 for each 32 bytes word (round up)
if (data == null) return 60;
return 60 + getSizeInWords(data.length) * 12;
}
@Override
public Pair execute(byte[] data) {
if (data == null) return Pair.of(true, HashUtil.sha256(EMPTY_BYTE_ARRAY));
return Pair.of(true, HashUtil.sha256(data));
}
}
public static class Ripempd160 extends PrecompiledContract {
@Override
public long getGasForData(byte[] data) {
// TODO #POC9 Replace magic numbers with constants
// gas charge for the execution:
// minimum 50 and additional 50 for each 32 bytes word (round up)
if (data == null) return 600;
return 600 + getSizeInWords(data.length) * 120;
}
@Override
public Pair execute(byte[] data) {
byte[] result = null;
if (data == null) result = HashUtil.ripemd160(EMPTY_BYTE_ARRAY);
else result = HashUtil.ripemd160(data);
return Pair.of(true, DataWord.of(result).getData());
}
}
public static class ECRecover extends PrecompiledContract {
@Override
public long getGasForData(byte[] data) {
return 3000;
}
@Override
public Pair execute(byte[] data) {
byte[] h = new byte[32];
byte[] v = new byte[32];
byte[] r = new byte[32];
byte[] s = new byte[32];
DataWord out = null;
try {
System.arraycopy(data, 0, h, 0, 32);
System.arraycopy(data, 32, v, 0, 32);
System.arraycopy(data, 64, r, 0, 32);
int sLength = data.length < 128 ? data.length - 96 : 32;
System.arraycopy(data, 96, s, 0, sLength);
ECKey.ECDSASignature signature = ECKey.ECDSASignature.fromComponents(r, s, v[31]);
if (validateV(v) && signature.validateComponents()) {
out = DataWord.of(ECKey.signatureToAddress(h, signature));
}
} catch (Throwable any) {
}
if (out == null) {
return Pair.of(true, EMPTY_BYTE_ARRAY);
} else {
return Pair.of(true, out.getData());
}
}
private static boolean validateV(byte[] v) {
for (int i = 0; i < v.length - 1; i++) {
if (v[i] != 0) return false;
}
return true;
}
}
/**
* Computes modular exponentiation on big numbers
*
* format of data[] array:
* [length_of_BASE] [length_of_EXPONENT] [length_of_MODULUS] [BASE] [EXPONENT] [MODULUS]
* where every length is a 32-byte left-padded integer representing the number of bytes.
* Call data is assumed to be infinitely right-padded with zero bytes.
*
* Returns an output as a byte array with the same length as the modulus
*/
public static class ModExp extends PrecompiledContract {
private static final BigInteger GQUAD_DIVISOR = BigInteger.valueOf(20);
private static final int ARGS_OFFSET = 32 * 3; // addresses length part
@Override
public long getGasForData(byte[] data) {
if (data == null) data = EMPTY_BYTE_ARRAY;
int baseLen = parseLen(data, 0);
int expLen = parseLen(data, 1);
int modLen = parseLen(data, 2);
byte[] expHighBytes = parseBytes(data, addSafely(ARGS_OFFSET, baseLen), Math.min(expLen, 32));
long multComplexity = getMultComplexity(Math.max(baseLen, modLen));
long adjExpLen = getAdjustedExponentLength(expHighBytes, expLen);
// use big numbers to stay safe in case of overflow
BigInteger gas = BigInteger.valueOf(multComplexity)
.multiply(BigInteger.valueOf(Math.max(adjExpLen, 1)))
.divide(GQUAD_DIVISOR);
return isLessThan(gas, BigInteger.valueOf(Long.MAX_VALUE)) ? gas.longValue() : Long.MAX_VALUE;
}
@Override
public Pair execute(byte[] data) {
if (data == null)
return Pair.of(true, EMPTY_BYTE_ARRAY);
int baseLen = parseLen(data, 0);
int expLen = parseLen(data, 1);
int modLen = parseLen(data, 2);
BigInteger base = parseArg(data, ARGS_OFFSET, baseLen);
BigInteger exp = parseArg(data, addSafely(ARGS_OFFSET, baseLen), expLen);
BigInteger mod = parseArg(data, addSafely(addSafely(ARGS_OFFSET, baseLen), expLen), modLen);
// check if modulus is zero
if (isZero(mod))
return Pair.of(true, new byte[modLen]); // should keep length of the result
byte[] res = stripLeadingZeroes(base.modPow(exp, mod).toByteArray());
// adjust result to the same length as the modulus has
if (res.length < modLen) {
byte[] adjRes = new byte[modLen];
System.arraycopy(res, 0, adjRes, modLen - res.length, res.length);
return Pair.of(true, adjRes);
} else {
return Pair.of(true, res);
}
}
private long getMultComplexity(long x) {
long x2 = x * x;
if (x <= 64) return x2;
if (x <= 1024) return x2 / 4 + 96 * x - 3072;
return x2 / 16 + 480 * x - 199680;
}
private long getAdjustedExponentLength(byte[] expHighBytes, long expLen) {
int leadingZeros = numberOfLeadingZeros(expHighBytes);
int highestBit = 8 * expHighBytes.length - leadingZeros;
// set index basement to zero
if (highestBit > 0) highestBit--;
if (expLen <= 32) {
return highestBit;
} else {
return 8 * (expLen - 32) + highestBit;
}
}
private int parseLen(byte[] data, int idx) {
byte[] bytes = parseBytes(data, 32 * idx, 32);
return DataWord.of(bytes).intValueSafe();
}
private BigInteger parseArg(byte[] data, int offset, int len) {
byte[] bytes = parseBytes(data, offset, len);
return bytesToBigInteger(bytes);
}
}
/**
* Computes point addition on Barreto–Naehrig curve.
* See {@link BN128Fp} for details
*
*
* input data[]:
* two points encoded as (x, y), where x and y are 32-byte left-padded integers,
* if input is shorter than expected, it's assumed to be right-padded with zero bytes
*
*
* output:
* resulting point (x', y'), where x and y encoded as 32-byte left-padded integers
*
*/
public static class BN128Addition extends PrecompiledContract {
@Override
public long getGasForData(byte[] data) {
return 500;
}
@Override
public Pair execute(byte[] data) {
if (data == null)
data = EMPTY_BYTE_ARRAY;
byte[] x1 = parseWord(data, 0);
byte[] y1 = parseWord(data, 1);
byte[] x2 = parseWord(data, 2);
byte[] y2 = parseWord(data, 3);
BN128 p1 = BN128Fp.create(x1, y1);
if (p1 == null)
return Pair.of(false, EMPTY_BYTE_ARRAY);
BN128 p2 = BN128Fp.create(x2, y2);
if (p2 == null)
return Pair.of(false, EMPTY_BYTE_ARRAY);
BN128 res = p1.add(p2).toEthNotation();
return Pair.of(true, encodeRes(res.x().bytes(), res.y().bytes()));
}
}
/**
* Computes multiplication of scalar value on a point belonging to Barreto–Naehrig curve.
* See {@link BN128Fp} for details
*
*
* input data[]:
* point encoded as (x, y) is followed by scalar s, where x, y and s are 32-byte left-padded integers,
* if input is shorter than expected, it's assumed to be right-padded with zero bytes
*
*
* output:
* resulting point (x', y'), where x and y encoded as 32-byte left-padded integers
*
*/
public static class BN128Multiplication extends PrecompiledContract {
@Override
public long getGasForData(byte[] data) {
return 40000;
}
@Override
public Pair execute(byte[] data) {
if (data == null)
data = EMPTY_BYTE_ARRAY;
byte[] x = parseWord(data, 0);
byte[] y = parseWord(data, 1);
byte[] s = parseWord(data, 2);
BN128 p = BN128Fp.create(x, y);
if (p == null)
return Pair.of(false, EMPTY_BYTE_ARRAY);
BN128 res = p.mul(BIUtil.toBI(s)).toEthNotation();
return Pair.of(true, encodeRes(res.x().bytes(), res.y().bytes()));
}
}
/**
* Computes pairing check.
* See {@link PairingCheck} for details.
*
*
* Input data[]:
* an array of points (a1, b1, ... , ak, bk),
* where "ai" is a point of {@link BN128Fp} curve and encoded as two 32-byte left-padded integers (x; y)
* "bi" is a point of {@link BN128G2} curve and encoded as four 32-byte left-padded integers {@code (ai + b; ci + d)},
* each coordinate of the point is a big-endian {@link Fp2} number, so {@code b} precedes {@code a} in the encoding:
* {@code (b, a; d, c)}
* thus each pair (ai, bi) has 192 bytes length, if 192 is not a multiple of {@code data.length} then execution fails
* the number of pairs is derived from input length by dividing it by 192 (the length of a pair)
*
*
* output:
* pairing product which is either 0 or 1, encoded as 32-byte left-padded integer
*
*/
public static class BN128Pairing extends PrecompiledContract {
private static final int PAIR_SIZE = 192;
@Override
public long getGasForData(byte[] data) {
if (data == null) return 100000;
return 80000 * (data.length / PAIR_SIZE) + 100000;
}
@Override
public Pair execute(byte[] data) {
if (data == null)
data = EMPTY_BYTE_ARRAY;
// fail if input len is not a multiple of PAIR_SIZE
if (data.length % PAIR_SIZE > 0)
return Pair.of(false, EMPTY_BYTE_ARRAY);
PairingCheck check = PairingCheck.create();
// iterating over all pairs
for (int offset = 0; offset < data.length; offset += PAIR_SIZE) {
Pair pair = decodePair(data, offset);
// fail if decoding has failed
if (pair == null)
return Pair.of(false, EMPTY_BYTE_ARRAY);
check.addPair(pair.getLeft(), pair.getRight());
}
check.run();
int result = check.result();
return Pair.of(true, DataWord.of(result).getData());
}
private Pair decodePair(byte[] in, int offset) {
byte[] x = parseWord(in, offset, 0);
byte[] y = parseWord(in, offset, 1);
BN128G1 p1 = BN128G1.create(x, y);
// fail if point is invalid
if (p1 == null) return null;
// (b, a)
byte[] b = parseWord(in, offset, 2);
byte[] a = parseWord(in, offset, 3);
// (d, c)
byte[] d = parseWord(in, offset, 4);
byte[] c = parseWord(in, offset, 5);
BN128G2 p2 = BN128G2.create(a, b, c, d);
// fail if point is invalid
if (p2 == null) return null;
return Pair.of(p1, p2);
}
}
}