Skip to content

Commit

Permalink
Merge pull request hyperledger-web3j#491 from e-Contract/transaction-…
Browse files Browse the repository at this point in the history
…decoder

Transaction decoder
  • Loading branch information
conor10 authored Apr 19, 2018
2 parents 15d6d56 + 48e0dec commit b8a297c
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 2 deletions.
2 changes: 1 addition & 1 deletion crypto/src/main/java/org/web3j/crypto/RawTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class RawTransaction {
private BigInteger value;
private String data;

private RawTransaction(BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, String to,
protected RawTransaction(BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, String to,
BigInteger value, String data) {
this.nonce = nonce;
this.gasPrice = gasPrice;
Expand Down
67 changes: 67 additions & 0 deletions crypto/src/main/java/org/web3j/crypto/SignedRawTransaction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.web3j.crypto;

import java.math.BigInteger;
import java.security.SignatureException;

public class SignedRawTransaction extends RawTransaction {

private static final int CHAIN_ID_INC = 35;
private static final int LOWER_REAL_V = 27;

private Sign.SignatureData signatureData;

public SignedRawTransaction(BigInteger nonce, BigInteger gasPrice,
BigInteger gasLimit, String to, BigInteger value, String data,
Sign.SignatureData signatureData) {
super(nonce, gasPrice, gasLimit, to, value, data);
this.signatureData = signatureData;
}

public Sign.SignatureData getSignatureData() {
return signatureData;
}

public String getFrom() throws SignatureException {
Integer chainId = getChainId();
byte[] encodedTransaction;
if (null == chainId) {
encodedTransaction = TransactionEncoder.encode(this);
} else {
encodedTransaction = TransactionEncoder.encode(this, chainId.byteValue());
}
byte v = signatureData.getV();
byte[] r = signatureData.getR();
byte[] s = signatureData.getS();
Sign.SignatureData signatureDataV = new Sign.SignatureData(getRealV(v), r, s);
BigInteger key = Sign.signedMessageToKey(encodedTransaction, signatureDataV);
return "0x" + Keys.getAddress(key);
}

public void verify(String from) throws SignatureException {
String actualFrom = getFrom();
if (!actualFrom.equals(from)) {
throw new SignatureException("from mismatch");
}
}

private byte getRealV(byte v) {
if (v == LOWER_REAL_V || v == (LOWER_REAL_V + 1)) {
return v;
}
byte realV = LOWER_REAL_V;
int inc = 0;
if ((int) v % 2 == 0) {
inc = 1;
}
return (byte) (realV + inc);
}

public Integer getChainId() {
byte v = signatureData.getV();
if (v == LOWER_REAL_V || v == (LOWER_REAL_V + 1)) {
return null;
}
Integer chainId = (v - CHAIN_ID_INC) / 2;
return chainId;
}
}
44 changes: 44 additions & 0 deletions crypto/src/main/java/org/web3j/crypto/TransactionDecoder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.web3j.crypto;

import java.math.BigInteger;

import org.web3j.rlp.RlpDecoder;
import org.web3j.rlp.RlpList;
import org.web3j.rlp.RlpString;
import org.web3j.utils.Numeric;

public class TransactionDecoder {

public static RawTransaction decode(String hexTransaction) {
byte[] transaction = Numeric.hexStringToByteArray(hexTransaction);
RlpList rlpList = RlpDecoder.decode(transaction);
RlpList values = (RlpList) rlpList.getValues().get(0);
BigInteger nonce = ((RlpString) values.getValues().get(0)).asBigInteger();
BigInteger gasPrice = ((RlpString) values.getValues().get(1)).asBigInteger();
BigInteger gasLimit = ((RlpString) values.getValues().get(2)).asBigInteger();
String to = ((RlpString) values.getValues().get(3)).asString();
BigInteger value = ((RlpString) values.getValues().get(4)).asBigInteger();
String data = ((RlpString) values.getValues().get(5)).asString();
if (values.getValues().size() > 6) {
byte v = ((RlpString) values.getValues().get(6)).getBytes()[0];
byte[] r = zeroPadded(((RlpString) values.getValues().get(7)).getBytes(), 32);
byte[] s = zeroPadded(((RlpString) values.getValues().get(8)).getBytes(), 32);
Sign.SignatureData signatureData = new Sign.SignatureData(v, r, s);
return new SignedRawTransaction(nonce, gasPrice, gasLimit,
to, value, data, signatureData);
} else {
return RawTransaction.createTransaction(nonce,
gasPrice, gasLimit, to, value, data);
}
}

private static byte[] zeroPadded(byte[] value, int size) {
if (value.length == size) {
return value;
}
int diff = size - value.length;
byte[] paddedValue = new byte[size];
System.arraycopy(value, 0, paddedValue, diff, value.length);
return paddedValue;
}
}
109 changes: 109 additions & 0 deletions crypto/src/test/java/org/web3j/crypto/TransactionDecoderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package org.web3j.crypto;

import java.math.BigInteger;

import org.junit.Test;

import org.web3j.utils.Numeric;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

public class TransactionDecoderTest {

@Test
public void testDecoding() throws Exception {
BigInteger nonce = BigInteger.ZERO;
BigInteger gasPrice = BigInteger.ONE;
BigInteger gasLimit = BigInteger.TEN;
String to = "0x0add5355";
BigInteger value = BigInteger.valueOf(Long.MAX_VALUE);
RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
nonce, gasPrice, gasLimit, to, value);
byte[] encodedMessage = TransactionEncoder.encode(rawTransaction);
String hexMessage = Numeric.toHexString(encodedMessage);

RawTransaction result = TransactionDecoder.decode(hexMessage);
assertNotNull(result);
assertEquals(nonce, result.getNonce());
assertEquals(gasPrice, result.getGasPrice());
assertEquals(gasLimit, result.getGasLimit());
assertEquals(to, result.getTo());
assertEquals(value, result.getValue());
assertEquals("", result.getData());
}

@Test
public void testDecodingSigned() throws Exception {
BigInteger nonce = BigInteger.ZERO;
BigInteger gasPrice = BigInteger.ONE;
BigInteger gasLimit = BigInteger.TEN;
String to = "0x0add5355";
BigInteger value = BigInteger.valueOf(Long.MAX_VALUE);
RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
nonce, gasPrice, gasLimit, to, value);
byte[] signedMessage = TransactionEncoder.signMessage(
rawTransaction, SampleKeys.CREDENTIALS);
String hexMessage = Numeric.toHexString(signedMessage);

RawTransaction result = TransactionDecoder.decode(hexMessage);
assertNotNull(result);
assertEquals(nonce, result.getNonce());
assertEquals(gasPrice, result.getGasPrice());
assertEquals(gasLimit, result.getGasLimit());
assertEquals(to, result.getTo());
assertEquals(value, result.getValue());
assertEquals("", result.getData());
assertTrue(result instanceof SignedRawTransaction);
SignedRawTransaction signedResult = (SignedRawTransaction) result;
assertNotNull(signedResult.getSignatureData());
Sign.SignatureData signatureData = signedResult.getSignatureData();
byte[] encodedTransaction = TransactionEncoder.encode(rawTransaction);
BigInteger key = Sign.signedMessageToKey(encodedTransaction, signatureData);
assertEquals(key, SampleKeys.PUBLIC_KEY);
assertEquals(SampleKeys.ADDRESS, signedResult.getFrom());
signedResult.verify(SampleKeys.ADDRESS);
assertNull(signedResult.getChainId());
}

@Test
public void testDecodingSignedChainId() throws Exception {
BigInteger nonce = BigInteger.ZERO;
BigInteger gasPrice = BigInteger.ONE;
BigInteger gasLimit = BigInteger.TEN;
String to = "0x0add5355";
BigInteger value = BigInteger.valueOf(Long.MAX_VALUE);
Integer chainId = 1;
RawTransaction rawTransaction = RawTransaction.createEtherTransaction(
nonce, gasPrice, gasLimit, to, value);
byte[] signedMessage = TransactionEncoder.signMessage(
rawTransaction, chainId.byteValue(), SampleKeys.CREDENTIALS);
String hexMessage = Numeric.toHexString(signedMessage);

RawTransaction result = TransactionDecoder.decode(hexMessage);
assertNotNull(result);
assertEquals(nonce, result.getNonce());
assertEquals(gasPrice, result.getGasPrice());
assertEquals(gasLimit, result.getGasLimit());
assertEquals(to, result.getTo());
assertEquals(value, result.getValue());
assertEquals("", result.getData());
assertTrue(result instanceof SignedRawTransaction);
SignedRawTransaction signedResult = (SignedRawTransaction) result;
assertEquals(SampleKeys.ADDRESS, signedResult.getFrom());
signedResult.verify(SampleKeys.ADDRESS);
assertEquals(chainId, signedResult.getChainId());
}

@Test
public void testRSize31() throws Exception {
//CHECKSTYLE:OFF
String hexTransaction = "0xf883370183419ce09433c98f20dd73d7bb1d533c4aa3371f2b30c6ebde80a45093dc7d00000000000000000000000000000000000000000000000000000000000000351c9fb90996c836fb34b782ee3d6efa9e2c79a75b277c014e353b51b23b00524d2da07435ebebca627a51a863bf590aff911c4746ab8386a0477c8221bb89671a5d58";
//CHECKSTYLE:ON
RawTransaction result = TransactionDecoder.decode(hexTransaction);
SignedRawTransaction signedResult = (SignedRawTransaction) result;
assertEquals("0x1b609b03e2e9b0275a61fa5c69a8f32550285536", signedResult.getFrom());
}
}
2 changes: 1 addition & 1 deletion rlp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
description 'Ethereum Recursive Length Prefix (RLP) encoding for serializing objects'

dependencies {
testCompile project(':utils')
compile project(':utils')
}
13 changes: 13 additions & 0 deletions rlp/src/main/java/org/web3j/rlp/RlpString.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.math.BigInteger;
import java.util.Arrays;

import org.web3j.utils.Numeric;

/**
* RLP string type.
*/
Expand All @@ -19,6 +21,17 @@ public byte[] getBytes() {
return value;
}

public BigInteger asBigInteger() {
if (value.length == 0) {
return BigInteger.ZERO;
}
return new BigInteger(value);
}

public String asString() {
return Numeric.toHexString(value);
}

public static RlpString create(byte[] value) {
return new RlpString(value);
}
Expand Down

0 comments on commit b8a297c

Please sign in to comment.