diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java index 48d7a72b300..ab72ccf76a7 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/AddModOperationBenchmark.java @@ -18,8 +18,149 @@ import org.hyperledger.besu.evm.operation.AddModOperation; import org.hyperledger.besu.evm.operation.Operation; +import java.util.concurrent.ThreadLocalRandom; + +import org.apache.tuweni.bytes.Bytes; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + public class AddModOperationBenchmark extends TernaryOperationBenchmark { + // Benches for (a + b) % c + + // Define available scenarios + public enum Case { + ADDMOD_32_32_32(1, 1, 1), + ADDMOD_64_32_32(2, 1, 1), + ADDMOD_64_64_32(2, 2, 1), + ADDMOD_64_64_64(2, 2, 2), + ADDMOD_128_32_32(4, 1, 1), + ADDMOD_128_64_32(4, 2, 1), + ADDMOD_128_64_64(4, 2, 2), + ADDMOD_128_128_32(4, 4, 1), + ADDMOD_128_128_64(4, 4, 2), + ADDMOD_128_128_128(4, 4, 3), + ADDMOD_192_32_32(6, 1, 1), + ADDMOD_192_64_32(6, 2, 1), + ADDMOD_192_64_64(6, 2, 2), + ADDMOD_192_128_32(6, 4, 1), + ADDMOD_192_128_64(6, 4, 2), + ADDMOD_192_128_128(6, 4, 4), + ADDMOD_192_192_32(6, 6, 1), + ADDMOD_192_192_64(6, 6, 2), + ADDMOD_192_192_128(6, 6, 4), + ADDMOD_192_192_192(6, 6, 6), + ADDMOD_256_32_32(8, 1, 1), + ADDMOD_256_64_32(8, 2, 1), + ADDMOD_256_64_64(8, 2, 2), + ADDMOD_256_128_32(8, 4, 1), + ADDMOD_256_128_64(8, 4, 2), + ADDMOD_256_128_128(8, 4, 4), + ADDMOD_256_192_32(8, 6, 1), + ADDMOD_256_192_64(8, 6, 2), + ADDMOD_256_192_128(8, 6, 4), + ADDMOD_256_192_192(8, 6, 6), + ADDMOD_256_256_32(8, 8, 1), + ADDMOD_256_256_64(8, 8, 2), + ADDMOD_256_256_128(8, 8, 4), + ADDMOD_256_256_192(8, 8, 6), + ADDMOD_256_256_256(8, 8, 8), + LARGER_ADDMOD_64_64_128(2, 2, 4), + LARGER_ADDMOD_192_192_256(6, 6, 8), + ZERO_ADDMOD_128_256_0(4, 8, 0), + FULL_RANDOM(-1, -1, -1); + + final int aSize; + final int bSize; + final int cSize; + + Case(final int aSize, final int bSize, final int cSize) { + this.aSize = aSize; + this.bSize = bSize; + this.cSize = cSize; + } + } + + @Param({ + "ADDMOD_32_32_32", + "ADDMOD_64_32_32", + "ADDMOD_64_64_32", + "ADDMOD_64_64_64", + "ADDMOD_128_32_32", + "ADDMOD_128_64_32", + "ADDMOD_128_64_64", + "ADDMOD_128_128_32", + "ADDMOD_128_128_64", + "ADDMOD_128_128_128", + "ADDMOD_192_32_32", + "ADDMOD_192_64_32", + "ADDMOD_192_64_64", + "ADDMOD_192_128_32", + "ADDMOD_192_128_64", + "ADDMOD_192_128_128", + "ADDMOD_192_192_32", + "ADDMOD_192_192_64", + "ADDMOD_192_192_128", + "ADDMOD_192_192_192", + "ADDMOD_256_32_32", + "ADDMOD_256_64_32", + "ADDMOD_256_64_64", + "ADDMOD_256_128_32", + "ADDMOD_256_128_64", + "ADDMOD_256_128_128", + "ADDMOD_256_192_32", + "ADDMOD_256_192_64", + "ADDMOD_256_192_128", + "ADDMOD_256_192_192", + "ADDMOD_256_256_32", + "ADDMOD_256_256_64", + "ADDMOD_256_256_128", + "ADDMOD_256_256_192", + "ADDMOD_256_256_256", + "LARGER_ADDMOD_64_64_128", + "LARGER_ADDMOD_192_192_256", + "ZERO_ADDMOD_128_256_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelper.createMessageCallFrame(); + + Case scenario = Case.valueOf(caseName); + aPool = new Bytes[SAMPLE_SIZE]; + bPool = new Bytes[SAMPLE_SIZE]; + cPool = new Bytes[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + int cSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.aSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.aSize * 4; + if (scenario.bSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.bSize * 4; + if (scenario.cSize < 0) cSize = random.nextInt(1, 33); + else cSize = scenario.cSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + final byte[] c = new byte[cSize]; + random.nextBytes(a); + random.nextBytes(b); + random.nextBytes(c); + aPool[i] = Bytes.wrap(a); + bPool[i] = Bytes.wrap(b); + cPool[i] = Bytes.wrap(c); + } + index = 0; + } + @Override protected Operation.OperationResult invoke(final MessageFrame frame) { return AddModOperation.staticOperation(frame); diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ModOperationBenchmark.java index 6404764b7e3..bb18601286b 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/ModOperationBenchmark.java @@ -18,7 +18,113 @@ import org.hyperledger.besu.evm.operation.ModOperation; import org.hyperledger.besu.evm.operation.Operation; +import java.math.BigInteger; +import java.util.concurrent.ThreadLocalRandom; + +import org.apache.tuweni.bytes.Bytes; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + public class ModOperationBenchmark extends BinaryOperationBenchmark { + // Benches for a % b + + // Define available scenarios + public enum Case { + MOD_32_32(1, 1), + MOD_64_32(2, 1), + MOD_64_64(2, 2), + MOD_128_32(4, 1), + MOD_128_64(4, 2), + MOD_128_128(4, 4), + MOD_192_32(6, 1), + MOD_192_64(6, 2), + MOD_192_128(6, 4), + MOD_192_192(6, 6), + MOD_256_32(8, 1), + MOD_256_64(8, 2), + MOD_256_128(8, 4), + MOD_256_192(8, 6), + MOD_256_256(8, 8), + LARGER_MOD_64_128(2, 4), + LARGER_MOD_192_256(6, 8), + ZERO_MOD_128_0(4, 0), + FULL_RANDOM(-1, -1); + + final int divSize; + final int modSize; + + Case(final int divSize, final int modSize) { + this.divSize = divSize; + this.modSize = modSize; + } + } + + @Param({ + "MOD_32_32", + "MOD_64_32", + "MOD_64_64", + "MOD_128_32", + "MOD_128_64", + "MOD_128_128", + "MOD_192_32", + "MOD_192_64", + "MOD_192_128", + "MOD_192_192", + "MOD_256_32", + "MOD_256_64", + "MOD_256_128", + "MOD_256_192", + "MOD_256_256", + "LARGER_MOD_64_128", + "LARGER_MOD_192_256", + "ZERO_MOD_128_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelper.createMessageCallFrame(); + + Case scenario = Case.valueOf(caseName); + aPool = new Bytes[SAMPLE_SIZE]; + bPool = new Bytes[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.divSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.divSize * 4; + if (scenario.modSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.modSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + random.nextBytes(a); + random.nextBytes(b); + + // Swap a and b if necessary + if ((scenario.divSize != scenario.modSize)) { + aPool[i] = Bytes.wrap(a); + bPool[i] = Bytes.wrap(b); + } else { + BigInteger aInt = new BigInteger(a); + BigInteger bInt = new BigInteger(b); + if ((aInt.compareTo(bInt) < 0)) { + aPool[i] = Bytes.wrap(b); + bPool[i] = Bytes.wrap(a); + } else { + aPool[i] = Bytes.wrap(a); + bPool[i] = Bytes.wrap(b); + } + } + } + index = 0; + } @Override protected Operation.OperationResult invoke(final MessageFrame frame) { diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java index be3771432e1..fbe5a3305cc 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/MulModOperationBenchmark.java @@ -18,8 +18,149 @@ import org.hyperledger.besu.evm.operation.MulModOperation; import org.hyperledger.besu.evm.operation.Operation; +import java.util.concurrent.ThreadLocalRandom; + +import org.apache.tuweni.bytes.Bytes; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + public class MulModOperationBenchmark extends TernaryOperationBenchmark { + // Benches for (a + b) % c + + // Define available scenarios + public enum Case { + MULMOD_32_32_32(1, 1, 1), + MULMOD_64_32_32(2, 1, 1), + MULMOD_64_64_32(2, 2, 1), + MULMOD_64_64_64(2, 2, 2), + MULMOD_128_32_32(4, 1, 1), + MULMOD_128_64_32(4, 2, 1), + MULMOD_128_64_64(4, 2, 2), + MULMOD_128_128_32(4, 4, 1), + MULMOD_128_128_64(4, 4, 2), + MULMOD_128_128_128(4, 4, 3), + MULMOD_192_32_32(6, 1, 1), + MULMOD_192_64_32(6, 2, 1), + MULMOD_192_64_64(6, 2, 2), + MULMOD_192_128_32(6, 4, 1), + MULMOD_192_128_64(6, 4, 2), + MULMOD_192_128_128(6, 4, 4), + MULMOD_192_192_32(6, 6, 1), + MULMOD_192_192_64(6, 6, 2), + MULMOD_192_192_128(6, 6, 4), + MULMOD_192_192_192(6, 6, 6), + MULMOD_256_32_32(8, 1, 1), + MULMOD_256_64_32(8, 2, 1), + MULMOD_256_64_64(8, 2, 2), + MULMOD_256_128_32(8, 4, 1), + MULMOD_256_128_64(8, 4, 2), + MULMOD_256_128_128(8, 4, 4), + MULMOD_256_192_32(8, 6, 1), + MULMOD_256_192_64(8, 6, 2), + MULMOD_256_192_128(8, 6, 4), + MULMOD_256_192_192(8, 6, 6), + MULMOD_256_256_32(8, 8, 1), + MULMOD_256_256_64(8, 8, 2), + MULMOD_256_256_128(8, 8, 4), + MULMOD_256_256_192(8, 8, 6), + MULMOD_256_256_256(8, 8, 8), + LARGER_MULMOD_64_64_128(2, 2, 4), + LARGER_MULMOD_192_192_256(6, 6, 8), + ZERO_MULMOD_128_256_0(4, 8, 0), + FULL_RANDOM(-1, -1, -1); + + final int aSize; + final int bSize; + final int cSize; + + Case(final int aSize, final int bSize, final int cSize) { + this.aSize = aSize; + this.bSize = bSize; + this.cSize = cSize; + } + } + + @Param({ + "MULMOD_32_32_32", + "MULMOD_64_32_32", + "MULMOD_64_64_32", + "MULMOD_64_64_64", + "MULMOD_128_32_32", + "MULMOD_128_64_32", + "MULMOD_128_64_64", + "MULMOD_128_128_32", + "MULMOD_128_128_64", + "MULMOD_128_128_128", + "MULMOD_192_32_32", + "MULMOD_192_64_32", + "MULMOD_192_64_64", + "MULMOD_192_128_32", + "MULMOD_192_128_64", + "MULMOD_192_128_128", + "MULMOD_192_192_32", + "MULMOD_192_192_64", + "MULMOD_192_192_128", + "MULMOD_192_192_192", + "MULMOD_256_32_32", + "MULMOD_256_64_32", + "MULMOD_256_64_64", + "MULMOD_256_128_32", + "MULMOD_256_128_64", + "MULMOD_256_128_128", + "MULMOD_256_192_32", + "MULMOD_256_192_64", + "MULMOD_256_192_128", + "MULMOD_256_192_192", + "MULMOD_256_256_32", + "MULMOD_256_256_64", + "MULMOD_256_256_128", + "MULMOD_256_256_192", + "MULMOD_256_256_256", + "LARGER_MULMOD_64_64_128", + "LARGER_MULMOD_192_192_256", + "ZERO_MULMOD_128_256_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelper.createMessageCallFrame(); + + Case scenario = Case.valueOf(caseName); + aPool = new Bytes[SAMPLE_SIZE]; + bPool = new Bytes[SAMPLE_SIZE]; + cPool = new Bytes[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + int cSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.aSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.aSize * 4; + if (scenario.bSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.bSize * 4; + if (scenario.cSize < 0) cSize = random.nextInt(1, 33); + else cSize = scenario.cSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + final byte[] c = new byte[cSize]; + random.nextBytes(a); + random.nextBytes(b); + random.nextBytes(c); + aPool[i] = Bytes.wrap(a); + bPool[i] = Bytes.wrap(b); + cPool[i] = Bytes.wrap(c); + } + index = 0; + } + @Override protected Operation.OperationResult invoke(final MessageFrame frame) { return MulModOperation.staticOperation(frame); diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SModOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SModOperationBenchmark.java index d09ecae1311..80ee76944e0 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SModOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/SModOperationBenchmark.java @@ -18,7 +18,113 @@ import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.operation.SModOperation; +import java.math.BigInteger; +import java.util.concurrent.ThreadLocalRandom; + +import org.apache.tuweni.bytes.Bytes; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; + public class SModOperationBenchmark extends BinaryOperationBenchmark { + // Benches for a % b + + // Define available scenarios + public enum Case { + SMOD_32_32(1, 1), + SMOD_64_32(2, 1), + SMOD_64_64(2, 2), + SMOD_128_32(4, 1), + SMOD_128_64(4, 2), + SMOD_128_128(4, 4), + SMOD_192_32(6, 1), + SMOD_192_64(6, 2), + SMOD_192_128(6, 4), + SMOD_192_192(6, 6), + SMOD_256_32(8, 1), + SMOD_256_64(8, 2), + SMOD_256_128(8, 4), + SMOD_256_192(8, 6), + SMOD_256_256(8, 8), + LARGER_SMOD_64_128(2, 4), + LARGER_SMOD_192_256(6, 8), + ZERO_SMOD_128_0(4, 0), + FULL_RANDOM(-1, -1); + + final int divSize; + final int modSize; + + Case(final int divSize, final int modSize) { + this.divSize = divSize; + this.modSize = modSize; + } + } + + @Param({ + "SMOD_32_32", + "SMOD_64_32", + "SMOD_64_64", + "SMOD_128_32", + "SMOD_128_64", + "SMOD_128_128", + "SMOD_192_32", + "SMOD_192_64", + "SMOD_192_128", + "SMOD_192_192", + "SMOD_256_32", + "SMOD_256_64", + "SMOD_256_128", + "SMOD_256_192", + "SMOD_256_256", + "LARGER_SMOD_64_128", + "LARGER_SMOD_192_256", + "ZERO_SMOD_128_0", + "FULL_RANDOM" + }) + private String caseName; + + @Setup(Level.Iteration) + @Override + public void setUp() { + frame = BenchmarkHelper.createMessageCallFrame(); + + Case scenario = Case.valueOf(caseName); + aPool = new Bytes[SAMPLE_SIZE]; + bPool = new Bytes[SAMPLE_SIZE]; + + final ThreadLocalRandom random = ThreadLocalRandom.current(); + int aSize; + int bSize; + + for (int i = 0; i < SAMPLE_SIZE; i++) { + if (scenario.divSize < 0) aSize = random.nextInt(1, 33); + else aSize = scenario.divSize * 4; + if (scenario.modSize < 0) bSize = random.nextInt(1, 33); + else bSize = scenario.modSize * 4; + + final byte[] a = new byte[aSize]; + final byte[] b = new byte[bSize]; + random.nextBytes(a); + random.nextBytes(b); + + // Swap a and b if necessary + if ((scenario.divSize != scenario.modSize)) { + aPool[i] = Bytes.wrap(a); + bPool[i] = Bytes.wrap(b); + } else { + BigInteger aInt = new BigInteger(a); + BigInteger bInt = new BigInteger(b); + if ((aInt.abs().compareTo(bInt.abs()) < 0)) { + aPool[i] = Bytes.wrap(b); + bPool[i] = Bytes.wrap(a); + } else { + aPool[i] = Bytes.wrap(a); + bPool[i] = Bytes.wrap(b); + } + } + } + index = 0; + } @Override protected Operation.OperationResult invoke(final MessageFrame frame) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/UInt256.java b/evm/src/main/java/org/hyperledger/besu/evm/UInt256.java new file mode 100644 index 00000000000..db366b280c5 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/UInt256.java @@ -0,0 +1,756 @@ +/* + * 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.hyperledger.besu.evm; + +import java.util.Arrays; + +/** + * 256-bits wide unsigned integer class. + * + *

This class is an optimised version of BigInteger for fixed width 256-bits integers. + */ +public final class UInt256 { + // region Internals + // -------------------------------------------------------------------------- + // UInt256 is a big-endian up to 256-bits integer. + // Internally, it is represented with int/long limbs in little-endian order. + // Wraps a view over limbs array from 0..length. + // Internally limbs are little-endian and can perhaps have more elements than length. + private final int[] limbs; + private final int length; + + // Maximum number of significant limbs + private static final int N_LIMBS = 8; + // Mask for long values + private static final long MASK_L = 0xFFFFFFFFL; + + // Getters for testing + int length() { + return length; + } + + int[] limbs() { + return limbs; + } + + // -------------------------------------------------------------------------- + // endregion + + // region Preallocating Small Integers + // -------------------------------------------------------------------------- + private static final int nSmallInts = 256; + private static final UInt256[] smallInts = new UInt256[nSmallInts]; + + static { + smallInts[0] = new UInt256(new int[] {}); + for (int i = 1; i < nSmallInts; i++) { + smallInts[i] = new UInt256(new int[] {i}); + } + } + + /** The constant 0. */ + public static final UInt256 ZERO = smallInts[0]; + + /** The constant 1. */ + public static final UInt256 ONE = smallInts[1]; + + /** The constant 2. */ + public static final UInt256 TWO = smallInts[2]; + + /** The constant 10. */ + public static final UInt256 TEN = smallInts[10]; + + /** The constant 16. */ + public static final UInt256 SIXTEEN = smallInts[16]; + + // -------------------------------------------------------------------------- + // endregion + + // region Constructors + // -------------------------------------------------------------------------- + + UInt256(final int[] limbs, final int length) { + this.limbs = limbs; + this.length = length; + // Unchecked length: assumes length is properly set. + } + + /** + * Instantiates a new UInt256 from limbs (int[]). + * + * @param limbs integer limbs in little-endian order. + */ + public UInt256(final int[] limbs) { + int i = Math.min(limbs.length, N_LIMBS) - 1; + while ((i >= 0) && (limbs[i] == 0)) i--; + this.limbs = limbs; + this.length = i + 1; + } + + /** + * Instantiates a new UInt256 from byte[]. + * + * @param bytes raw bytes in BigEndian order. + * @return Big-endian UInt256 represented by the bytes. + */ + public static UInt256 fromBytesBE(final byte[] bytes) { + int offset = 0; + while ((offset < bytes.length) && (bytes[offset] == 0x00)) ++offset; + int nBytes = bytes.length - offset; + if (nBytes == 0) return ZERO; + int len = (nBytes + 3) / 4; + int[] limbs = new int[len]; + // int[] limbs = new int[N_LIMBS]; + + int i; + int base; + // Up to most significant limb take 4 bytes. + for (i = 0, base = bytes.length - 4; i < len - 1; ++i, base = base - 4) { + limbs[i] = + (bytes[base] << 24) + | ((bytes[base + 1] & 0xFF) << 16) + | ((bytes[base + 2] & 0xFF) << 8) + | ((bytes[base + 3] & 0xFF)); + } + // Last effective limb + limbs[i] = + switch (nBytes - i * 4) { + case 1 -> ((bytes[offset] & 0xFF)); + case 2 -> (((bytes[offset] & 0xFF) << 8) | (bytes[offset + 1] & 0xFF)); + case 3 -> + (((bytes[offset] & 0xFF) << 16) + | ((bytes[offset + 1] & 0xFF) << 8) + | (bytes[offset + 2] & 0xFF)); + case 4 -> + ((bytes[offset] << 24) + | ((bytes[offset + 1] & 0xFF) << 16) + | ((bytes[offset + 2] & 0xFF) << 8) + | (bytes[offset + 3] & 0xFF)); + default -> throw new IllegalStateException("Unexpected value"); + }; + return new UInt256(limbs, len); + } + + /** + * Instantiates a new UInt256 from byte[]. + * + * @param bytes raw bytes in BigEndian order. + * @return Big-endian UInt256 represented by the bytes. + */ + public static UInt256 fromSignedBytesBE(final byte[] bytes) { + int nBytes = bytes.length; + if (nBytes == 0) return ZERO; + int len = (nBytes + 3) / 4; + int[] limbs = new int[len]; + // int[] limbs = new int[N_LIMBS]; + + int i; + int base; + // Up to most significant limb take 4 bytes. + for (i = 0, base = bytes.length - 4; i < len - 1; ++i, base = base - 4) { + limbs[i] = + (bytes[base] << 24) + | ((bytes[base + 1] & 0xFF) << 16) + | ((bytes[base + 2] & 0xFF) << 8) + | ((bytes[base + 3] & 0xFF)); + } + // Last effective limb + limbs[i] = + switch (nBytes - i * 4) { + case 1 -> ((bytes[0])); + case 2 -> (((bytes[0]) << 8) | (bytes[1] & 0xFF)); + case 3 -> (((bytes[0]) << 16) | ((bytes[1] & 0xFF) << 8) | (bytes[2] & 0xFF)); + case 4 -> + ((bytes[0] << 24) + | ((bytes[1] & 0xFF) << 16) + | ((bytes[2] & 0xFF) << 8) + | (bytes[3] & 0xFF)); + default -> throw new IllegalStateException("Unexpected value"); + }; + return new UInt256(limbs, len); + } + + /** + * Instantiates a new UInt256 from an int. + * + * @param value int value to convert to UInt256. + * @return The UInt256 equivalent of value. + */ + public static UInt256 fromInt(final int value) { + if (0 <= value && value < nSmallInts) return smallInts[value]; + return new UInt256(new int[] {value}, 1); + } + + /** + * Instantiates a new UInt256 from a long. + * + * @param value long value to convert to UInt256. + * @return The UInt256 equivalent of value. + */ + public static UInt256 fromLong(final long value) { + if (0 <= value && value < nSmallInts) return smallInts[(int) value]; + return new UInt256(new int[] {(int) value, (int) (value >>> 32)}); + } + + // -------------------------------------------------------------------------- + // endregion + + // region Conversions + // -------------------------------------------------------------------------- + /** + * Convert to int. + * + * @return Value truncated to an int, possibly lossy. + */ + public int intValue() { + return (limbs.length == 0 ? 0 : limbs[0]); + } + + /** + * Convert to long. + * + * @return Value truncated to a long, possibly lossy. + */ + public long longValue() { + switch (length) { + case 0 -> { + return 0L; + } + case 1 -> { + return (limbs[0] & MASK_L); + } + default -> { + return (limbs[0] & MASK_L) | ((limbs[1] & MASK_L) << 32); + } + } + } + + /** + * Convert to BigEndian byte array. + * + * @return Big-endian ordered bytes for this UInt256 value. + */ + public byte[] toBytesBE() { + byte[] out = new byte[32]; + for (int i = 0, offset = 28; i < length; i++, offset -= 4) { + int v = limbs[i]; + out[offset] = (byte) (v >>> 24); + out[offset + 1] = (byte) (v >>> 16); + out[offset + 2] = (byte) (v >>> 8); + out[offset + 3] = (byte) v; + } + return out; + } + + /** + * Convert to BigEndian byte array. + * + * @return Big-endian ordered bytes for this UInt256 value. + */ + public byte[] toSignedBytesBE() { + byte[] out = new byte[32]; + if (length > 0 && limbs[length - 1] < 0) Arrays.fill(out, (byte) 0xFF); + for (int i = 0, offset = 28; i < length; i++, offset -= 4) { + int v = limbs[i]; + out[offset] = (byte) (v >>> 24); + out[offset + 1] = (byte) (v >>> 16); + out[offset + 2] = (byte) (v >>> 8); + out[offset + 3] = (byte) v; + } + return out; + } + + /** + * Convert to BigEndian byte array. + * + * @param sign if sign is negative, pad with 1s, else pad with 0s. + * @return Big-endian ordered bytes for this UInt256 value. + */ + public byte[] toSignedBytesBE(final int sign) { + byte[] out = new byte[32]; + if (sign < 0) Arrays.fill(out, (byte) 0xFF); + for (int i = 0, offset = 28; i < length; i++, offset -= 4) { + int v = limbs[i]; + out[offset] = (byte) (v >>> 24); + out[offset + 1] = (byte) (v >>> 16); + out[offset + 2] = (byte) (v >>> 8); + out[offset + 3] = (byte) v; + } + return out; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("0x"); + byte[] out = new byte[length * 4]; + for (int i = 0, offset = 4 * (length - 1); i < length; i++, offset -= 4) { + int v = limbs[i]; + out[offset] = (byte) (v >>> 24); + out[offset + 1] = (byte) (v >>> 16); + out[offset + 2] = (byte) (v >>> 8); + out[offset + 3] = (byte) v; + } + for (byte b : out) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + /** + * Convert to hexstring according to sign. + * + *

If sign is negative, pad with 1s, else pad with 0s. + * + * @param sign padding with 0s or 1s whether sign is non-negative. + * @return HexString + */ + public String toString(final int sign) { + if (sign >= 0) return toString(); + StringBuilder sb = new StringBuilder("0x"); + byte[] out = new byte[length * 4]; + Arrays.fill(out, (byte) 0xFF); + for (int i = 0, offset = 4 * (length - 1); i < length; i++, offset -= 4) { + int v = limbs[i]; + out[offset] = (byte) (v >>> 24); + out[offset + 1] = (byte) (v >>> 16); + out[offset + 2] = (byte) (v >>> 8); + out[offset + 3] = (byte) v; + } + for (byte b : out) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + // -------------------------------------------------------------------------- + // endregion + + // region Comparisons + // -------------------------------------------------------------------------- + + /** + * Is the value 0 ? + * + * @return true if this UInt256 value is 0. + */ + public boolean isZero() { + if (length == 0) return true; + for (int i = 0; i < length; i++) { + if (limbs[i] != 0) return false; + } + return true; + } + + /** + * Compares two UInt256. + * + * @param a left UInt256 + * @param b right UInt256 + * @return 0 if a == b, negative if a < b and positive if a > b. + */ + public static int compare(final UInt256 a, final UInt256 b) { + int comp = Integer.compare(a.length, b.length); + if (comp != 0) return comp; + for (int i = a.length - 1; i >= 0; i--) { + comp = Integer.compareUnsigned(a.limbs[i], b.limbs[i]); + if (comp != 0) return comp; + } + return 0; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (!(obj instanceof UInt256)) return false; + UInt256 other = (UInt256) obj; + + // Compare lengths after trimming leading zero limbs + int cmp = UInt256.compare(this, other); + return cmp == 0; + } + + @Override + public int hashCode() { + int h = 1; + for (int i = 0; i < length; i++) { + h = 31 * h + limbs[i]; + } + return h; + } + + // -------------------------------------------------------------------------- + // endregion + + // region Bitwise Operations + // -------------------------------------------------------------------------- + + /** + * Shifts value to the left. + * + * @param shift number of bits to shift. If negative, shift right instead. + * @return Shifted UInt256 value. + */ + public UInt256 shiftLeft(final int shift) { + if (shift >= 256) return ZERO; + if (shift < 0) return shiftRight(-shift); + if (shift == 0 || isZero()) return this; + int nDiffBits = shift - numberOfLeadingZeros(this.limbs, this.length); + int size = Math.min(this.length + (nDiffBits + 31) / 32, N_LIMBS); + int[] shifted = new int[size]; + shiftLeftInto(shifted, this.limbs, shift); + return new UInt256(shifted, size); + } + + /** + * Shifts value to the right. + * + * @param shift number of bits to shift. If negative, shift left instead. + * @return Shifted UInt256 value. + */ + public UInt256 shiftRight(final int shift) { + if (shift < 0) return shiftLeft(-shift); + if (isZero()) return ZERO; + int[] shifted = new int[this.length]; + shiftRightInto(shifted, this.limbs, shift); + return new UInt256(shifted); + } + + // -------------------------------------------------------------------------- + // endregion + + // region Arithmetic Operations + // -------------------------------------------------------------------------- + + /** + * (Signed) absolute value + * + * @return The absolute value of this signed integer. + */ + public UInt256 abs() { + int[] newLimbs = Arrays.copyOf(limbs, length); + absInplace(newLimbs); + return new UInt256(newLimbs, length); + } + + /** + * Addition (modulo 2**256). + * + * @param other The integer to add. + * @return The sum of this with other. + */ + public UInt256 add(final UInt256 other) { + if (other.isZero()) return this; + if (this.isZero()) return other; + return new UInt256(addWithCarry(this.limbs, other.limbs)); + } + + /** + * Multiplication (modulo 2**256). + * + * @param other The integer to add. + * @return The sum of this with other. + */ + public UInt256 mul(final UInt256 other) { + if (this.isZero() || other.isZero()) return ZERO; + int[] result = new int[this.length + other.length + 1]; + addMul(result, this.limbs, other.limbs); + return new UInt256(result); + } + + /** + * Unsigned modulo reduction. + * + * @param modulus The modulus of the reduction + * @return The remainder modulo {@code modulus}. + */ + public UInt256 mod(final UInt256 modulus) { + int cmp = compare(this, modulus); + if (cmp < 0) return this; + if (cmp == 0 || modulus.isZero() || this.isZero()) return ZERO; + return new UInt256(knuthRemainder(this.limbs, modulus.limbs)); + } + + /** + * Signed modulo reduction. + * + * @param modulus The modulus of the reduction + * @return The remainder modulo {@code modulus}. + */ + public UInt256 signedMod(final UInt256 modulus) { + if (this.isZero() || modulus.isZero()) return ZERO; + int[] x = new int[this.length]; + int[] y = new int[modulus.length]; + absInto(x, this.limbs); + absInto(y, modulus.limbs); + int[] r = knuthRemainder(x, y); + if (isNegative(this.limbs)) { + int[] s = new int[N_LIMBS]; + System.arraycopy(r, 0, s, 0, r.length); + negate(s); + return new UInt256(s); + } + return new UInt256(r); + } + + /** + * Modular addition. + * + * @param other The integer to add to this. + * @param modulus The modulus of the reduction. + * @return This integer this + other (mod modulus). + */ + public UInt256 addMod(final UInt256 other, final UInt256 modulus) { + if (modulus.isZero()) return ZERO; + if (this.isZero()) return other.mod(modulus); + if (other.isZero()) return this.mod(modulus); + int[] sum = addWithCarry(this.limbs, other.limbs); + int[] rem = knuthRemainder(sum, modulus.limbs); + return new UInt256(rem); + } + + /** + * Modular multiplication. + * + * @param other The integer to add to this. + * @param modulus The modulus of the reduction. + * @return This integer this + other (mod modulus). + */ + public UInt256 mulMod(final UInt256 other, final UInt256 modulus) { + if (this.isZero() || other.isZero() || modulus.isZero()) return ZERO; + int[] result = new int[this.length + other.length + 1]; + addMul(result, this.limbs, other.limbs); + result = knuthRemainder(result, modulus.limbs); + return new UInt256(result); + } + + // -------------------------------------------------------------------------- + // endregion + + // region Support (private) Algorithms + // -------------------------------------------------------------------------- + private static boolean isNegative(final int[] x) { + return x[x.length - 1] < 0; + } + + private static void negate(final int[] x) { + int carry = 1; + for (int i = 0; i < x.length; i++) { + x[i] = ~x[i] + carry; + carry = (x[i] == 0 && carry == 1) ? 1 : 0; + } + } + + private static void absInplace(final int[] x) { + if (isNegative(x)) negate(x); + } + + private static void absInto(final int[] dst, final int[] src) { + System.arraycopy(src, 0, dst, 0, Math.min(dst.length, src.length)); + absInplace(dst); + } + + private static int numberOfLeadingZeros(final int[] x, final int limit) { + int leadingIndex = limit - 1; + while ((leadingIndex >= 0) && (x[leadingIndex] == 0)) leadingIndex--; + return 32 * (limit - leadingIndex - 1) + Integer.numberOfLeadingZeros(x[leadingIndex]); + } + + private static int numberOfLeadingZeros(final int[] x) { + return numberOfLeadingZeros(x, x.length); + } + + private static void shiftLeftInto(final int[] result, final int[] x, final int shift) { + // Unchecked: result should be initialised with zeroes + int limbShift = shift / 32; + int bitShift = shift % 32; + int nLimbs = Math.min(x.length, result.length - limbShift); + if (limbShift >= result.length) return; + if (bitShift == 0) { + System.arraycopy(x, 0, result, limbShift, nLimbs); + return; + } + + int j = limbShift; + int carry = 0; + for (int i = 0; i < nLimbs; ++i, ++j) { + result[j] = (x[i] << bitShift) | carry; + carry = x[i] >>> (32 - bitShift); + } + if (carry != 0 && j < result.length) result[j] = carry; // last carry + } + + private static void shiftRightInto(final int[] result, final int[] x, final int shift) { + int limbShift = shift / 32; + int bitShift = shift % 32; + int nLimbs = Math.min(x.length - limbShift, result.length); + + if (limbShift >= x.length) return; + if (bitShift == 0) { + System.arraycopy(x, limbShift, result, 0, nLimbs); + return; + } + + int carry = 0; + for (int i = nLimbs - 1 + limbShift, j = nLimbs - 1; j >= 0; i--, j--) { + int r = (x[i] >>> bitShift) | carry; + result[j] = r; + carry = x[i] << (32 - bitShift); + } + } + + private static int[] addWithCarry(final int[] x, final int[] y) { + // Step 1: Add with carry + int[] a; + int[] b; + if (x.length < y.length) { + a = y; + b = x; + } else { + a = x; + b = y; + } + int maxLen = a.length; + int minLen = b.length; + int[] sum = new int[maxLen + 1]; + long carry = 0; + for (int i = 0; i < minLen; i++) { + long ai = a[i] & MASK_L; + long bi = b[i] & MASK_L; + long s = ai + bi + carry; + sum[i] = (int) s; + carry = s >>> 32; + } + int icarry = (int) carry; + for (int i = minLen; i < maxLen; i++) { + sum[i] = a[i] + icarry; + icarry = (a[i] != 0 && sum[i] == 0) ? 1 : 0; + } + sum[maxLen] = icarry; + return sum; + } + + private static void addMul(final int[] lhs, final int[] a, final int[] b) { + // Shortest in outer loop, swap if needed + int[] x; + int[] y; + if (a.length < b.length) { + x = b; + y = a; + } else { + x = a; + y = b; + } + // x: widening int -> long + long[] xl = new long[x.length]; + for (int i = 0; i < xl.length; i++) { + xl[i] = x[i] & MASK_L; + } + // Main algo + for (int i = 0; i < y.length; i++) { + long carry = 0; + long yi = y[i] & MASK_L; + + int k = i; + for (int j = 0; j < x.length; j++, k++) { + long prod = yi * xl[j]; + long sum = (lhs[k] & MASK_L) + prod + carry; + lhs[k] = (int) sum; + carry = sum >>> 32; + } + + // propagate leftover carry + while (carry != 0 && k < lhs.length) { + long sum = (lhs[k] & MASK_L) + carry; + lhs[k] = (int) sum; + carry = sum >>> 32; + k++; + } + } + } + + private static int[] knuthRemainder(final int[] dividend, final int[] modulus) { + int shift = numberOfLeadingZeros(modulus); + int limbShift = shift / 32; + int n = modulus.length - limbShift; + if (n == 0) return new int[0]; + if (n == 1) { + if (dividend.length == 1) + return (new int[] {Integer.remainderUnsigned(dividend[0], modulus[0])}); + long d = modulus[0] & MASK_L; + long rem = 0; + // Process from most significant limb downwards + for (int i = dividend.length - 1; i >= 0; i--) { + long cur = (rem << 32) | (dividend[i] & MASK_L); + rem = Long.remainderUnsigned(cur, d); + } + return (new int[] {(int) rem}); + } + // Normalize + int m = dividend.length - n; + int bitShift = shift % 32; + int[] vLimbs = new int[n]; + shiftLeftInto(vLimbs, modulus, bitShift); + int[] uLimbs = new int[dividend.length + 1]; + shiftLeftInto(uLimbs, dividend, bitShift); + + // Main division loop + long vn1 = vLimbs[n - 1] & MASK_L; + long vn2 = vLimbs[n - 2] & MASK_L; + for (int j = m; j >= 0; j--) { + long ujn = (uLimbs[j + n] & MASK_L); + long ujn1 = (uLimbs[j + n - 1] & MASK_L); + long ujn2 = (uLimbs[j + n - 2] & MASK_L); + + long dividendPart = (ujn << 32) | ujn1; + // Check that no need for Unsigned version of divrem. + long qhat = Long.divideUnsigned(dividendPart, vn1); + long rhat = Long.remainderUnsigned(dividendPart, vn1); + + while (qhat == 0x1_0000_0000L || Long.compareUnsigned(qhat * vn2, (rhat << 32) | ujn2) > 0) { + qhat--; + rhat += vn1; + if (rhat >= 0x1_0000_0000L) break; + } + + // Multiply-subtract qhat*v from u slice + long borrow = 0; + for (int i = 0; i < n; i++) { + long prod = (vLimbs[i] & MASK_L) * qhat; + long sub = (uLimbs[i + j] & MASK_L) - (prod & MASK_L) - borrow; + uLimbs[i + j] = (int) sub; + borrow = (prod >>> 32) - (sub >> 32); + } + long sub = (uLimbs[j + n] & MASK_L) - borrow; + uLimbs[j + n] = (int) sub; + + if (sub < 0) { + // Add back + long carry = 0; + for (int i = 0; i < n; i++) { + long sum = (uLimbs[i + j] & MASK_L) + (vLimbs[i] & MASK_L) + carry; + uLimbs[i + j] = (int) sum; + carry = sum >>> 32; + } + uLimbs[j + n] = (int) (uLimbs[j + n] + carry); + } + } + // Unnormalize remainder + int[] shifted = new int[n]; + shiftRightInto(shifted, uLimbs, bitShift); + return shifted; + } + // -------------------------------------------------------------------------- + // endregion +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java index 4e21e36bc20..ceb5a6d0ab2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java @@ -15,12 +15,10 @@ package org.hyperledger.besu.evm.operation; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import java.math.BigInteger; -import java.util.Arrays; - import org.apache.tuweni.bytes.Bytes; /** The Add mod operation. */ @@ -50,29 +48,22 @@ public Operation.OperationResult executeFixedCostOperation( * @return the operation result */ public static OperationResult staticOperation(final MessageFrame frame) { + Bytes resultBytes; final Bytes value0 = frame.popStackItem(); final Bytes value1 = frame.popStackItem(); final Bytes value2 = frame.popStackItem(); if (value2.isZero()) { - frame.pushStackItem(Bytes.EMPTY); + resultBytes = Bytes.EMPTY; } else { - BigInteger b0 = new BigInteger(1, value0.toArrayUnsafe()); - BigInteger b1 = new BigInteger(1, value1.toArrayUnsafe()); - BigInteger b2 = new BigInteger(1, value2.toArrayUnsafe()); - - BigInteger result = b0.add(b1).mod(b2); - Bytes resultBytes = Bytes.wrap(result.toByteArray()); - if (resultBytes.size() > 32) { - resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); - } - - final byte[] padding = new byte[32 - resultBytes.size()]; - Arrays.fill(padding, result.signum() < 0 ? (byte) 0xFF : 0x00); - - frame.pushStackItem(Bytes.concatenate(Bytes.wrap(padding), resultBytes)); + UInt256 b0 = UInt256.fromBytesBE(value0.toArrayUnsafe()); + UInt256 b1 = UInt256.fromBytesBE(value1.toArrayUnsafe()); + UInt256 b2 = UInt256.fromBytesBE(value2.toArrayUnsafe()); + resultBytes = Bytes.wrap(b0.addMod(b1, b2).toBytesBE()); } + + frame.pushStackItem(resultBytes); return addModSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ModOperation.java index e61495e9f5b..0ed5a884d84 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ModOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ModOperation.java @@ -15,12 +15,10 @@ package org.hyperledger.besu.evm.operation; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import java.math.BigInteger; -import java.util.Arrays; - import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -53,24 +51,15 @@ public Operation.OperationResult executeFixedCostOperation( public static OperationResult staticOperation(final MessageFrame frame) { final Bytes value0 = frame.popStackItem(); final Bytes value1 = frame.popStackItem(); + Bytes resultBytes; if (value1.isZero()) { - frame.pushStackItem(Bytes32.ZERO); + resultBytes = (Bytes) Bytes32.ZERO; } else { - BigInteger b1 = new BigInteger(1, value0.toArrayUnsafe()); - BigInteger b2 = new BigInteger(1, value1.toArrayUnsafe()); - final BigInteger result = b1.mod(b2); - - Bytes resultBytes = Bytes.wrap(result.toByteArray()); - if (resultBytes.size() > 32) { - resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); - } - - final byte[] padding = new byte[32 - resultBytes.size()]; - Arrays.fill(padding, result.signum() < 0 ? (byte) 0xFF : 0x00); - - frame.pushStackItem(Bytes.concatenate(Bytes.wrap(padding), resultBytes)); + UInt256 b0 = UInt256.fromBytesBE(value0.toArrayUnsafe()); + UInt256 b1 = UInt256.fromBytesBE(value1.toArrayUnsafe()); + resultBytes = Bytes.wrap(b0.mod(b1).toBytesBE()); } - + frame.pushStackItem(resultBytes); return modSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/MulModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/MulModOperation.java index 589ef9570be..bbdd55c9c09 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/MulModOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/MulModOperation.java @@ -15,12 +15,10 @@ package org.hyperledger.besu.evm.operation; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import java.math.BigInteger; -import java.util.Arrays; - import org.apache.tuweni.bytes.Bytes; /** The Mul mod operation. */ @@ -54,25 +52,17 @@ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes value1 = frame.popStackItem(); final Bytes value2 = frame.popStackItem(); + Bytes resultBytes; if (value2.isZero()) { - frame.pushStackItem(Bytes.EMPTY); + resultBytes = Bytes.EMPTY; } else { - BigInteger b0 = new BigInteger(1, value0.toArrayUnsafe()); - BigInteger b1 = new BigInteger(1, value1.toArrayUnsafe()); - BigInteger b2 = new BigInteger(1, value2.toArrayUnsafe()); - - BigInteger result = b0.multiply(b1).mod(b2); - Bytes resultBytes = Bytes.wrap(result.toByteArray()); - if (resultBytes.size() > 32) { - resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); - } - - final byte[] padding = new byte[32 - resultBytes.size()]; - Arrays.fill(padding, result.signum() < 0 ? (byte) 0xFF : 0x00); - - frame.pushStackItem(Bytes.concatenate(Bytes.wrap(padding), resultBytes)); + UInt256 b0 = UInt256.fromBytesBE(value0.toArrayUnsafe()); + UInt256 b1 = UInt256.fromBytesBE(value1.toArrayUnsafe()); + UInt256 b2 = UInt256.fromBytesBE(value2.toArrayUnsafe()); + resultBytes = Bytes.wrap(b0.mulMod(b1, b2).toBytesBE()); } + frame.pushStackItem(resultBytes); return mulModSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SModOperation.java index e210aec6069..88bd6eb31fb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SModOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SModOperation.java @@ -15,13 +15,12 @@ package org.hyperledger.besu.evm.operation; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.UInt256; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import java.math.BigInteger; -import java.util.Arrays; - import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; /** The SMod operation. */ public class SModOperation extends AbstractFixedCostOperation { @@ -53,32 +52,15 @@ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes value0 = frame.popStackItem(); final Bytes value1 = frame.popStackItem(); + Bytes resultBytes; if (value1.isZero()) { - frame.pushStackItem(Bytes.EMPTY); + resultBytes = (Bytes) Bytes32.ZERO; } else { - final BigInteger b1 = - value0.size() < 32 - ? new BigInteger(1, value0.toArrayUnsafe()) - : new BigInteger(value0.toArrayUnsafe()); - final BigInteger b2 = - value1.size() < 32 - ? new BigInteger(1, value1.toArrayUnsafe()) - : new BigInteger(value1.toArrayUnsafe()); - BigInteger result = b1.abs().mod(b2.abs()); - if (b1.signum() < 0) { - result = result.negate(); - } - - Bytes resultBytes = Bytes.wrap(result.toByteArray()); - if (resultBytes.size() > 32) { - resultBytes = resultBytes.slice(resultBytes.size() - 32, 32); - } - - final byte[] padding = new byte[32 - resultBytes.size()]; - Arrays.fill(padding, result.signum() < 0 ? (byte) 0xFF : 0x00); - - frame.pushStackItem(Bytes.concatenate(Bytes.wrap(padding), resultBytes)); + UInt256 b0 = UInt256.fromSignedBytesBE(value0.toArrayUnsafe()); + UInt256 b1 = UInt256.fromSignedBytesBE(value1.toArrayUnsafe()); + resultBytes = Bytes.wrap(b0.signedMod(b1).toBytesBE()); } + frame.pushStackItem(resultBytes); return smodSuccess; } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/UInt256ParameterisedTest.java b/evm/src/test/java/org/hyperledger/besu/evm/UInt256ParameterisedTest.java new file mode 100644 index 00000000000..0377ee7c39f --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/UInt256ParameterisedTest.java @@ -0,0 +1,455 @@ +/* + * Copyright contributors to Besu. + * + * 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.hyperledger.besu.evm; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class UInt256ParameterisedTest { + + // Test constants + private static final BigInteger TWO_TO_64 = BigInteger.TWO.pow(64); + private static final BigInteger TWO_TO_128 = BigInteger.TWO.pow(128); + private static final BigInteger TWO_TO_192 = BigInteger.TWO.pow(192); + private static final BigInteger TWO_TO_256 = BigInteger.TWO.pow(256); + private static final BigInteger UINT256_MAX = TWO_TO_256.subtract(BigInteger.ONE); + + private static final int RANDOM_TEST_COUNT = 6; + + // region Test Data Providers + + /** Provides unary test cases (single BigInteger values). */ + static Stream provideUnaryTestCases() { + List cases = new ArrayList<>(); + + // Basic values + cases.add(BigInteger.ZERO); + cases.add(BigInteger.ONE); + cases.add(BigInteger.TWO); + cases.add(BigInteger.valueOf(3)); + + // Boundary values + cases.add(BigInteger.valueOf(Short.MAX_VALUE)); + cases.add(BigInteger.valueOf(0xFFFF - 1)); // UnsignedShort.MAX_VALUE - 1 + cases.add(BigInteger.valueOf(0xFFFF)); // UnsignedShort.MAX_VALUE + cases.add(BigInteger.valueOf(0xFFFF + 1)); // UnsignedShort.MAX_VALUE + 1 + cases.add(BigInteger.valueOf(Integer.MAX_VALUE)); + cases.add(BigInteger.valueOf(0xFFFFFFFFL - 1)); // UnsignedInteger.MAX_VALUE - 1 + cases.add(BigInteger.valueOf(0xFFFFFFFFL)); // UnsignedInteger.MAX_VALUE + cases.add(BigInteger.valueOf(0xFFFFFFFFL + 1)); // UnsignedInteger.MAX_VALUE + 1 + cases.add(BigInteger.valueOf(Long.MAX_VALUE)); + + // Large values + cases.add(new BigInteger("FFFFFFFFFFFFFFFE", 16)); // UnsignedLong.MAX_VALUE - 1 + cases.add(new BigInteger("FFFFFFFFFFFFFFFF", 16)); // UnsignedLong.MAX_VALUE + cases.add(new BigInteger("080000000000000008000000000000001", 16)); + cases.add(TWO_TO_64); + cases.add(TWO_TO_128); + cases.add(TWO_TO_192); + cases.add(TWO_TO_128.subtract(BigInteger.ONE)); // UInt128Max + cases.add(TWO_TO_192.subtract(BigInteger.ONE)); // UInt192Max + cases.add(UINT256_MAX); + + // Add random values + cases.addAll(generateRandomUnsigned(RANDOM_TEST_COUNT)); + + return cases.stream(); + } + + /** Provides unary test cases with signed interpretation (for SMod tests). */ + static Stream provideSignedUnaryTestCases() { + List cases = new ArrayList<>(); + + // Basic values + cases.add(BigInteger.ZERO); + cases.add(BigInteger.ONE); + cases.add(BigInteger.TWO); + cases.add(BigInteger.valueOf(3)); + + // Boundary values + cases.add(BigInteger.valueOf(Short.MAX_VALUE)); + cases.add(BigInteger.valueOf(0xFFFF)); // UnsignedShort.MAX_VALUE + cases.add(BigInteger.valueOf(Integer.MAX_VALUE)); + cases.add(BigInteger.valueOf(0xFFFFFFFFL)); // UnsignedInteger.MAX_VALUE + cases.add(BigInteger.valueOf(Long.MAX_VALUE)); + + // Critical test cases: Values with MSB of top limb set, but positive in 256-bit space + // These expose bugs in isNegative() when stored in fewer than 8 limbs + cases.add(new BigInteger("80000000", 16)); // 32-bit: bit 31 set, but bit 255 clear + cases.add(new BigInteger("80000001", 16)); // 32-bit: bit 31 set, but bit 255 clear + cases.add(new BigInteger("8000000000000000", 16)); // 64-bit: bit 63 set, but bit 255 clear + cases.add(new BigInteger("8000000000000001", 16)); // 64-bit: bit 63 set, but bit 255 clear + + // Large values - unsigned interpretation + cases.add(new BigInteger("FFFFFFFFFFFFFFFF", 16)); // UnsignedLong.MAX_VALUE + cases.add(new BigInteger("080000000000000008000000000000001", 16)); + cases.add( + new BigInteger("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", 16)); + cases.add( + new BigInteger("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16)); + cases.add(TWO_TO_64); + cases.add(TWO_TO_128); + cases.add(TWO_TO_192); + cases.add(TWO_TO_128.subtract(BigInteger.ONE)); // UInt128Max + cases.add(TWO_TO_192.subtract(BigInteger.ONE)); // UInt192Max + + // Int256 boundaries + BigInteger INT256_MAX = BigInteger.ONE.shiftLeft(255).subtract(BigInteger.ONE); // 2^255 - 1 + BigInteger INT256_MIN = INT256_MAX.negate(); // -(2^255 - 1) + cases.add(INT256_MAX); + cases.add(INT256_MIN); + + cases.add(UINT256_MAX); + + // Add random signed values + cases.addAll(generateRandomSigned(RANDOM_TEST_COUNT)); + + return cases.stream(); + } + + /** Provides signed binary test cases (pairs of BigInteger values for signed operations). */ + static Stream provideSignedBinaryTestCases() { + List unary = provideSignedUnaryTestCases().toList(); + List binary = new ArrayList<>(); + + for (BigInteger a : unary) { + for (BigInteger b : unary) { + binary.add(Arguments.of(a, b)); + } + } + + return binary.stream(); + } + + /** Provides binary test cases (pairs of BigInteger values). */ + static Stream provideBinaryTestCases() { + List unary = provideUnaryTestCases().toList(); + List binary = new ArrayList<>(); + + for (BigInteger a : unary) { + for (BigInteger b : unary) { + binary.add(Arguments.of(a, b)); + } + } + + return binary.stream(); + } + + /** Provides ternary test cases (triples of BigInteger values). */ + static Stream provideTernaryTestCases() { + List binary = provideBinaryTestCases().toList(); + List unary = provideUnaryTestCases().toList(); + List ternary = new ArrayList<>(); + + for (Arguments binArgs : binary) { + BigInteger a = (BigInteger) binArgs.get()[0]; + BigInteger b = (BigInteger) binArgs.get()[1]; + for (BigInteger c : unary) { + ternary.add(Arguments.of(a, b, c)); + } + } + + return ternary.stream(); + } + + /** Provides shift test cases (BigInteger value and int shift amount). */ + static Stream provideShiftTestCases() { + List unary = provideUnaryTestCases().toList(); + List shifts = new ArrayList<>(); + + for (BigInteger value : unary) { + for (int shift = 0; shift <= 256; shift++) { + shifts.add(Arguments.of(value, shift)); + } + } + + return shifts.stream(); + } + + // endregion + + // region Helper Methods + + /** Converts BigInteger to UInt256, wrapping to 256-bit range. */ + private static UInt256 toUInt256(final BigInteger value) { + BigInteger wrapped = value.mod(TWO_TO_256); + return fromBigInteger(wrapped); + } + + /** + * Create UInt256 from BigInteger. + * + * @param value BigInteger value to convert (must be non-negative and <= 2^256-1) + * @return UInt256 representation of the BigInteger value. + * @throws IllegalArgumentException if value is negative or exceeds 256 bits + */ + private static UInt256 fromBigInteger(final java.math.BigInteger value) { + if (value.signum() < 0) { + throw new IllegalArgumentException("UInt256 cannot represent negative values"); + } + if (value.bitLength() > 256) { + throw new IllegalArgumentException("Value exceeds 256 bits"); + } + if (value.equals(java.math.BigInteger.ZERO)) return UInt256.ZERO; + + byte[] bytes = value.toByteArray(); + // Remove sign byte if present + int offset = 0; + if (bytes.length > 32 || (bytes.length > 0 && bytes[0] == 0)) { + offset = bytes.length - 32; + if (offset < 0) { + // Need to pad with zeros + byte[] padded = new byte[32]; + System.arraycopy(bytes, 0, padded, 32 - bytes.length, bytes.length); + return UInt256.fromBytesBE(padded); + } + } + + return UInt256.fromBytesBE(java.util.Arrays.copyOfRange(bytes, offset, bytes.length)); + } + + private static BigInteger toBigInteger(final UInt256 value) { + if (value.isZero()) return java.math.BigInteger.ZERO; + byte[] bytes = value.toBytesBE(); + return new java.math.BigInteger(1, bytes); + } + + /** Generates random unsigned 256-bit BigInteger values. */ + private static List generateRandomUnsigned(final int count) { + List randoms = new ArrayList<>(); + Random rand = new Random(12345); + byte[] data = new byte[32]; + + for (int i = 0; i < count; i++) { + rand.nextBytes(data); + data[data.length - 1] &= 0x7F; // Clear sign bit to ensure positive + randoms.add(new BigInteger(1, data)); + } + + return randoms; + } + + /** Generates random signed 256-bit BigInteger values (can be positive or negative). */ + private static List generateRandomSigned(final int count) { + List randoms = new ArrayList<>(); + Random rand = new Random(12345); // Same seed as unsigned for consistency + byte[] data = new byte[32]; // 256 bits = 32 bytes + + for (int i = 0; i < count; i++) { + rand.nextBytes(data); + // Don't clear sign bit - allow negative values + randoms.add(new BigInteger(data)); + } + + return randoms; + } + + /** Wraps result to 256-bit range. */ + private static BigInteger wrap256(final BigInteger value) { + return value.mod(TWO_TO_256); + } + + // endregion + + // region Arithmetic Operations Tests + + @ParameterizedTest + @MethodSource("provideBinaryTestCases") + void testAdd(final BigInteger a, final BigInteger b) { + BigInteger expected = wrap256(a.add(b)); + + UInt256 uint256a = toUInt256(a); + UInt256 uint256b = toUInt256(b); + UInt256 result = uint256a.add(uint256b); + + assertThat(toBigInteger(result)).isEqualTo(expected); + } + + @ParameterizedTest + @MethodSource("provideBinaryTestCases") + void testMul(final BigInteger a, final BigInteger b) { + BigInteger expected = wrap256(a.multiply(b)); + + UInt256 uint256a = toUInt256(a); + UInt256 uint256b = toUInt256(b); + UInt256 result = uint256a.mul(uint256b); + + assertThat(toBigInteger(result)).isEqualTo(expected); + } + + @ParameterizedTest + @MethodSource("provideBinaryTestCases") + void testMod(final BigInteger a, final BigInteger b) { + if (b.equals(BigInteger.ZERO)) { + return; // Skip division by zero + } + + BigInteger expected = wrap256(a.mod(b)); + + UInt256 uint256a = toUInt256(a); + UInt256 uint256b = toUInt256(b); + UInt256 result = uint256a.mod(uint256b); + + assertThat(toBigInteger(result)).isEqualTo(expected); + } + + @ParameterizedTest + @MethodSource("provideSignedBinaryTestCases") + void testSMod(final BigInteger a, final BigInteger b) { + if (b.equals(BigInteger.ZERO)) { + return; // Skip division by zero + } + + Bytes32 expected; + BigInteger rem; + if (BigInteger.ZERO.compareTo(b) == 0) expected = Bytes32.ZERO; + else { + rem = a.abs().mod(b.abs()); + if ((a.compareTo(BigInteger.ZERO) < 0) && (rem.compareTo(BigInteger.ZERO) != 0)) { + rem = rem.negate(); + expected = bigIntTo32B(rem, -1); + } else { + expected = bigIntTo32B(rem, 1); + } + } + + UInt256 uint256a = UInt256.fromSignedBytesBE(a.toByteArray()); + UInt256 uint256b = UInt256.fromSignedBytesBE(b.toByteArray()); + Bytes32 result = Bytes32.leftPad(Bytes.wrap(uint256a.signedMod(uint256b).toBytesBE())); + + assertThat(result).as("testSMod(%s, %s)", a, b).isEqualTo(expected); + } + + private Bytes32 bigIntTo32B(final BigInteger x, final int sign) { + if (sign >= 0) return bigIntTo32B(x); + byte[] a = new byte[32]; + Arrays.fill(a, (byte) 0xFF); + byte[] b = x.toByteArray(); + System.arraycopy(b, 0, a, 32 - b.length, b.length); + if (a.length > 32) return Bytes32.wrap(a, a.length - 32); + return Bytes32.leftPad(Bytes.wrap(a)); + } + + private Bytes32 bigIntTo32B(final BigInteger x) { + byte[] a = x.toByteArray(); + if (a.length > 32) return Bytes32.wrap(a, a.length - 32); + return Bytes32.leftPad(Bytes.wrap(a)); + } + + @ParameterizedTest + @MethodSource("provideTernaryTestCases") + void testAddMod(final BigInteger a, final BigInteger b, final BigInteger m) { + if (m.equals(BigInteger.ZERO)) { + return; // Skip division by zero + } + + BigInteger expected = a.add(b).mod(m); + expected = wrap256(expected); + + UInt256 uint256a = toUInt256(a); + UInt256 uint256b = toUInt256(b); + UInt256 uint256m = toUInt256(m); + + UInt256 result = uint256a.addMod(uint256b, uint256m); + assertThat(toBigInteger(result)).isEqualTo(expected); + } + + @ParameterizedTest + @MethodSource("provideTernaryTestCases") + void testMulMod(final BigInteger a, final BigInteger b, final BigInteger m) { + if (m.equals(BigInteger.ZERO)) { + return; // Skip division by zero + } + + BigInteger expected = a.multiply(b).mod(m); + expected = wrap256(expected); + + UInt256 uint256a = toUInt256(a); + UInt256 uint256b = toUInt256(b); + UInt256 uint256m = toUInt256(m); + + UInt256 result = uint256a.mulMod(uint256b, uint256m); + assertThat(toBigInteger(result)).isEqualTo(expected); + } + + // endregion + + // region Bitwise Operations Tests + + @ParameterizedTest + @MethodSource("provideShiftTestCases") + void testLeftShift(final BigInteger value, final int shift) { + BigInteger expected = wrap256(value.shiftLeft(shift)); + + UInt256 uint256 = toUInt256(value); + UInt256 result = uint256.shiftLeft(shift); + + assertThat(toBigInteger(result)).isEqualTo(expected); + } + + @ParameterizedTest + @MethodSource("provideShiftTestCases") + void testRightShift(final BigInteger value, final int shift) { + BigInteger expected = wrap256(value.shiftRight(shift)); + + UInt256 uint256 = toUInt256(value); + UInt256 result = uint256.shiftRight(shift); + + assertThat(toBigInteger(result)).isEqualTo(expected); + } + + // endregion + + // region Comparison Tests + + @ParameterizedTest + @MethodSource("provideBinaryTestCases") + void testComparisons(final BigInteger a, final BigInteger b) { + UInt256 uint256a = toUInt256(a); + UInt256 uint256b = toUInt256(b); + + assertThat(UInt256.compare(uint256a, uint256b) < 0).isEqualTo(a.compareTo(b) < 0); + assertThat(UInt256.compare(uint256a, uint256b) <= 0).isEqualTo(a.compareTo(b) <= 0); + assertThat(UInt256.compare(uint256a, uint256b) > 0).isEqualTo(a.compareTo(b) > 0); + assertThat(UInt256.compare(uint256a, uint256b) >= 0).isEqualTo(a.compareTo(b) >= 0); + assertThat(uint256a.equals(uint256b)).isEqualTo(a.equals(b)); + } + + // endregion + + // region Conversion Tests + + @ParameterizedTest + @MethodSource("provideUnaryTestCases") + void testToBigIntegerAndBack(final BigInteger value) { + UInt256 uint256 = toUInt256(value); + BigInteger result = toBigInteger(uint256); + assertThat(result).isEqualTo(value); + } + + // endregion +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/UInt256Test.java b/evm/src/test/java/org/hyperledger/besu/evm/UInt256Test.java new file mode 100644 index 00000000000..07d6093ba78 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/UInt256Test.java @@ -0,0 +1,351 @@ +/* + * 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.hyperledger.besu.evm; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Random; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; + +public class UInt256Test { + static final int SAMPLE_SIZE = 30000; + + private Bytes32 bigIntTo32B(final BigInteger x) { + byte[] a = x.toByteArray(); + if (a.length > 32) return Bytes32.wrap(a, a.length - 32); + return Bytes32.leftPad(Bytes.wrap(a)); + } + + private Bytes32 bigIntTo32B(final BigInteger x, final int sign) { + if (sign >= 0) return bigIntTo32B(x); + byte[] a = new byte[32]; + Arrays.fill(a, (byte) 0xFF); + byte[] b = x.toByteArray(); + System.arraycopy(b, 0, a, 32 - b.length, b.length); + if (a.length > 32) return Bytes32.wrap(a, a.length - 32); + return Bytes32.leftPad(Bytes.wrap(a)); + } + + @Test + public void fromInts() { + UInt256 result; + + result = UInt256.fromInt(0); + assertThat(result.length()).as("Int 0 length").isEqualTo(0); + assertThat(result.isZero()).as("Int 0, isZero").isTrue(); + + int[] testInts = new int[] {130, -128, 32500}; + for (int i : testInts) { + result = UInt256.fromInt(i); + assertThat(result.length()).as(String.format("Int %s length", i)).isEqualTo(1); + assertThat(result.intValue()).as(String.format("Int %s value", i)).isEqualTo(i); + } + } + + @Test + public void fromBytesBE() { + byte[] input; + UInt256 result; + int[] expectedLimbs; + + input = new byte[] {-128, 0, 0, 0}; + result = UInt256.fromBytesBE(input); + expectedLimbs = new int[] {-2147483648}; + assertThat(result.length()).as("4b-neg-length").isEqualTo(1); + assertThat(result.limbs()).as("4b-neg-limbs").isEqualTo(expectedLimbs); + + input = new byte[] {0, 0, 1, 1, 1}; + result = UInt256.fromBytesBE(input); + expectedLimbs = new int[] {1 + 256 + 65536}; + assertThat(result.length()).as("3b-length").isEqualTo(1); + assertThat(result.limbs()).as("3b-limbs").isEqualTo(expectedLimbs); + + input = new byte[] {1, 0, 0, 0, 0, 1, 1, 1}; + result = UInt256.fromBytesBE(input); + expectedLimbs = new int[] {1 + 256 + 65536, 16777216}; + assertThat(result.length()).as("8b-length").isEqualTo(2); + assertThat(result.limbs()).as("8b-limbs").isEqualTo(expectedLimbs); + + input = + new byte[] { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + result = UInt256.fromBytesBE(input); + expectedLimbs = new int[] {0, 0, 0, 0, 0, 0, 0, 16777216}; + assertThat(result.length()).as("32b-length").isEqualTo(8); + assertThat(result.limbs()).as("32b-limbs").isEqualTo(expectedLimbs); + + input = + new byte[] { + 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + result = UInt256.fromBytesBE(input); + expectedLimbs = new int[] {0, 0, 0, 0, 0, 0, 257}; + assertThat(result.length()).as("32b-padded-length").isEqualTo(7); + assertThat(result.limbs()).as("32b-padded-limbs").isEqualTo(expectedLimbs); + } + + @Test + public void fromToBytesBE() { + byte[] input = + new byte[] { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + }; + UInt256 asUint = UInt256.fromBytesBE(input); + BigInteger asBigInt = new BigInteger(1, input); + assertThat(asUint.toBytesBE()).isEqualTo(asBigInt.toByteArray()); + } + + @Test + public void smallInts() { + UInt256 number = UInt256.fromInt(523); + UInt256 modulus = UInt256.fromInt(27); + UInt256 remainder = number.mod(modulus); + UInt256 expected = UInt256.fromInt(523 % 27); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void smallMod() { + byte[] num_arr = + new byte[] { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + }; + UInt256 number = UInt256.fromBytesBE(num_arr); + UInt256 modulus = UInt256.fromInt(27); + int remainder = number.mod(modulus).intValue(); + BigInteger big_number = new BigInteger(1, num_arr); + BigInteger big_modulus = BigInteger.valueOf(27L); + int expected = big_number.mod(big_modulus).intValue(); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void smallModFullDividend() { + byte[] num_arr = + new byte[] { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, -127 + }; + UInt256 number = UInt256.fromBytesBE(num_arr); + UInt256 modulus = UInt256.fromInt(27); + int remainder = number.mod(modulus).intValue(); + BigInteger big_number = new BigInteger(1, num_arr); + BigInteger big_modulus = BigInteger.valueOf(27L); + int expected = big_number.mod(big_modulus).intValue(); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void bigMod() { + byte[] num_arr = + new byte[] { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 + }; + byte[] mod_arr = new byte[] {-111, 126, 78, 12}; + UInt256 number = UInt256.fromBytesBE(num_arr); + UInt256 modulus = UInt256.fromBytesBE(mod_arr); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + BigInteger big_number = new BigInteger(1, num_arr); + BigInteger big_modulus = new BigInteger(1, mod_arr); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void bigModWithExtraCarry() { + byte[] num_arr = + new byte[] { + -126, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 123 + }; + byte[] mod_arr = new byte[] {12, 126, 78, -11}; + UInt256 number = UInt256.fromBytesBE(num_arr); + UInt256 modulus = UInt256.fromBytesBE(mod_arr); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + BigInteger big_number = new BigInteger(1, num_arr); + BigInteger big_modulus = new BigInteger(1, mod_arr); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void modA() { + BigInteger big_number = new BigInteger("0000000067e36864", 16); + BigInteger big_modulus = new BigInteger("001fff", 16); + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void modB() { + BigInteger big_number = new BigInteger("022b1c8c1227a00000", 16); + BigInteger big_modulus = new BigInteger("038d7ea4c68000", 16); + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void modGeneralState() { + BigInteger big_number = new BigInteger("cea0c5cc171fa61277e5604a3bc8aef4de3d3882", 16); + BigInteger big_modulus = new BigInteger("7dae7454bb193b1c28e64a6a935bc3", 16); + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = Bytes32.leftPad(Bytes.wrap(big_number.mod(big_modulus).toByteArray())); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void modDiv8Mod8() { + final Random random = new Random(41335); + for (int i = 0; i < SAMPLE_SIZE; i++) { + final byte[] a = new byte[32]; + final byte[] b = new byte[32]; + random.nextBytes(a); + random.nextBytes(b); + BigInteger aInt = new BigInteger(1, a); + BigInteger bInt = new BigInteger(1, b); + int comp = aInt.compareTo(bInt); + BigInteger big_number = (comp >= 0) ? aInt : bInt; + BigInteger big_modulus = (comp >= 0) ? bInt : aInt; + UInt256 number = UInt256.fromBytesBE(big_number.toByteArray()); + UInt256 modulus = UInt256.fromBytesBE(big_modulus.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(number.mod(modulus).toBytesBE())); + Bytes32 expected = + BigInteger.ZERO.compareTo(big_modulus) == 0 + ? Bytes32.ZERO + : bigIntTo32B(big_number.mod(big_modulus)); + assertThat(remainder).isEqualTo(expected); + } + } + + @Test + public void referenceTest459() { + BigInteger xbig = new BigInteger("000000010000000000000000000000000000000000000000", 16); + BigInteger ybig = new BigInteger("0000c350", 16); + BigInteger mbig = new BigInteger("000003e8", 16); + UInt256 x = UInt256.fromBytesBE(xbig.toByteArray()); + UInt256 y = UInt256.fromBytesBE(ybig.toByteArray()); + UInt256 m = UInt256.fromBytesBE(mbig.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(x.addMod(y, m).toBytesBE())); + Bytes32 expected = + BigInteger.ZERO.compareTo(mbig) == 0 ? Bytes32.ZERO : bigIntTo32B(xbig.add(ybig).mod(mbig)); + assertThat(remainder).isEqualTo(expected); + } + + @Test + public void addMod() { + final Random random = new Random(42); + for (int i = 0; i < SAMPLE_SIZE; i++) { + int aSize = random.nextInt(1, 33); + int bSize = random.nextInt(1, 33); + int cSize = random.nextInt(1, 33); + final byte[] aArray = new byte[aSize]; + final byte[] bArray = new byte[bSize]; + final byte[] cArray = new byte[cSize]; + random.nextBytes(aArray); + random.nextBytes(bArray); + random.nextBytes(cArray); + BigInteger aInt = new BigInteger(1, aArray); + BigInteger bInt = new BigInteger(1, bArray); + BigInteger cInt = new BigInteger(1, cArray); + UInt256 a = UInt256.fromBytesBE(aInt.toByteArray()); + UInt256 b = UInt256.fromBytesBE(bInt.toByteArray()); + UInt256 c = UInt256.fromBytesBE(cInt.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(a.addMod(b, c).toBytesBE())); + Bytes32 expected = + BigInteger.ZERO.compareTo(cInt) == 0 + ? Bytes32.ZERO + : bigIntTo32B(aInt.add(bInt).mod(cInt)); + assertThat(remainder).isEqualTo(expected); + } + } + + @Test + public void mulMod() { + final Random random = new Random(123); + for (int i = 0; i < SAMPLE_SIZE; i++) { + int aSize = random.nextInt(1, 33); + int bSize = random.nextInt(1, 33); + int cSize = random.nextInt(1, 33); + final byte[] aArray = new byte[aSize]; + final byte[] bArray = new byte[bSize]; + final byte[] cArray = new byte[cSize]; + random.nextBytes(aArray); + random.nextBytes(bArray); + random.nextBytes(cArray); + BigInteger aInt = new BigInteger(1, aArray); + BigInteger bInt = new BigInteger(1, bArray); + BigInteger cInt = new BigInteger(1, cArray); + UInt256 a = UInt256.fromBytesBE(aInt.toByteArray()); + UInt256 b = UInt256.fromBytesBE(bInt.toByteArray()); + UInt256 c = UInt256.fromBytesBE(cInt.toByteArray()); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(a.mulMod(b, c).toBytesBE())); + Bytes32 expected = + BigInteger.ZERO.compareTo(cInt) == 0 + ? Bytes32.ZERO + : bigIntTo32B(aInt.multiply(bInt).mod(cInt)); + assertThat(remainder).isEqualTo(expected); + } + } + + @Test + public void signedMod() { + final Random random = new Random(432); + for (int i = 0; i < SAMPLE_SIZE; i++) { + int aSize = random.nextInt(1, 33); + int bSize = random.nextInt(1, 33); + final byte[] aArray = new byte[aSize]; + final byte[] bArray = new byte[bSize]; + random.nextBytes(aArray); + random.nextBytes(bArray); + BigInteger aInt = new BigInteger(aArray); + BigInteger bInt = new BigInteger(bArray); + UInt256 a = UInt256.fromSignedBytesBE(aArray); + UInt256 b = UInt256.fromSignedBytesBE(bArray); + Bytes32 remainder = Bytes32.leftPad(Bytes.wrap(a.signedMod(b).toBytesBE())); + Bytes32 expected; + BigInteger rem = BigInteger.ZERO; + if (BigInteger.ZERO.compareTo(bInt) == 0) expected = Bytes32.ZERO; + else { + rem = aInt.abs().mod(bInt.abs()); + if ((aInt.compareTo(BigInteger.ZERO) < 0) && (rem.compareTo(BigInteger.ZERO) != 0)) { + rem = rem.negate(); + expected = bigIntTo32B(rem, -1); + } else { + expected = bigIntTo32B(rem, 1); + } + } + assertThat(remainder).isEqualTo(expected); + } + } +}