From 1d5e2ded775eac32a4a150cdf76c13b1d2bbf72b Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Mon, 28 Aug 2017 14:55:01 +0600 Subject: [PATCH 1/5] EIP-196 precompiles for point addition and scalar multiplication on BN128 curve --- .../org/ethereum/config/BlockchainConfig.java | 5 + .../config/blockchain/AbstractConfig.java | 5 + .../config/blockchain/ByzantiumConfig.java | 5 + .../config/blockchain/Eip150HFConfig.java | 5 + .../java/org/ethereum/crypto/bn/BN128.java | 235 ++++++++++++++++++ .../main/java/org/ethereum/util/ByteUtil.java | 25 ++ .../org/ethereum/vm/PrecompiledContracts.java | 119 ++++++++- 7 files changed, 393 insertions(+), 6 deletions(-) create mode 100644 ethereumj-core/src/main/java/org/ethereum/crypto/bn/BN128.java diff --git a/ethereumj-core/src/main/java/org/ethereum/config/BlockchainConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/BlockchainConfig.java index 92e14c98eb..e381ae6d74 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/BlockchainConfig.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/BlockchainConfig.java @@ -143,6 +143,11 @@ String validateTransactionChanges(BlockStore blockStore, Block curBlock, Transac */ boolean eip211(); + /** + * EIP213: https://github.com/ethereum/EIPs/pull/213 + */ + boolean eip213(); + /** * EIP214: https://github.com/ethereum/EIPs/pull/214 */ diff --git a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/AbstractConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/AbstractConfig.java index e0a88e4b7a..d1333c8680 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/AbstractConfig.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/AbstractConfig.java @@ -173,6 +173,11 @@ public boolean eip211() { return false; } + @Override + public boolean eip213() { + return false; + } + @Override public boolean eip214() { return false; diff --git a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ByzantiumConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ByzantiumConfig.java index c94f272798..5a7a247c38 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ByzantiumConfig.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/ByzantiumConfig.java @@ -78,6 +78,11 @@ public boolean eip211() { return true; } + @Override + public boolean eip213() { + return true; + } + @Override public boolean eip214() { return true; diff --git a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/Eip150HFConfig.java b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/Eip150HFConfig.java index 8d74787268..50fd6538cd 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/blockchain/Eip150HFConfig.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/blockchain/Eip150HFConfig.java @@ -162,6 +162,11 @@ public boolean eip211() { return false; } + @Override + public boolean eip213() { + return parent.eip213(); + } + @Override public boolean eip214() { return false; diff --git a/ethereumj-core/src/main/java/org/ethereum/crypto/bn/BN128.java b/ethereumj-core/src/main/java/org/ethereum/crypto/bn/BN128.java new file mode 100644 index 0000000000..90684f928f --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/crypto/bn/BN128.java @@ -0,0 +1,235 @@ +package org.ethereum.crypto.bn; + +import java.math.BigInteger; + +/** + * Implementation of Barreto–Naehrig curve which is applicable for zkSNARKs calculations.
+ * This specific curve was introduced in + * libff + * and used by a proving system in + * ZCash protocol
+ *
+ * + * Curve equation:
+ * Y^2 = X^3 + b, where "b" equals 3
+ * The curve is defined over F_p, where "p" equals 21888242871839275222246405745257275088696311157297823662689037894645226208583
+ * Point at infinity is encoded as (0, 0)
+ *
+ * + * Code of {@link #add(BN128)} and {@link #dbl()} has been ported from + * libff + * + * @author Mikhail Kalinin + * @since 21.08.2017 + */ +public class BN128 { + + // "b" curve parameter + private static final BigInteger B = BigInteger.valueOf(3); + + // "p" field parameter + private static final BigInteger P = new BigInteger("21888242871839275222246405745257275088696311157297823662689037894645226208583"); + + // the point at infinity + private static final BN128 ZERO = new BN128(BigInteger.ZERO, BigInteger.ZERO); + + /** + * Convenient calculations modulo P + */ + static class Fp { + + static final Fp ONE = new Fp(BigInteger.ONE); + + BigInteger v; + + Fp(BigInteger v) { this.v = v; } + Fp add(Fp o) { return new Fp(this.v.add(o.v).mod(P)); } + Fp add(BigInteger o) { return new Fp(this.v.add(o).mod(P)); } + Fp mul(Fp o) { return new Fp(this.v.multiply(o.v).mod(P)); } + Fp sub(Fp o) { return new Fp(this.v.subtract(o.v).mod(P)); } + Fp squared() { return new Fp(v.multiply(v).mod(P)); } + Fp dbl() { return new Fp(v.add(v).mod(P)); } + Fp inverse() { return new Fp(v.modInverse(P)); } + boolean isZero() { return v.compareTo(BigInteger.ZERO) == 0; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Fp fp = (Fp) o; + + return !(v != null ? v.compareTo(fp.v) != 0 : fp.v != null); + } + + @Override + public String toString() { + return v.toString(); + } + } + + private Fp x; + private Fp y; + + private BN128(Fp x, Fp y) { + this.x = x; + this.y = y; + } + + private BN128(BigInteger x, BigInteger y) { + this.x = new Fp(x); + this.y = new Fp(y); + } + + /** + * Checks whether x and y belong to Fp, + * then checks whether point with (x; y) coordinates lays on the curve. + * + * Returns new point if all checks have been passed, + * otherwise returns null + */ + public static BN128 create(byte[] xx, byte[] yy) { + + BigInteger bx = new BigInteger(1, xx); + BigInteger by = new BigInteger(1, yy); + + // check whether scalars belongs to F_p + if (bx.compareTo(P) >= 0) return null; + if (by.compareTo(P) >= 0) return null; + + Fp x = new Fp(bx); + Fp y = new Fp(by); + + // check for point at infinity + if (x.isZero() && y.isZero()) { + return ZERO; + } + + // check whether point belongs to the curve + if (!isOnCurve(x, y)) return null; + + return new BN128(x, y); + } + + /** + * Transforms given Jacobian to affine coordinates and then creates a point + */ + static BN128 fromJacobian(Fp x, Fp y, Fp z) { + + Fp zInv = z.inverse(); + Fp zInv2 = zInv.squared(); + Fp zInv3 = zInv2.mul(zInv); + + Fp ax = x.mul(zInv2); + Fp ay = y.mul(zInv3); + + return new BN128(ax, ay); + } + + private static boolean isOnCurve(Fp x, Fp y) { + + if (x.isZero() && y.isZero()) return true; + + Fp left = y.squared(); // y^2 + Fp right = x.squared().mul(x).add(B); // x^3 + 3 + return left.equals(right); + } + + public BN128 add(BN128 o) { + + if (this.isZero()) return o; // 0 + P = P + if (o.isZero()) return this; // P + 0 = P + + Fp x1 = this.x, y1 = this.y; + Fp x2 = o.x, y2 = o.y; + + if (x1.equals(x2)) + if (y1.equals(y2)) { + return this.dbl(); // P + P = 2P + } else { + return ZERO; // P + (-P) = 0 + } + + // ported code is started from here + // next calculations are done in Jacobian coordinates assuming that z1 = 1, z2 = 1 + + Fp h = x2.sub(x1); // h = x2 - x1 + Fp i = h.dbl().squared(); // i = (2 * h)^2 + Fp j = h.mul(i); // j = h * i + Fp r = y2.sub(y1).dbl(); // r = 2 * (y2 - y1) + Fp v = x1.mul(i); // v = x1 * i + + Fp x3 = r.squared().sub(j).sub(v.dbl()); // x3 = r^2 - j - 2 * v + Fp y3 = v.sub(x3).mul(r).sub(y1.mul(j).dbl()); // y3 = r * (v - x3) - 2 * (y1 * j) + Fp z3 = ZZ.mul(h); // z3 = ((z1+z2)^2 - z1^2 - z2^2) * h = ZZ * h + + return fromJacobian(x3, y3, z3); + } + // zz = ((z1+z2)^2 - z1^2 - z2^2), z1 and z2 always equal 1 + static final Fp ZZ = Fp.ONE.add(Fp.ONE).squared().sub(Fp.ONE).sub(Fp.ONE); + + + public BN128 mul(BigInteger s) { + + if (s.compareTo(BigInteger.ZERO) == 0) // P * 0 = 0 + return ZERO; + + if (isZero()) return this; // 0 * s = 0 + + // keep s immutable + BN128 res = ZERO; + BN128 addend = this; + + while (s.compareTo(BigInteger.ZERO) != 0) { + + if (s.testBit(0)) // add if bit is set + res = res.add(addend); + + s = s.shiftRight(1); + + if (s.compareTo(BigInteger.ZERO) != 0) // double if m still has non-zero bits + addend = addend.dbl(); + } + + return res; + } + + private BN128 dbl() { + + if (isZero()) return this; + + // ported code is started from here + // next calculations are done in Jacobian coordinates with z = 1 + + Fp a = x.squared(); // a = x^2 + Fp b = y.squared(); // b = y^2 + Fp c = b.squared(); // c = b^2 + Fp d = x.add(b).squared().sub(a).sub(c); + d = d.add(d); // d = 2 * ((x + b)^2 - a - c) + Fp e = a.add(a).add(a); // e = 3 * a + Fp f = e.squared(); // f = e^2 + + Fp x3 = f.sub(d.add(d)); // rx = f - 2 * d + Fp y3 = e.mul(d.sub(x3)).sub(c.dbl().dbl().dbl()); // ry = e * (d - rx) - 8 * c + Fp z3 = y.dbl(); // z3 = 2 * y * z = 2 * y + + return fromJacobian(x3, y3, z3); + } + + public byte[] xBytes() { + return x.v.toByteArray(); + } + + public byte[] yBytes() { + return y.v.toByteArray(); + } + + public boolean isZero() { + return x.isZero() && y.isZero(); + } + + @Override + public String toString() { + return String.format("(%s; %s)", x.v.toString(), y.v.toString()); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java b/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java index 18550fb0ff..0d639e0751 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java @@ -683,4 +683,29 @@ public static int numberOfLeadingZeros(byte[] bytes) { } } + /** + * Parses fixed number of bytes starting from {@code offset} in {@code input} array. + * If {@code input} has not enough bytes return array will be right padded with zero bytes. + * I.e. if {@code offset} is higher than {@code input.length} then zero byte array of length {@code len} will be returned + */ + public static byte[] parseBytes(byte[] input, int offset, int len) { + + if (offset >= input.length || len == 0) + return EMPTY_BYTE_ARRAY; + + byte[] bytes = new byte[len]; + System.arraycopy(input, offset, bytes, 0, Math.min(input.length - offset, len)); + return bytes; + } + + /** + * Parses 32-bytes word from given input. + * Uses {@link #parseBytes(byte[], int, int)} method, + * thus, result will be right-padded with zero bytes if there is not enough bytes in {@code input} + * + * @param idx an index of the word starting from {@code 0} + */ + public static byte[] parseWord(byte[] input, int idx) { + return parseBytes(input, 32 * idx, 32); + } } \ No newline at end of file diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/PrecompiledContracts.java b/ethereumj-core/src/main/java/org/ethereum/vm/PrecompiledContracts.java index cd3aa5d6e3..da28e9b4bb 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/PrecompiledContracts.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/PrecompiledContracts.java @@ -20,6 +20,8 @@ import org.ethereum.config.BlockchainConfig; import org.ethereum.crypto.ECKey; import org.ethereum.crypto.HashUtil; +import org.ethereum.crypto.bn.BN128; +import org.ethereum.util.BIUtil; import java.math.BigInteger; @@ -39,13 +41,16 @@ public class PrecompiledContracts { 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 DataWord ecRecoverAddr = new DataWord("0000000000000000000000000000000000000000000000000000000000000001"); private static final DataWord sha256Addr = new DataWord("0000000000000000000000000000000000000000000000000000000000000002"); private static final DataWord ripempd160Addr = new DataWord("0000000000000000000000000000000000000000000000000000000000000003"); private static final DataWord identityAddr = new DataWord("0000000000000000000000000000000000000000000000000000000000000004"); private static final DataWord modExpAddr = new DataWord("0000000000000000000000000000000000000000000000000000000000000005"); - + private static final DataWord altBN128AddAddr = new DataWord("0000000000000000000000000000000000000000000000000000000000000006"); + private static final DataWord altBN128MulAddr = new DataWord("0000000000000000000000000000000000000000000000000000000000000007"); public static PrecompiledContract getContractForAddress(DataWord address, BlockchainConfig config) { @@ -57,10 +62,24 @@ public static PrecompiledContract getContractForAddress(DataWord address, Blockc // 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; 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); @@ -286,16 +305,104 @@ 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 BN128} 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
+ *
+ * + * throws exception if coordinates are invalid or one of input points does not belong to the curve + */ + public static class BN128Addition extends PrecompiledContract { + + @Override + public long getGasForData(byte[] data) { + + if (data == null) return 0; + + return 500; + } + + @Override + public byte[] execute(byte[] data) { + + if (data == null) + return EMPTY_BYTE_ARRAY; + + byte[] x1 = parseWord(data, 0); + byte[] y1 = parseWord(data, 1); + + byte[] x2 = parseWord(data, 2); + byte[] y2 = parseWord(data, 3); - private byte[] parseBytes(byte[] data, int offset, int len) { + BN128 p1 = BN128.create(x1, y1); + if (p1 == null) + return EMPTY_BYTE_ARRAY; - if (offset >= data.length || len == 0) + BN128 p2 = BN128.create(x2, y2); + if (p2 == null) return EMPTY_BYTE_ARRAY; - byte[] bytes = new byte[len]; - System.arraycopy(data, offset, bytes, 0, Math.min(data.length - offset, len)); - return bytes; + BN128 res = p1.add(p2); + + return encodeRes(res.xBytes(), res.yBytes()); + } + } + + /** + * Computes multiplication of scalar value on a point belonging to Barreto–Naehrig curve. + * See {@link BN128} for details
+ *
+ * + * input data[]:
+ * point encoded as (x, y) and 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
+ *
+ * + * throws exception if coordinates are invalid or point does not belong to the curve + */ + public static class BN128Multiplication extends PrecompiledContract { + + @Override + public long getGasForData(byte[] data) { + + if (data == null) return 0; + + return 2000; } + @Override + public byte[] execute(byte[] data) { + + if (data == null) + return EMPTY_BYTE_ARRAY; + + byte[] x = parseWord(data, 0); + byte[] y = parseWord(data, 1); + + byte[] s = parseWord(data, 2); + + BN128 p = BN128.create(x, y); + if (p == null) + return EMPTY_BYTE_ARRAY; + + BN128 res = p.mul(BIUtil.toBI(s)); + + return encodeRes(res.xBytes(), res.yBytes()); + } } } From dc1b1711dc418bc36469365ce9d22c25f261fda5 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Mon, 28 Aug 2017 15:13:41 +0600 Subject: [PATCH 2/5] Add success flag to precompiles results --- .../ethereum/core/TransactionExecutor.java | 3 +- .../org/ethereum/vm/PrecompiledContracts.java | 51 ++++++++++--------- .../java/org/ethereum/vm/program/Program.java | 17 +++++-- .../ethereum/vm/PrecompiledContractTest.java | 24 ++++----- 4 files changed, 53 insertions(+), 42 deletions(-) diff --git a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java index e8bf00c888..c69142b306 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java @@ -17,6 +17,7 @@ */ package org.ethereum.core; +import org.apache.commons.lang3.tuple.Pair; import org.ethereum.config.BlockchainConfig; import org.ethereum.config.CommonConfig; import org.ethereum.config.SystemProperties; @@ -225,7 +226,7 @@ private void call() { m_endGas = m_endGas.subtract(BigInteger.valueOf(requiredGas + basicTxCost)); // FIXME: save return for vm trace - byte[] out = precompiledContract.execute(tx.getData()); + Pair out = precompiledContract.execute(tx.getData()); } } else { diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/PrecompiledContracts.java b/ethereumj-core/src/main/java/org/ethereum/vm/PrecompiledContracts.java index da28e9b4bb..723c585267 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/PrecompiledContracts.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/PrecompiledContracts.java @@ -17,6 +17,7 @@ */ 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; @@ -84,7 +85,7 @@ private static byte[] encodeRes(byte[] w1, byte[] w2) { public static abstract class PrecompiledContract { public abstract long getGasForData(byte[] data); - public abstract byte[] execute(byte[] data); + public abstract Pair execute(byte[] data); } public static class Identity extends PrecompiledContract { @@ -102,8 +103,8 @@ public long getGasForData(byte[] data) { } @Override - public byte[] execute(byte[] data) { - return data; + public Pair execute(byte[] data) { + return Pair.of(true, data); } } @@ -120,10 +121,10 @@ public long getGasForData(byte[] data) { } @Override - public byte[] execute(byte[] data) { + public Pair execute(byte[] data) { - if (data == null) return HashUtil.sha256(EMPTY_BYTE_ARRAY); - return HashUtil.sha256(data); + if (data == null) return Pair.of(true, HashUtil.sha256(EMPTY_BYTE_ARRAY)); + return Pair.of(true, HashUtil.sha256(data)); } } @@ -142,13 +143,13 @@ public long getGasForData(byte[] data) { } @Override - public byte[] execute(byte[] data) { + public Pair execute(byte[] data) { byte[] result = null; if (data == null) result = HashUtil.ripemd160(EMPTY_BYTE_ARRAY); else result = HashUtil.ripemd160(data); - return new DataWord(result).getData(); + return Pair.of(true, new DataWord(result).getData()); } } @@ -161,7 +162,7 @@ public long getGasForData(byte[] data) { } @Override - public byte[] execute(byte[] data) { + public Pair execute(byte[] data) { byte[] h = new byte[32]; byte[] v = new byte[32]; @@ -186,9 +187,9 @@ public byte[] execute(byte[] data) { } if (out == null) { - return new byte[0]; + return Pair.of(true, EMPTY_BYTE_ARRAY); } else { - return out.getData(); + return Pair.of(true, out.getData()); } } @@ -239,10 +240,10 @@ public long getGasForData(byte[] data) { } @Override - public byte[] execute(byte[] data) { + public Pair execute(byte[] data) { if (data == null) - return EMPTY_BYTE_ARRAY; + return Pair.of(true, EMPTY_BYTE_ARRAY); int baseLen = parseLen(data, 0); int expLen = parseLen(data, 1); @@ -254,7 +255,7 @@ public byte[] execute(byte[] data) { // check if modulus is zero if (isZero(mod)) - return EMPTY_BYTE_ARRAY; + return Pair.of(true, EMPTY_BYTE_ARRAY); byte[] res = stripLeadingZeroes(base.modPow(exp, mod).toByteArray()); @@ -264,10 +265,10 @@ public byte[] execute(byte[] data) { byte[] adjRes = new byte[modLen]; System.arraycopy(res, 0, adjRes, modLen - res.length, res.length); - return adjRes; + return Pair.of(true, adjRes); } else { - return res; + return Pair.of(true, res); } } @@ -334,10 +335,10 @@ public long getGasForData(byte[] data) { } @Override - public byte[] execute(byte[] data) { + public Pair execute(byte[] data) { if (data == null) - return EMPTY_BYTE_ARRAY; + return Pair.of(true, EMPTY_BYTE_ARRAY); byte[] x1 = parseWord(data, 0); byte[] y1 = parseWord(data, 1); @@ -347,15 +348,15 @@ public byte[] execute(byte[] data) { BN128 p1 = BN128.create(x1, y1); if (p1 == null) - return EMPTY_BYTE_ARRAY; + return Pair.of(false, EMPTY_BYTE_ARRAY); BN128 p2 = BN128.create(x2, y2); if (p2 == null) - return EMPTY_BYTE_ARRAY; + return Pair.of(false, EMPTY_BYTE_ARRAY); BN128 res = p1.add(p2); - return encodeRes(res.xBytes(), res.yBytes()); + return Pair.of(true, encodeRes(res.xBytes(), res.yBytes())); } } @@ -386,10 +387,10 @@ public long getGasForData(byte[] data) { } @Override - public byte[] execute(byte[] data) { + public Pair execute(byte[] data) { if (data == null) - return EMPTY_BYTE_ARRAY; + return Pair.of(true, EMPTY_BYTE_ARRAY); byte[] x = parseWord(data, 0); byte[] y = parseWord(data, 1); @@ -398,11 +399,11 @@ public byte[] execute(byte[] data) { BN128 p = BN128.create(x, y); if (p == null) - return EMPTY_BYTE_ARRAY; + return Pair.of(false, EMPTY_BYTE_ARRAY); BN128 res = p.mul(BIUtil.toBI(s)); - return encodeRes(res.xBytes(), res.yBytes()); + return Pair.of(true, encodeRes(res.xBytes(), res.yBytes())); } } } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java b/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java index 863af6cf75..9dcec23f25 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java @@ -17,6 +17,7 @@ */ package org.ethereum.vm.program; +import org.apache.commons.lang3.tuple.Pair; import org.ethereum.config.BlockchainConfig; import org.ethereum.config.CommonConfig; import org.ethereum.config.SystemProperties; @@ -1170,11 +1171,19 @@ public void callToPrecompiledAddress(MessageCall msg, PrecompiledContract contra track.rollback(); } else { - this.refundGas(msg.getGas().longValue() - requiredGas, "call pre-compiled"); - byte[] out = contract.execute(data); + Pair out = contract.execute(data); + + if (out.getLeft()) { // success + this.refundGas(msg.getGas().longValue() - requiredGas, "call pre-compiled"); + this.stackPushOne(); + } else { + // spend all gas on failure and push zero + this.refundGas(0, "call pre-compiled"); + this.stackPushZero(); + } + + this.memorySave(msg.getOutDataOffs().intValue(), out.getRight()); - this.memorySave(msg.getOutDataOffs().intValue(), out); - this.stackPushOne(); track.commit(); } } diff --git a/ethereumj-core/src/test/java/org/ethereum/vm/PrecompiledContractTest.java b/ethereumj-core/src/test/java/org/ethereum/vm/PrecompiledContractTest.java index 330c57175c..49264f9fa1 100644 --- a/ethereumj-core/src/test/java/org/ethereum/vm/PrecompiledContractTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/vm/PrecompiledContractTest.java @@ -52,7 +52,7 @@ public void identityTest1() { byte[] data = Hex.decode("112233445566"); byte[] expected = Hex.decode("112233445566"); - byte[] result = contract.execute(data); + byte[] result = contract.execute(data).getRight(); assertArrayEquals(expected, result); } @@ -66,7 +66,7 @@ public void sha256Test1() { byte[] data = null; String expected = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; - byte[] result = contract.execute(data); + byte[] result = contract.execute(data).getRight(); assertEquals(expected, Hex.toHexString(result)); } @@ -79,7 +79,7 @@ public void sha256Test2() { byte[] data = ByteUtil.EMPTY_BYTE_ARRAY; String expected = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; - byte[] result = contract.execute(data); + byte[] result = contract.execute(data).getRight(); assertEquals(expected, Hex.toHexString(result)); } @@ -92,7 +92,7 @@ public void sha256Test3() { byte[] data = Hex.decode("112233"); String expected = "49ee2bf93aac3b1fb4117e59095e07abe555c3383b38d608da37680a406096e8"; - byte[] result = contract.execute(data); + byte[] result = contract.execute(data).getRight(); assertEquals(expected, Hex.toHexString(result)); } @@ -106,7 +106,7 @@ public void Ripempd160Test1() { byte[] data = Hex.decode("0000000000000000000000000000000000000000000000000000000000000001"); String expected = "000000000000000000000000ae387fcfeb723c3f5964509af111cf5a67f30661"; - byte[] result = contract.execute(data); + byte[] result = contract.execute(data).getRight(); assertEquals(expected, Hex.toHexString(result)); } @@ -119,7 +119,7 @@ public void ecRecoverTest1() { PrecompiledContract contract = PrecompiledContracts.getContractForAddress(addr, eip160Config); String expected = "000000000000000000000000ae387fcfeb723c3f5964509af111cf5a67f30661"; - byte[] result = contract.execute(data); + byte[] result = contract.execute(data).getRight(); System.out.println(Hex.toHexString(result)); @@ -147,7 +147,7 @@ public void modExpTest() { assertEquals(2611, contract.getGasForData(data1)); - byte[] res1 = contract.execute(data1); + byte[] res1 = contract.execute(data1).getRight(); assertEquals(32, res1.length); assertEquals(BigInteger.ONE, bytesToBigInteger(res1)); @@ -160,7 +160,7 @@ public void modExpTest() { assertEquals(2611, contract.getGasForData(data2)); - byte[] res2 = contract.execute(data2); + byte[] res2 = contract.execute(data2).getRight(); assertEquals(32, res2.length); assertEquals(BigInteger.ZERO, bytesToBigInteger(res2)); @@ -185,7 +185,7 @@ public void modExpTest() { assertEquals(153, contract.getGasForData(data4)); - byte[] res4 = contract.execute(data4); + byte[] res4 = contract.execute(data4).getRight(); assertEquals(32, res4.length); assertEquals(new BigInteger("26689440342447178617115869845918039756797228267049433585260346420242739014315"), bytesToBigInteger(res4)); @@ -200,7 +200,7 @@ public void modExpTest() { assertEquals(153, contract.getGasForData(data5)); - byte[] res5 = contract.execute(data5); + byte[] res5 = contract.execute(data5).getRight(); assertEquals(32, res5.length); assertEquals(new BigInteger("26689440342447178617115869845918039756797228267049433585260346420242739014315"), bytesToBigInteger(res5)); @@ -231,11 +231,11 @@ public void modExpTest() { assertEquals(0, contract.getGasForData(data8)); - byte[] res8 = contract.execute(data8); + byte[] res8 = contract.execute(data8).getRight(); assertArrayEquals(EMPTY_BYTE_ARRAY, res8); assertEquals(0, contract.getGasForData(null)); - assertArrayEquals(EMPTY_BYTE_ARRAY, contract.execute(null)); + assertArrayEquals(EMPTY_BYTE_ARRAY, contract.execute(null).getRight()); } } From f82d16a5b9aadced157c1368f3aff0e98d3fefd5 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Mon, 28 Aug 2017 16:28:48 +0600 Subject: [PATCH 3/5] Add Tx logs hash handling in StateTest suite --- .../ethereum/jsontestsuite/GitHubStateTest.java | 10 +++++----- .../org/ethereum/jsontestsuite/suite/Logs.java | 15 +++++++++++++++ .../jsontestsuite/suite/StateTestCase.java | 11 +++++------ .../jsontestsuite/suite/StateTestData.java | 2 +- .../jsontestsuite/suite/StateTestDataEntry.java | 2 +- .../jsontestsuite/suite/model/PostDataTck.java | 9 ++++++--- .../suite/runners/StateTestRunner.java | 4 +--- 7 files changed, 34 insertions(+), 19 deletions(-) diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubStateTest.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubStateTest.java index 95e370b183..24aa98dd26 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubStateTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/GitHubStateTest.java @@ -29,15 +29,14 @@ @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class GitHubStateTest { - static String commitSHA = "400b1a86d4b13b46e8f13ca65dd206bc46120495"; - static String treeSHA = "8fb6cc05cadb1432e532b7492fc5e5b771ffea58"; // https://github.com/ethereum/tests/tree/develop/GeneralStateTests/ + static String commitSHA = "f17609b3c2fc7404addf3d228955b16ae8e0cfb4"; + static String treeSHA = "fdcc301d8fdd65e6f1b56ffca9a786aa337c7bb8"; // https://github.com/ethereum/tests/tree/develop/GeneralStateTests/ static GitHubJSONTestSuite.Network[] targetNets = { GitHubJSONTestSuite.Network.Frontier, GitHubJSONTestSuite.Network.Homestead, GitHubJSONTestSuite.Network.EIP150, GitHubJSONTestSuite.Network.EIP158, // GitHubJSONTestSuite.Network.Byzantium - }; static GeneralStateTestSuite suite; @@ -58,7 +57,7 @@ public void clean() { // it reduces impact on GitHub API public void stSingleTest() throws IOException { GeneralStateTestSuite.runSingle( - "stRevertTest/RevertOpcodeInCreateReturns.json", commitSHA, GitHubJSONTestSuite.Network.Byzantium); + "stZeroKnowledge/pointAdd.json", commitSHA, GitHubJSONTestSuite.Network.Byzantium); } @Test @@ -220,7 +219,8 @@ public void stSystemOperationsTest() throws IOException { public void stTransactionTest() throws IOException { // TODO enable when zero sig Txes comes in suite.runAll("stTransactionTest", new HashSet<>(Arrays.asList( - "zeroSigTransacrionCreate", + "zeroSigTransactionCreate", + "zeroSigTransactionCreatePrice0", "zeroSigTransacrionCreatePrice0", "zeroSigTransaction", "zeroSigTransaction0Price", diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/Logs.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/Logs.java index db9add378f..7329b7f139 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/Logs.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/Logs.java @@ -17,6 +17,10 @@ */ package org.ethereum.jsontestsuite.suite; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.DeserializationContext; +import org.codehaus.jackson.map.JsonDeserializer; import org.ethereum.crypto.HashUtil; import org.ethereum.util.ByteUtil; import org.ethereum.util.FastByteComparisons; @@ -29,6 +33,7 @@ import org.spongycastle.util.encoders.Hex; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -38,14 +43,24 @@ public class Logs { List logs; byte[] logsHash; + @SuppressWarnings("unchecked") public Logs(Object logs) { if (logs instanceof JSONArray) { init((JSONArray) logs); + } else if (logs instanceof List) { + this.logs = (List) logs; } else { init((String) logs); } } + public static class Deserializer extends JsonDeserializer { + @Override + public Logs deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + return new Logs(jp.readValueAs(Object.class)); + } + } + private void init(String logsHash) { this.logsHash = Utils.parseData(logsHash); } diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestCase.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestCase.java index 02f5e35d3b..0990301fa0 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestCase.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestCase.java @@ -17,21 +17,21 @@ */ package org.ethereum.jsontestsuite.suite; +import org.codehaus.jackson.map.annotate.JsonDeserialize; import org.ethereum.config.BlockchainNetConfig; import org.ethereum.jsontestsuite.GitHubJSONTestSuite; import org.ethereum.jsontestsuite.suite.model.TransactionTck; import org.ethereum.jsontestsuite.suite.model.AccountTck; import org.ethereum.jsontestsuite.suite.model.EnvTck; -import org.ethereum.jsontestsuite.suite.model.LogTck; -import java.util.List; import java.util.Map; public class StateTestCase { private EnvTck env; - private List logs; + @JsonDeserialize(using = Logs.Deserializer.class) + private Logs logs; private String out; private Map pre; private String postStateRoot; @@ -40,7 +40,6 @@ public class StateTestCase { private GitHubJSONTestSuite.Network network; private String name; - public StateTestCase() { } @@ -52,11 +51,11 @@ public void setEnv(EnvTck env) { this.env = env; } - public List getLogs() { + public Logs getLogs() { return logs; } - public void setLogs(List logs) { + public void setLogs(Logs logs) { this.logs = logs; } diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestData.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestData.java index 3306bee403..5a798b06a2 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestData.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestData.java @@ -36,7 +36,7 @@ public List getTestCases(GitHubJSONTestSuite.Network network) { for (int i = 0; i < testCases.size(); i++) { StateTestCase testCase = testCases.get(i); testCase.setName(String.format("%s_%s%s", e.getKey(), network.name(), - testCases.size() > 1 ? " " + String.valueOf(i + 1) : "")); + testCases.size() > 1 ? "_" + String.valueOf(i + 1) : "")); } cases.addAll(testCases); } diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestDataEntry.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestDataEntry.java index 42c543622b..38a662551a 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestDataEntry.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/StateTestDataEntry.java @@ -64,7 +64,7 @@ public List getTestCases(GitHubJSONTestSuite.Network network) { if (data.getLogs() != null) { testCase.setLogs(data.getLogs()); } else { - testCase.setLogs(Collections.emptyList()); + testCase.setLogs(new Logs("0x")); } if (data.getHash() != null) { testCase.setPostStateRoot(data.getHash().startsWith("0x") ? data.getHash().substring(2) : data.getHash()); diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/model/PostDataTck.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/model/PostDataTck.java index 52c4c5e7bc..f4ee0d6480 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/model/PostDataTck.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/model/PostDataTck.java @@ -1,6 +1,8 @@ package org.ethereum.jsontestsuite.suite.model; import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.codehaus.jackson.map.annotate.JsonDeserialize; +import org.ethereum.jsontestsuite.suite.Logs; import java.util.List; @@ -12,7 +14,8 @@ public class PostDataTck { String hash; - List logs; + @JsonDeserialize(using = Logs.Deserializer.class) + Logs logs; Indexes indexes; public String getHash() { @@ -23,11 +26,11 @@ public void setHash(String hash) { this.hash = hash; } - public List getLogs() { + public Logs getLogs() { return logs; } - public void setLogs(List logs) { + public void setLogs(Logs logs) { this.logs = logs; } diff --git a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/runners/StateTestRunner.java b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/runners/StateTestRunner.java index 40f8ad0164..a49e2c5e6d 100644 --- a/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/runners/StateTestRunner.java +++ b/ethereumj-core/src/test/java/org/ethereum/jsontestsuite/suite/runners/StateTestRunner.java @@ -113,12 +113,10 @@ public List runImpl() { repository.commit(); List origLogs = programResult.getLogInfoList(); - List postLogs = LogBuilder.build(stateTestCase.getLogs()); + List logsResult = stateTestCase.getLogs().compareToReal(origLogs); List results = new ArrayList<>(); - List logsResult = LogsValidator.valid(origLogs, postLogs); - if (stateTestCase.getPost() != null) { Repository postRepository = RepositoryBuilder.build(stateTestCase.getPost()); List repoResults = RepositoryValidator.valid(repository, postRepository); From cedfc93cee46254df42bd68abd0eab835fb4c011 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Mon, 28 Aug 2017 21:27:06 +0600 Subject: [PATCH 4/5] Revert state changes if precompile execution fails --- .../src/main/java/org/ethereum/vm/program/Program.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java b/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java index 9dcec23f25..ed6fdf44ea 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java @@ -1176,15 +1176,15 @@ public void callToPrecompiledAddress(MessageCall msg, PrecompiledContract contra if (out.getLeft()) { // success this.refundGas(msg.getGas().longValue() - requiredGas, "call pre-compiled"); this.stackPushOne(); + track.commit(); } else { - // spend all gas on failure and push zero + // spend all gas on failure, push zero and revert state changes this.refundGas(0, "call pre-compiled"); this.stackPushZero(); + track.rollback(); } this.memorySave(msg.getOutDataOffs().intValue(), out.getRight()); - - track.commit(); } } From cda227bee869ec3311e455d4c040be0de4ec75e9 Mon Sep 17 00:00:00 2001 From: Anton Nahsatyrev Date: Wed, 30 Aug 2017 14:41:41 +0300 Subject: [PATCH 5/5] Fix merge error --- .../src/main/java/org/ethereum/vm/program/Program.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java b/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java index 15288926be..05e5a15440 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/program/Program.java @@ -1176,7 +1176,7 @@ public void callToPrecompiledAddress(MessageCall msg, PrecompiledContract contra if (out.getLeft()) { // success this.refundGas(msg.getGas().longValue() - requiredGas, "call pre-compiled"); this.stackPushOne(); - returnDataBuffer = out; + returnDataBuffer = out.getRight(); track.commit(); } else { // spend all gas on failure, push zero and revert state changes