diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java index 0f49a1f0..150253f2 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java @@ -65,24 +65,24 @@ public class Transaction { private ECDSASignature signature; /* Tx in encoded form */ + private byte[] rlpEncodedSigned; private byte[] rlpEncoded; - private byte[] unsignedRLPEncoded; /* Indicates if this transaction has been parsed * from the rlp-encoded data */ private boolean parsed = false; public Transaction(byte[] rawData) { - this.rlpEncoded = rawData; + this.rlpEncodedSigned = rawData; parsed = false; } - public Transaction(byte[] nonce, byte[] value, byte[] recieveAddress, byte[] gasPrice, byte[] gas, byte[] data) { + public Transaction(byte[] nonce, byte[] gasPrice, byte[] gas, byte[] recieveAddress, byte[] value, byte[] data) { this.nonce = nonce; - this.value = value; - this.receiveAddress = recieveAddress; this.gasPrice = gasPrice; this.gasLimit = gas; - if(recieveAddress == null) { + this.receiveAddress = recieveAddress; + this.value = value; + if(recieveAddress == null || receiveAddress.length == 0) { this.init = data; } else { this.data = data; @@ -92,10 +92,10 @@ public class Transaction { public void rlpParse(){ - RLPList decodedTxList = RLP.decode2(rlpEncoded); + RLPList decodedTxList = RLP.decode2(rlpEncodedSigned); RLPList transaction = (RLPList) decodedTxList.get(0); - this.hash = HashUtil.sha3(rlpEncoded); + this.hash = HashUtil.sha3(rlpEncodedSigned); this.nonce = ((RLPItem) transaction.get(0)).getRLPData(); this.gasPrice = ((RLPItem) transaction.get(1)).getRLPData(); @@ -126,7 +126,7 @@ public class Transaction { public byte[] getHash() { if (!parsed) rlpParse(); - byte[] plainMsg = this.getRlpUnsigned(); + byte[] plainMsg = this.getEncoded(); return HashUtil.sha3(plainMsg); } @@ -221,9 +221,9 @@ public class Transaction { * For signature games you have to keep also * rlp of the transaction without any signature data */ - public byte[] getRlpUnsigned(){ + public byte[] getEncoded(){ - if (unsignedRLPEncoded != null) return unsignedRLPEncoded; + if (rlpEncoded != null) return rlpEncoded; byte[] nonce = RLP.encodeElement(this.nonce); byte[] gasPrice = RLP.encodeElement(this.gasPrice); @@ -234,19 +234,19 @@ public class Transaction { if(Arrays.equals(this.receiveAddress, new byte[0])) { byte[] init = RLP.encodeElement(this.init); - this.unsignedRLPEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, value, receiveAddress, + this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress, value, data, init); } else { - this.unsignedRLPEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, value, receiveAddress, + this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress, value, data); } - return unsignedRLPEncoded; + return rlpEncoded; } - public byte[] getEncoded() { + public byte[] getEncodedSigned() { - if(rlpEncoded != null) return rlpEncoded; + if(rlpEncodedSigned != null) return rlpEncodedSigned; byte[] nonce = RLP.encodeElement(this.nonce); byte[] gasPrice = RLP.encodeElement(this.gasPrice); @@ -255,18 +255,18 @@ public class Transaction { byte[] value = RLP.encodeElement(this.value); byte[] data = RLP.encodeElement(this.data); - byte[] v = RLP.encodeByte( signature.v ); - byte[] r = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.r)); - byte[] s = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.s)); + byte[] v = RLP.encodeByte( signature.v ); + byte[] r = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.r)); + byte[] s = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.s)); if(Arrays.equals(this.receiveAddress, new byte[0])) { byte[] init = RLP.encodeElement(this.init); - this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, value, receiveAddress, + this.rlpEncodedSigned = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress, value, data, init, v, r, s); } else { - this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, value, receiveAddress, + this.rlpEncodedSigned = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress, value, data, v, r, s); } - return rlpEncoded; + return rlpEncodedSigned; } } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/client/ClientPeer.java b/ethereumj-core/src/main/java/org/ethereum/net/client/ClientPeer.java index 84c4ef56..d813be4b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/client/ClientPeer.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/client/ClientPeer.java @@ -88,7 +88,7 @@ public class ClientPeer { */ public void sendTransaction(Transaction transaction){ - transaction.getEncoded(); + transaction.getEncodedSigned(); java.util.List txList = new ArrayList(); txList.add(transaction); TransactionsMessage transactionsMessage = new TransactionsMessage(txList); diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java b/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java index b881b5c1..4254a041 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/message/TransactionsMessage.java @@ -32,7 +32,7 @@ public class TransactionsMessage extends Message { for (Transaction tx : transactionList){ - byte[] txPayload = tx.getEncoded(); + byte[] txPayload = tx.getEncodedSigned(); try { baos.write(txPayload); } catch (IOException e) { @@ -43,7 +43,7 @@ public class TransactionsMessage extends Message { byte[][] elements = new byte[transactionList.size() + 1][]; elements[0] = new byte[]{Command.TRANSACTIONS.asByte()}; for (int i = 0; i < transactionList.size(); ++i){ - elements[i + 1] = transactionList.get(i).getEncoded(); + elements[i + 1] = transactionList.get(i).getEncodedSigned(); } payload = RLP.encodeList(elements); diff --git a/ethereumj-core/src/main/java/org/ethereum/util/Utils.java b/ethereumj-core/src/main/java/org/ethereum/util/Utils.java index a69303ac..8bd5bece 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/Utils.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/Utils.java @@ -17,6 +17,17 @@ import org.spongycastle.util.encoders.Hex; public class Utils { private static SecureRandom random = new SecureRandom(); + + public static byte[] asUnsignedByteArray(BigInteger bigInteger) { + byte[] array = bigInteger.toByteArray(); + if (array[0] == 0) { + byte[] tmp = new byte[array.length - 1]; + System.arraycopy(array, 1, tmp, 0, tmp.length); + array = tmp; + } + return array; + } + public static byte[] hexStringToByteArr(String hexString){ diff --git a/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java b/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java index c79c1779..18492553 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java @@ -2,68 +2,157 @@ package org.ethereum.core; import static org.junit.Assert.*; +import java.io.IOException; import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import org.ethereum.crypto.ECKey; +import org.ethereum.crypto.HashUtil; +import org.junit.Assert; import org.junit.Test; +import org.spongycastle.util.BigIntegers; import org.spongycastle.util.encoders.Hex; public class TransactionTest { - private static String RLP_ENCODED_TX = "f88b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a4701ca00502e84be138ca397e49f96e8c82e5a99afc09e0ea4582cc109ea221eeb479efa078f18d645b39ec44778c12ffc4b0"; - private static String RLP_ENCODED_TX2 = "f8ccf8a6808609184e72a0008227109491a10664d0cd489085a7a018beb5245d4f2272f180b840000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011ca0c2604bd6eeca76afce4e7775d87960e3d4ed3b69235a3f94d6f1497c9831b50ca0664124a6b323350dd57a650434dc6bf8ddf37cd1a2686fee377e512aa12f1214a0c84f20b2df6abd635babd7af64cd76756cd4e39c0ccb87eaaaad609f21d1fe51820334"; + private static String RLP_ENCODED_TX = "e88085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc1000080"; private static String HASH_RAW_TX = ""; private static String HASH_SIGNED_TX = ""; + @Test /* sign transaction https://tools.ietf.org/html/rfc6979 */ + public void test1() throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException, IOException { + + //python taken exact data + String txRLPRawData = "a9e880872386f26fc1000085e8d4a510008203e89413978aee95f38490e9769c39b2773ed763d9cd5f80"; + // String txRLPRawData = "f82804881bc16d674ec8000094cd2a3d9f938e13cd947ec05abc7fe734df8dd8268609184e72a0006480"; + + byte[] cowPrivKey = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); + ECKey key = ECKey.fromPrivate(cowPrivKey); + + byte[] data = Hex.decode(txRLPRawData); + + // step 1: serialize + RLP encode + // step 2: hash = sha3(step1) + byte[] txHash = HashUtil.sha3(data); + + String signature = key.doSign(txHash).toBase64(); + System.out.println(signature); + } + + @Test /* achieve public key of the sender */ + public void test2() throws Exception { + + // cat --> 79B08AD8787060333663D19704909EE7B1903E58 + // cow --> CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826 + + BigInteger value = new BigInteger("1000000000000000000000000"); + + byte[] privKey = HashUtil.sha3("cat".getBytes()); + Address receiveAddress = new Address(privKey); + + byte[] senderPrivKey = HashUtil.sha3("cow".getBytes()); + + byte[] gasPrice= Hex.decode("09184e72a000"); + byte[] gas = Hex.decode("4255"); + + Transaction tx = new Transaction(null, value.toByteArray(), + receiveAddress.getAddress(), gasPrice, gas, null); + + tx.sign(senderPrivKey); + + System.out.println("v\t\t\t: " + Hex.toHexString(new byte[] { tx.getSignature().v })); + System.out.println("r\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().r))); + System.out.println("s\t\t\t: " + Hex.toHexString(BigIntegers.asUnsignedByteArray(tx.getSignature().s))); + + System.out.println("RLP encoded tx\t\t: " + Hex.toHexString( tx.getEncodedSigned() )); + + // retrieve the signer/sender of the transaction + ECKey key = ECKey.signatureToKey(tx.getHash(), tx.getSignature().toBase64()); + + System.out.println("Tx unsigned RLP\t\t: " + Hex.toHexString( tx.getEncoded())); + System.out.println("Tx signed RLP\t\t: " + Hex.toHexString( tx.getEncodedSigned() )); + + System.out.println("Signature public key\t: " + Hex.toHexString(key.getPubKey())); + System.out.println("Sender is\t\t: " + Hex.toHexString(key.getAddress())); + + Assert.assertEquals("cd2a3d9f938e13cd947ec05abc7fe734df8dd826", + Hex.toHexString(key.getAddress())); + } + + @Test /* encode transaction */ + public void test3() throws Exception { + + BigInteger value = new BigInteger("1000000000000000000000000"); + + byte[] privKey = HashUtil.sha3("cat".getBytes()); + Address receiveAddress = new Address(privKey); + + byte[] gasPrice= Hex.decode("09184e72a000"); + byte[] gas = Hex.decode("4255"); + + Transaction tx = new Transaction(null, value.toByteArray(), + receiveAddress.getAddress(), gasPrice, gas, null); + + tx.sign(privKey); + byte[] payload = tx.getEncodedSigned(); + + System.out.println(Hex.toHexString( payload )); + } + @Test public void testTransactionFromRLP() { // from RLP encoding - byte[] encodedTxBytes = Hex.decode(RLP_ENCODED_TX2); + byte[] encodedTxBytes = Hex.decode(RLP_ENCODED_TX); Transaction tx = new Transaction(encodedTxBytes); assertNull(Hex.toHexString(tx.getNonce())); assertNull(Hex.toHexString(tx.getValue())); - assertEquals(RLP_ENCODED_TX2, Hex.toHexString(tx.getReceiveAddress())); - assertEquals(RLP_ENCODED_TX2, Hex.toHexString(tx.getGasPrice())); - assertEquals(RLP_ENCODED_TX2, Hex.toHexString(tx.getGasLimit())); - assertEquals(RLP_ENCODED_TX2, Hex.toHexString(tx.getData())); - assertEquals(RLP_ENCODED_TX2, Hex.toHexString(tx.getInit())); + assertEquals(RLP_ENCODED_TX, Hex.toHexString(tx.getReceiveAddress())); + assertEquals(RLP_ENCODED_TX, Hex.toHexString(tx.getGasPrice())); + assertEquals(RLP_ENCODED_TX, Hex.toHexString(tx.getGasLimit())); + assertEquals(RLP_ENCODED_TX, Hex.toHexString(tx.getData())); + assertEquals(RLP_ENCODED_TX, Hex.toHexString(tx.getInit())); assertEquals(28, tx.getSignature().v); assertEquals("c2604bd6eeca76afce4e7775d87960e3d4ed3b69235a3f94d6f1497c9831b50c", tx.getSignature().r); assertEquals("664124a6b323350dd57a650434dc6bf8ddf37cd1a2686fee377e512aa12f1214", tx.getSignature().s); - assertEquals(RLP_ENCODED_TX2, Hex.toHexString(tx.getEncoded())); + assertEquals(RLP_ENCODED_TX, Hex.toHexString(tx.getEncodedSigned())); } @Test public void testTransactionFromNew() throws Exception { - byte[] privKeyBytes = Hex.decode("3ecb44df2159c26e0f995712d4f39b6f6e499b40749b1cf1246c37f9516cb6a4"); + byte[] privKeyBytes = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); -// nonce=0, gasprice=10 ** 12, startgas=10000, to=, value=10 ** 16, data='').sign(k) -// byte[] nonce = BigInteger.ZERO.toByteArray(); -// byte[] value = Denomination.ETHER.getDenomination().toByteArray(); -// byte[] recieveAddress = Hex.decode("8a40bfaa73256b60764c1bf40675a99083efb075"); -// byte[] gasPrice = Denomination.SZABO.getDenomination().toByteArray(); -// byte[] gas = new BigInteger("10000").toByteArray(); -// byte[] data = new byte[0]; + String RLP_TX_UNSIGNED = "eb8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc1000080"; + String RLP_TX_SIGNED = "f86b8085e8d4a510008227109413978aee95f38490e9769c39b2773ed763d9cd5f872386f26fc10000801ba0eab47c1a49bf2fe5d40e01d313900e19ca485867d462fe06e139e3a536c6d4f4a014a569d327dcda4b29f74f93c0e9729d2f49ad726e703f9cd90dbb0fbf6649f1"; + + byte[] nonce = BigIntegers.asUnsignedByteArray(BigInteger.ZERO); + byte[] gasPrice = Hex.decode("e8d4a51000"); // 1000000000000 + byte[] gas = Hex.decode("2710"); // 10000 + byte[] recieveAddress = Hex.decode("13978aee95f38490e9769c39b2773ed763d9cd5f"); + byte[] value = Hex.decode("2386f26fc10000"); //10000000000000000" + byte[] data = new byte[0]; - - byte[] nonce = null; - byte[] value = null; - byte[] recieveAddress = Hex.decode("91a10664d0cd489085a7a018beb5245d4f2272f1"); - byte[] gasPrice = Hex.decode("09184e72a000"); - byte[] gas = Hex.decode("2710"); - byte[] data = Hex.decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"); - - Transaction tx = new Transaction(nonce, value, recieveAddress, gasPrice, gas, data); - byte[] encoded = tx.getEncoded(); - String test = Hex.toHexString(encoded); + Transaction tx = new Transaction(nonce, gasPrice, gas, recieveAddress, value, data); + + // Testing unsigned + String encodedUnsigned = Hex.toHexString(tx.getEncoded()); - System.out.println(RLP_ENCODED_TX2); - System.out.println(test); + System.out.println(RLP_TX_UNSIGNED); + System.out.println(encodedUnsigned); - assertEquals(RLP_ENCODED_TX2, test); + assertEquals(RLP_TX_UNSIGNED, encodedUnsigned); assertEquals(HASH_RAW_TX, Hex.toHexString(tx.getHash())); + + // Testing signed tx.sign(privKeyBytes); + String encodedSigned = Hex.toHexString(tx.getEncodedSigned()); + System.out.println(RLP_TX_SIGNED); + System.out.println(encodedSigned); + + assertEquals(RLP_TX_SIGNED, encodedSigned); assertEquals(HASH_RAW_TX, Hex.toHexString(tx.getHash())); } } diff --git a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java index 86cec0ab..08094131 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java @@ -538,7 +538,7 @@ public class MessagesTest { receiveAddress.getAddress(), gasPrice, gas, null); tx.sign(privKey); - tx.getEncoded(); + tx.getEncodedSigned(); List txList = new ArrayList(); txList.add(tx); @@ -548,11 +548,7 @@ public class MessagesTest { //todo: add assertion to the test whenever you know what should be the result System.out.println(Hex.toHexString( transactionsMessage.getPayload() )); - // !!! [ 15:51:24 | eth ] Ignoring invalid transaction: Invalid transaction format: Bad field 1 (8b00d3c21bcecceda1000000) } - - - } diff --git a/ethereumj-core/src/test/java/org/ethereum/transaction/TransactionTest.java b/ethereumj-core/src/test/java/org/ethereum/transaction/TransactionTest.java deleted file mode 100644 index 3f9f28c7..00000000 --- a/ethereumj-core/src/test/java/org/ethereum/transaction/TransactionTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.ethereum.transaction; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; - -import org.ethereum.core.Address; -import org.ethereum.core.Transaction; -import org.ethereum.crypto.ECKey; -import org.ethereum.crypto.HashUtil; -import org.junit.Assert; -import org.junit.Test; -import org.spongycastle.util.BigIntegers; -import org.spongycastle.util.encoders.Hex; - -public class TransactionTest { - - @Test /* sign transaction https://tools.ietf.org/html/rfc6979 */ - public void test1() throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException, IOException { - - //python taken exact data - String txRLPRawData = "a9e880872386f26fc1000085e8d4a510008203e89413978aee95f38490e9769c39b2773ed763d9cd5f80"; - // String txRLPRawData = "f82804881bc16d674ec8000094cd2a3d9f938e13cd947ec05abc7fe734df8dd8268609184e72a0006480"; - - byte[] cowPrivKey = Hex.decode("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4"); - ECKey key = ECKey.fromPrivate(cowPrivKey); - - byte[] data = Hex.decode(txRLPRawData); - - // step 1: serialize + RLP encode - // step 2: hash = sha3(step1) - byte[] txHash = HashUtil.sha3(data); - - String signature = key.doSign(txHash).toBase64(); - System.out.println(signature); - } - - @Test /* achieve public key of the sender */ - public void test2() throws Exception { - - // cat --> 79B08AD8787060333663D19704909EE7B1903E58 - // cow --> CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826 - - BigInteger value = new BigInteger("1000000000000000000000000"); - - byte[] privKey = HashUtil.sha3("cat".getBytes()); - Address receiveAddress = new Address(privKey); - - byte[] senderPrivKey = HashUtil.sha3("cow".getBytes()); - - byte[] gasPrice= Hex.decode("09184e72a000"); - byte[] gas = Hex.decode("4255"); - - Transaction tx = new Transaction(null, value.toByteArray(), - receiveAddress.getAddress(), gasPrice, gas, null); - - tx.sign(senderPrivKey); - - - - System.out.println("r: " + Hex.toHexString(tx.getSignature().r.toByteArray())); - System.out.println("s: " + Hex.toHexString(tx.getSignature().s.toByteArray())); - - System.out.println(Hex.toHexString( tx.getEncoded() )); - - // retrieve the signer/sender of the transaction - ECKey key = ECKey.signatureToKey(tx.getHash(), tx.getSignature().toBase64()); - - System.out.println("Tx unsigned RLP: " + Hex.toHexString( tx.getRlpUnsigned() )); - System.out.println("Tx signed RLP: " + Hex.toHexString( tx.getEncoded() )); - - System.out.println("Signature public key\t: " + Hex.toHexString(key.getPubKey())); - System.out.println("Sender is\t\t: " + Hex.toHexString(key.getAddress())); - - Assert.assertEquals("CD2A3D9F938E13CD947EC05ABC7FE734DF8DD826", - Hex.toHexString(key.getAddress()).toUpperCase()); - } - - - @Test /* encode transaction */ - public void test3() throws Exception { - - BigInteger value = new BigInteger("1000000000000000000000000"); - - byte[] privKey = HashUtil.sha3("cat".getBytes()); - Address receiveAddress = new Address(privKey); - - byte[] gasPrice= Hex.decode("09184e72a000"); - byte[] gas = Hex.decode("4255"); - - Transaction tx = new Transaction(null, value.toByteArray(), - receiveAddress.getAddress(), gasPrice, gas, null); - - tx.sign(privKey); - byte[] payload = tx.getEncoded(); - - System.out.println(Hex.toHexString( payload )); - } - -} diff --git a/ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java b/ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java index 3a280383..ae85f959 100644 --- a/ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/util/RLPTest.java @@ -237,7 +237,7 @@ public class RLPTest { public void testEncodeElementNull(){ byte[] actuals = RLP.encodeElement(null); - assertArrayEquals(new byte[] { (byte) 0x00 }, actuals); + assertArrayEquals(new byte[] { (byte) 0x80 }, actuals); } @Test /** found bug encode list affects element value,