/*
* Copyright (c) [2016] [ <ether.camp> ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ethereumJ library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ethereum.vm;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import org.ethereum.db.ByteArrayWrapper;
import org.ethereum.util.ByteUtil;
import org.ethereum.util.FastByteComparisons;
import org.spongycastle.util.Arrays;
import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.nio.ByteBuffer;
/**
* DataWord is the 32-byte array representation of a 256-bit number
* Calculations can be done on this word with other DataWords
*
* @author Roman Mandeleil
* @since 01.06.2014
*/
public class DataWord implements Comparable<DataWord> {
/* Maximum value of the DataWord */
public static final BigInteger _2_256 = BigInteger.valueOf(2).pow(256);
public static final BigInteger MAX_VALUE = _2_256.subtract(BigInteger.ONE);
public static final DataWord ZERO = new DataWord(new byte[32]); // don't push it in to the stack
public static final DataWord ZERO_EMPTY_ARRAY = new DataWord(new byte[0]); // don't push it in to the stack
private byte[] data = new byte[32];
public DataWord() {
}
public DataWord(int num) {
this(ByteBuffer.allocate(4).putInt(num));
}
public DataWord(long num) {
this(ByteBuffer.allocate(8).putLong(num));
}
private DataWord(ByteBuffer buffer) {
final ByteBuffer data = ByteBuffer.allocate(32);
final byte[] array = buffer.array();
System.arraycopy(array, 0, data.array(), 32 - array.length, array.length);
this.data = data.array();
}
@JsonCreator
public DataWord(String data) {
this(Hex.decode(data));
}
public DataWord(ByteArrayWrapper wrappedData){
this(wrappedData.getData());
}
public DataWord(byte[] data) {
if (data == null)
this.data = ByteUtil.EMPTY_BYTE_ARRAY;
else if (data.length == 32)
this.data = data;
else if (data.length <= 32)
System.arraycopy(data, 0, this.data, 32 - data.length, data.length);
else
throw new RuntimeException("Data word can't exceed 32 bytes: " + data);
}
public byte[] getData() {
return data;
}
public byte[] getNoLeadZeroesData() {
return ByteUtil.stripLeadingZeroes(data);
}
public byte[] getLast20Bytes() {
return Arrays.copyOfRange(data, 12, data.length);
}
public BigInteger value() {
return new BigInteger(1, data);
}
/**
* Converts this DataWord to an int, checking for lost information.
* If this DataWord is out of the possible range for an int result
* then an ArithmeticException is thrown.
*
* @return this DataWord converted to an int.
* @throws ArithmeticException - if this will not fit in an int.
*/
public int intValue() {
int intVal = 0;
for (int i = 0; i < data.length; i++)
{
intVal = (intVal << 8) + (data[i] & 0xff);
}
return intVal;
}
/**
* In case of int overflow returns Integer.MAX_VALUE
* otherwise works as #intValue()
*/
public int intValueSafe() {
int bytesOccupied = bytesOccupied();
int intValue = intValue();
if (bytesOccupied > 4 || intValue < 0) return Integer.MAX_VALUE;
return intValue;
}
/**
* Converts this DataWord to a long, checking for lost information.
* If this DataWord is out of the possible range for a long result
* then an ArithmeticException is thrown.
*
* @return this DataWord converted to a long.
* @throws ArithmeticException - if this will not fit in a long.
*/
public long longValue() {
long longVal = 0;
for (int i = 0; i < data.length; i++)
{
longVal = (longVal << 8) + (data[i] & 0xff);
}
return longVal;
}
/**
* In case of long overflow returns Long.MAX_VALUE
* otherwise works as #longValue()
*/
public long longValueSafe() {
int bytesOccupied = bytesOccupied();
long longValue = longValue();
if (bytesOccupied > 8 || longValue < 0) return Long.MAX_VALUE;
return longValue;
}
public BigInteger sValue() {
return new BigInteger(data);
}
public String bigIntValue() {
return new BigInteger(data).toString();
}
public boolean isZero() {
for (byte tmp : data) {
if (tmp != 0) return false;
}
return true;
}
// only in case of signed operation
// when the number is explicit defined
// as negative
public boolean isNegative() {
int result = data[0] & 0x80;
return result == 0x80;
}
public DataWord and(DataWord w2) {
for (int i = 0; i < this.data.length; ++i) {
this.data[i] &= w2.data[i];
}
return this;
}
public DataWord or(DataWord w2) {
for (int i = 0; i < this.data.length; ++i) {
this.data[i] |= w2.data[i];
}
return this;
}
public DataWord xor(DataWord w2) {
for (int i = 0; i < this.data.length; ++i) {
this.data[i] ^= w2.data[i];
}
return this;
}
public void negate() {
if (this.isZero()) return;
for (int i = 0; i < this.data.length; ++i) {
this.data[i] = (byte) ~this.data[i];
}
for (int i = this.data.length - 1; i >= 0; --i) {
this.data[i] = (byte) (1 + this.data[i] & 0xFF);
if (this.data[i] != 0) break;
}
}
public void bnot() {
if (this.isZero()) {
this.data = ByteUtil.copyToArray(MAX_VALUE);
return;
}
this.data = ByteUtil.copyToArray(MAX_VALUE.subtract(this.value()));
}
// By : Holger
// From : http://stackoverflow.com/a/24023466/459349
public void add(DataWord word) {
byte[] result = new byte[32];
for (int i = 31, overflow = 0; i >= 0; i--) {
int v = (this.data[i] & 0xff) + (word.data[i] & 0xff) + overflow;
result[i] = (byte) v;
overflow = v >>> 8;
}
this.data = result;
}
// old add-method with BigInteger quick hack
public void add2(DataWord word) {
BigInteger result = value().add(word.value());
this.data = ByteUtil.copyToArray(result.and(MAX_VALUE));
}
// TODO: mul can be done in more efficient way
// TODO: with shift left shift right trick
// TODO without BigInteger quick hack
public void mul(DataWord word) {
BigInteger result = value().multiply(word.value());
this.data = ByteUtil.copyToArray(result.and(MAX_VALUE));
}
// TODO: improve with no BigInteger
public void div(DataWord word) {
if (word.isZero()) {
this.and(ZERO);
return;
}
BigInteger result = value().divide(word.value());
this.data = ByteUtil.copyToArray(result.and(MAX_VALUE));
}
// TODO: improve with no BigInteger
public void sDiv(DataWord word) {
if (word.isZero()) {
this.and(ZERO);
return;
}
BigInteger result = sValue().divide(word.sValue());
this.data = ByteUtil.copyToArray(result.and(MAX_VALUE));
}
// TODO: improve with no BigInteger
public void sub(DataWord word) {
BigInteger result = value().subtract(word.value());
this.data = ByteUtil.copyToArray(result.and(MAX_VALUE));
}
// TODO: improve with no BigInteger
public void exp(DataWord word) {
BigInteger result = value().modPow(word.value(), _2_256);
this.data = ByteUtil.copyToArray(result);
}
// TODO: improve with no BigInteger
public void mod(DataWord word) {
if (word.isZero()) {
this.and(ZERO);
return;
}
BigInteger result = value().mod(word.value());
this.data = ByteUtil.copyToArray(result.and(MAX_VALUE));
}
public void sMod(DataWord word) {
if (word.isZero()) {
this.and(ZERO);
return;
}
BigInteger result = sValue().abs().mod(word.sValue().abs());
result = (sValue().signum() == -1) ? result.negate() : result;
this.data = ByteUtil.copyToArray(result.and(MAX_VALUE));
}
public void addmod(DataWord word1, DataWord word2) {
if (word2.isZero()) {
this.data = new byte[32];
return;
}
BigInteger result = value().add(word1.value()).mod(word2.value());
this.data = ByteUtil.copyToArray(result.and(MAX_VALUE));
}
public void mulmod(DataWord word1, DataWord word2) {
if (this.isZero() || word1.isZero() || word2.isZero()) {
this.data = new byte[32];
return;
}
BigInteger result = value().multiply(word1.value()).mod(word2.value());
this.data = ByteUtil.copyToArray(result.and(MAX_VALUE));
}
@JsonValue
@Override
public String toString() {
return Hex.toHexString(data);
}
public String toPrefixString() {
byte[] pref = getNoLeadZeroesData();
if (pref.length == 0) return "";
if (pref.length < 7)
return Hex.toHexString(pref);
return Hex.toHexString(pref).substring(0, 6);
}
public String shortHex() {
String hexValue = Hex.toHexString(getNoLeadZeroesData()).toUpperCase();
return "0x" + hexValue.replaceFirst("^0+(?!$)", "");
}
public DataWord clone() {
return new DataWord(Arrays.clone(data));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DataWord dataWord = (DataWord) o;
return java.util.Arrays.equals(data, dataWord.data);
}
@Override
public int hashCode() {
return java.util.Arrays.hashCode(data);
}
@Override
public int compareTo(DataWord o) {
if (o == null || o.getData() == null) return -1;
int result = FastByteComparisons.compareTo(
data, 0, data.length,
o.getData(), 0, o.getData().length);
// Convert result into -1, 0 or 1 as is the convention
return (int) Math.signum(result);
}
public void signExtend(byte k) {
if (0 > k || k > 31)
throw new IndexOutOfBoundsException();
byte mask = this.sValue().testBit((k * 8) + 7) ? (byte) 0xff : 0;
for (int i = 31; i > k; i--) {
this.data[31 - i] = mask;
}
}
public int bytesOccupied() {
int firstNonZero = ByteUtil.firstNonZeroByte(data);
if (firstNonZero == -1) return 0;
return 31 - firstNonZero + 1;
}
public boolean isHex(String hex) {
return Hex.toHexString(data).equals(hex);
}
public String asString(){
return new String(getNoLeadZeroesData());
}
}