Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 65 additions & 54 deletions src/main/java/org/arkecosystem/crypto/transactions/Deserializer.java
Original file line number Diff line number Diff line change
@@ -1,46 +1,83 @@
package org.arkecosystem.crypto.transactions;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;
import java.util.Map;
import org.arkecosystem.crypto.configuration.Network;
import org.arkecosystem.crypto.encoding.Hex;
import org.arkecosystem.crypto.enums.AbiFunction;
import org.arkecosystem.crypto.transactions.types.*;
import org.arkecosystem.crypto.utils.AbiDecoder;
import org.arkecosystem.crypto.utils.RlpDecoder;

public class Deserializer {
private static final int SIGNATURE_SIZE = 64;
private static final int RECOVERY_SIZE = 1;

private final ByteBuffer buffer;
private final byte[] rawBytes;

public Deserializer(String serialized) {
byte[] bytes = serialized.contains("\0") ? serialized.getBytes() : Hex.decode(serialized);
this.buffer = ByteBuffer.wrap(bytes);
this.buffer.order(ByteOrder.LITTLE_ENDIAN);
this.rawBytes = Hex.decode(serialized);
}

public static Deserializer newDeserializer(String serialized) {
return new Deserializer(serialized);
}

public AbstractTransaction deserialize() {
int startPosition = buffer.position();
List<byte[]> fields = RlpDecoder.decode(rawBytes);

// Fields: [nonce, gasPrice, gasLimit, to, value, data, v, r, s]
long nonce = bytesToLong(fields.get(0));
long gasPrice = bytesToLong(fields.get(1));
long gasLimit = bytesToLong(fields.get(2));
String recipientAddress = fields.get(3).length > 0 ? "0x" + Hex.encode(fields.get(3)) : "";
String value = fields.get(4).length > 0 ? new BigInteger(1, fields.get(4)).toString() : "0";
String data = fields.get(5).length > 0 ? Hex.encode(fields.get(5)) : "";

// Recover signature
String signature = null;
if (fields.size() >= 9) {
int vEncoded =
fields.get(6).length > 0 ? new BigInteger(1, fields.get(6)).intValue() : 0;
byte[] r = fields.get(7);
byte[] s = fields.get(8);

int chainId = Network.get().chainId();
int v = vEncoded - (chainId * 2 + 35);

if (r.length > 0 || s.length > 0) {
byte[] rPadded = padTo32(r);
byte[] sPadded = padTo32(s);
byte[] sigBytes = new byte[65];
System.arraycopy(rPadded, 0, sigBytes, 0, 32);
System.arraycopy(sPadded, 0, sigBytes, 32, 32);
sigBytes[64] = (byte) v;
signature = Hex.encode(sigBytes);
}
}

// Create temp transaction to guess type
AbstractTransaction tempTransaction = new EvmCall();
deserializeCommon(tempTransaction);
deserializeData(tempTransaction);
tempTransaction.nonce = nonce;
tempTransaction.gasPrice = gasPrice;
tempTransaction.gasLimit = gasLimit;
tempTransaction.recipientAddress = recipientAddress;
tempTransaction.value = value;
tempTransaction.data = data;
tempTransaction.network = Network.get().version();

AbstractTransaction transaction = guessTransactionFromTransactionData(tempTransaction);

buffer.position(startPosition);

deserializeCommon(transaction);
deserializeData(transaction);
deserializeSignatures(transaction);

transaction.recoverSender();
transaction.nonce = nonce;
transaction.gasPrice = gasPrice;
transaction.gasLimit = gasLimit;
transaction.recipientAddress = recipientAddress;
transaction.value = value;
transaction.data = data;
transaction.network = Network.get().version();
transaction.signature = signature;

if (signature != null) {
transaction.recoverSender();
}

transaction.computeId();

Expand All @@ -49,7 +86,7 @@ public AbstractTransaction deserialize() {

private AbstractTransaction guessTransactionFromTransactionData(
AbstractTransaction transactionData) {
if (!"0".equals(transactionData.value)) {
if (!"0".equals(transactionData.value) && !"".equals(transactionData.value)) {
return new Transfer();
}

Expand Down Expand Up @@ -87,41 +124,15 @@ private Map<String, Object> decodePayload(AbstractTransaction transaction) {
}
}

private void deserializeCommon(AbstractTransaction transaction) {
transaction.network = Byte.toUnsignedInt(buffer.get());
transaction.nonce = buffer.getLong();
transaction.gasPrice = buffer.getInt();
transaction.gasLimit = buffer.getInt();
}

private void deserializeData(AbstractTransaction transaction) {
byte[] valueBytes = new byte[32];
buffer.get(valueBytes);
transaction.value = new BigInteger(1, valueBytes).toString();

int recipientMarker = Byte.toUnsignedInt(buffer.get());
if (recipientMarker == 1) {
byte[] recipientBytes = new byte[20];
buffer.get(recipientBytes);
transaction.recipientAddress = "0x" + Hex.encode(recipientBytes);
}

int payloadLength = buffer.getInt();
if (payloadLength > 0) {
byte[] payloadBytes = new byte[payloadLength];
buffer.get(payloadBytes);
transaction.data = Hex.encode(payloadBytes);
} else {
transaction.data = "";
}
private static long bytesToLong(byte[] bytes) {
if (bytes.length == 0) return 0;
return new BigInteger(1, bytes).longValue();
}

private void deserializeSignatures(AbstractTransaction transaction) {
int signatureLength = SIGNATURE_SIZE + RECOVERY_SIZE;
if (buffer.remaining() >= signatureLength) {
byte[] signatureBytes = new byte[signatureLength];
buffer.get(signatureBytes);
transaction.signature = Hex.encode(signatureBytes);
}
private static byte[] padTo32(byte[] input) {
if (input.length >= 32) return input;
byte[] padded = new byte[32];
System.arraycopy(input, 0, padded, 32 - input.length, input.length);
return padded;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package org.arkecosystem.crypto.transactions;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.arkecosystem.crypto.encoding.Hex;
import org.arkecosystem.crypto.transactions.types.AbstractTransaction;
import org.arkecosystem.crypto.utils.TransactionUtils;

public class Serializer {
private final AbstractTransaction transaction;
Expand All @@ -22,61 +19,6 @@ public static byte[] getBytes(AbstractTransaction transaction, boolean skipSigna
}

public byte[] serialize(boolean skipSignature) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.order(ByteOrder.LITTLE_ENDIAN);

serializeCommon(buffer);

serializeData(buffer);

serializeSignatures(buffer, skipSignature);

byte[] result = new byte[buffer.position()];
buffer.flip();
buffer.get(result);
return result;
}

private void serializeCommon(ByteBuffer buffer) {
buffer.put((byte) transaction.network);
buffer.putLong(transaction.nonce);
buffer.putInt((int) transaction.gasPrice);
buffer.putInt((int) transaction.gasLimit);
}

private void serializeData(ByteBuffer buffer) {
// Convert 'value' from String to BigInteger and write as Uint256 (32 bytes)
byte[] valueBytes = new BigInteger(transaction.value).toByteArray();
byte[] valueBytesPadded = new byte[32];
int srcPos = Math.max(0, valueBytes.length - 32);
int destPos = 32 - (valueBytes.length - srcPos);
System.arraycopy(valueBytes, srcPos, valueBytesPadded, destPos, valueBytes.length - srcPos);
buffer.put(valueBytesPadded);

// Write recipient marker and address
if (transaction.recipientAddress != null && !transaction.recipientAddress.isEmpty()) {
buffer.put((byte) 1);
byte[] recipientBytes =
Hex.decode(transaction.recipientAddress.replaceFirst("^0x", "").toLowerCase());

buffer.put(recipientBytes);
} else {
buffer.put((byte) 0);
}

// Write payload length as UInt32 and the payload itself if present
String payloadHex =
transaction.data != null ? transaction.data.replaceFirst("^0x", "") : "";
int payloadLength = payloadHex.length() / 2;
buffer.putInt(payloadLength);
if (payloadLength > 0) {
buffer.put(Hex.decode(payloadHex));
}
}

private void serializeSignatures(ByteBuffer buffer, boolean skipSignature) {
if (!skipSignature && transaction.signature != null) {
buffer.put(Hex.decode(transaction.signature));
}
return TransactionUtils.toBuffer(transaction.toHashMap(), skipSignature);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ private void initializeTransactionDefaults() {
this.transaction.refreshPayloadData();
}

public TBuilder gasLimit(int gasLimit) {
public TBuilder gasLimit(long gasLimit) {
this.transaction.gasLimit = gasLimit;
return this.instance();
}
Expand All @@ -34,7 +34,7 @@ public TBuilder recipientAddress(String recipientAddressId) {
return this.instance();
}

public TBuilder gasPrice(int gasPrice) {
public TBuilder gasPrice(long gasPrice) {
this.transaction.gasPrice = gasPrice;
return this.instance();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import org.arkecosystem.crypto.identities.PrivateKey;
import org.arkecosystem.crypto.transactions.Serializer;
import org.arkecosystem.crypto.utils.AbiDecoder;
import org.arkecosystem.crypto.utils.TransactionHasher;
import org.arkecosystem.crypto.utils.TransactionUtils;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;

Expand All @@ -26,8 +26,8 @@ public abstract class AbstractTransaction {
public String value = "0";
public String recipientAddress;
public String id;
public int gasLimit;
public int gasPrice;
public long gasLimit;
public long gasPrice;
public String validatorPublicKey;
public String vote;

Expand All @@ -38,13 +38,18 @@ public AbstractTransaction(Map<String, Object> data) {
this.network = ((Number) data.get("network")).intValue();
}
if (data.containsKey("nonce")) {
this.nonce = Long.parseLong(data.get("nonce").toString());
Object nonceVal = data.get("nonce");
if (nonceVal instanceof Number) {
this.nonce = ((Number) nonceVal).longValue();
} else {
this.nonce = Long.parseLong(nonceVal.toString());
}
}
if (data.containsKey("gasPrice")) {
this.gasPrice = ((Number) data.get("gasPrice")).intValue();
this.gasPrice = ((Number) data.get("gasPrice")).longValue();
}
if (data.containsKey("gasLimit")) {
this.gasLimit = ((Number) data.get("gasLimit")).intValue();
this.gasLimit = ((Number) data.get("gasLimit")).longValue();
}
if (data.containsKey("recipientAddress")) {
this.recipientAddress = (String) data.get("recipientAddress");
Expand Down Expand Up @@ -93,18 +98,7 @@ public String getId() {
}

public byte[] hash(boolean skipSignature) {
HashMap<String, Object> map = new HashMap<>();
map.put("gasPrice", this.gasPrice);
map.put("network", this.network);
map.put("nonce", this.nonce);
map.put("value", this.value);
map.put("gasLimit", this.gasLimit);
map.put("data", this.data);
map.put("recipientAddress", this.recipientAddress);
if (!skipSignature && this.signature != null) {
map.put("signature", this.signature);
}
return TransactionHasher.toHash(map, skipSignature);
return TransactionUtils.toHash(toHashMap(), skipSignature);
}

public AbstractTransaction sign(String passphrase) {
Expand Down Expand Up @@ -181,7 +175,6 @@ public void recoverSender() {

this.senderPublicKey = recoveredKey.getPublicKeyAsHex();

// Compute the sender's address (EVM address)
this.senderAddress = Address.fromPublicKey(this.senderPublicKey);
}

Expand Down
58 changes: 58 additions & 0 deletions src/main/java/org/arkecosystem/crypto/utils/RlpDecoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.arkecosystem.crypto.utils;

import java.util.ArrayList;
import java.util.List;

public class RlpDecoder {

public static List<byte[]> decode(byte[] input) {
List<byte[]> result = new ArrayList<>();
decodeList(input, 0, input.length, result);
return result;
}

private static void decodeList(byte[] input, int offset, int end, List<byte[]> result) {
if (offset >= end) return;

int prefix = input[offset] & 0xFF;

if (prefix <= 0x7F) {
result.add(new byte[] {input[offset]});
decodeList(input, offset + 1, end, result);
} else if (prefix <= 0xB7) {
int len = prefix - 0x80;
byte[] data = new byte[len];
System.arraycopy(input, offset + 1, data, 0, len);
result.add(data);
decodeList(input, offset + 1 + len, end, result);
} else if (prefix <= 0xBF) {
int lenOfLen = prefix - 0xB7;
int len = readLength(input, offset + 1, lenOfLen);
byte[] data = new byte[len];
System.arraycopy(input, offset + 1 + lenOfLen, data, 0, len);
result.add(data);
decodeList(input, offset + 1 + lenOfLen + len, end, result);
} else if (prefix <= 0xF7) {
int len = prefix - 0xC0;
List<byte[]> inner = new ArrayList<>();
decodeList(input, offset + 1, offset + 1 + len, inner);
result.addAll(inner);
decodeList(input, offset + 1 + len, end, result);
} else {
int lenOfLen = prefix - 0xF7;
int len = readLength(input, offset + 1, lenOfLen);
List<byte[]> inner = new ArrayList<>();
decodeList(input, offset + 1 + lenOfLen, offset + 1 + lenOfLen + len, inner);
result.addAll(inner);
decodeList(input, offset + 1 + lenOfLen + len, end, result);
}
}

private static int readLength(byte[] input, int offset, int lenOfLen) {
int len = 0;
for (int i = 0; i < lenOfLen; i++) {
len = (len << 8) | (input[offset + i] & 0xFF);
}
return len;
}
}
Loading
Loading