From cf82f9b4590ba21c3dc0a485867558c7dc40a334 Mon Sep 17 00:00:00 2001 From: romanman Date: Tue, 20 May 2014 19:16:07 +0300 Subject: [PATCH] Transactions, RLP, and more cool stuff... --- ethereumj-core/pom.xml | 20 +++ .../main/java/org/ethereum/core/Address.java | 21 ++- .../java/org/ethereum/core/Transaction.java | 124 ++++++++++-------- .../main/java/org/ethereum/core/Wallet.java | 3 +- .../ethereum/gui/ConnectionConsoleWindow.java | 6 +- .../java/org/ethereum/gui/PayOutDialog.java | 6 +- .../org/ethereum/gui/WalletAddressPanel.java | 2 +- .../java/org/ethereum/manager/MainData.java | 3 +- .../org/ethereum/net/client/ClientPeer.java | 2 +- .../ethereum/net/message/StaticMessages.java | 4 +- .../net/message/TransactionsMessage.java | 4 +- .../src/main/java/org/ethereum/util/RLP.java | 2 +- .../org/ethereum/core/TransactionTest.java | 4 +- .../java/org/ethereum/net/MessagesTest.java | 5 +- .../ethereum/transaction/TransactionTest.java | 25 +++- 15 files changed, 134 insertions(+), 97 deletions(-) diff --git a/ethereumj-core/pom.xml b/ethereumj-core/pom.xml index 0a052b9b..82b7acf4 100644 --- a/ethereumj-core/pom.xml +++ b/ethereumj-core/pom.xml @@ -7,6 +7,12 @@ 0.1-SNAPSHOT EthereumJ http://www.ethereumj.org + + + UTF-8 @@ -114,6 +120,20 @@ 1.7 + + + maven-assembly-plugin + + + + org.ethereum.gui.ToolBar + + + + jar-with-dependencies + + + \ No newline at end of file diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Address.java b/ethereumj-core/src/main/java/org/ethereum/core/Address.java index 727261af..fc4c9df7 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Address.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Address.java @@ -1,7 +1,6 @@ package org.ethereum.core; import org.ethereum.crypto.ECKey; -import org.ethereum.crypto.HashUtil; import org.ethereum.util.Utils; import org.spongycastle.util.encoders.Hex; @@ -16,34 +15,34 @@ import java.util.Arrays; public class Address { byte[] privKey; - byte[] pubKey; + byte[] address; public Address(){ privKey = new BigInteger(130, Utils.getRandom()).toString(32).getBytes(); - this.pubKey = ECKey.fromPrivate(privKey).getAddress(); + this.address = ECKey.fromPrivate(privKey).getAddress(); } public Address(byte[] privKey) { this.privKey = privKey; - this.pubKey = ECKey.fromPrivate(privKey).getAddress(); + this.address = ECKey.fromPrivate(privKey).getAddress(); } - public Address(byte[] privKey, byte[] pubKey) { + public Address(byte[] privKey, byte[] address) { this.privKey = privKey; - this.pubKey = pubKey; + this.address = address; } public byte[] getPrivKey() { return privKey; } - public byte[] getPubKey() { - return pubKey; + public byte[] getAddress() { + return address; } @Override public String toString() { - return Hex.toHexString(pubKey); + return Hex.toHexString(address); } @Override @@ -53,13 +52,13 @@ public class Address { Address address = (Address) o; - if (!Arrays.equals(pubKey, address.pubKey)) return false; + if (!Arrays.equals(this.address, address.address)) return false; return true; } @Override public int hashCode() { - return Arrays.hashCode(pubKey); + return Arrays.hashCode(address); } } 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 7da18715..9b4eb362 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java @@ -9,6 +9,7 @@ import org.ethereum.util.RLP; import org.ethereum.util.RLPItem; import org.ethereum.util.RLPList; import org.ethereum.util.Utils; +import org.spongycastle.util.BigIntegers; import org.spongycastle.util.encoders.Hex; import edu.emory.mathcs.backport.java.util.Arrays; @@ -24,6 +25,8 @@ import edu.emory.mathcs.backport.java.util.Arrays; public class Transaction { private byte[] rlpEncoded; + private byte[] unsignedRLPEncoded; + private boolean parsed = false; /* creation contract tx @@ -129,8 +132,11 @@ public class Transaction { } public byte[] getHash() { + if (!parsed) rlpParse(); - return HashUtil.sha3(this.getEncoded(false)); + byte[] plainMsg = this.getRlpUnsigned(); + + return HashUtil.sha3(plainMsg); } public byte[] getNonce() { @@ -218,71 +224,77 @@ public class Transaction { ", signatureS=" + Utils.toHexString(signature.s.toByteArray()) + ']'; } - - public byte[] getEncoded(boolean signed) { - if(rlpEncoded == null) { - // TODO: Alternative clean way to encode, using RLP.encode() after it's optimized - // return new Object[] { nonce, value, receiveAddress, gasPrice, - // gasLimit, data, init, signature }; + /** + * For signature games you have to keep also + * rlp of the transaction without any signature data + */ + public byte[] getRlpUnsigned(){ - /* Temporary order for an RLP encoded transaction in cpp client */ - byte[] nonce = RLP.encodeElement(this.nonce); - byte[] gasPrice = RLP.encodeElement(this.gasPrice); - byte[] gasLimit = RLP.encodeElement(this.gasLimit); - byte[] receiveAddress = RLP.encodeElement(this.receiveAddress); - byte[] value = RLP.encodeElement(this.value); - byte[] data = RLP.encodeElement(this.data); + if (unsignedRLPEncoded != null) return unsignedRLPEncoded; - byte[] v = null; - byte[] r = null; - byte[] s = null; + byte[] nonce = RLP.encodeElement(this.nonce); + byte[] gasPrice = RLP.encodeElement(this.gasPrice); + byte[] gasLimit = RLP.encodeElement(this.gasLimit); + byte[] receiveAddress = RLP.encodeElement(this.receiveAddress); + byte[] value = RLP.encodeElement(this.value); + byte[] data = RLP.encodeElement(this.data); - if(signed && signature != null) { -// byte[] signature = RLP.encodeElement(this.signature); + if(Arrays.equals(this.receiveAddress, new byte[0])) { + byte[] init = RLP.encodeElement(this.init); + this.unsignedRLPEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, value, receiveAddress, + data, init); + } else { + this.unsignedRLPEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, value, receiveAddress, + data); + } - v = RLP.encodeByte( signature.v ); - r = RLP.encodeElement(signature.r.toByteArray()); - s = RLP.encodeElement(signature.s.toByteArray()); + return unsignedRLPEncoded; + } - if(Arrays.equals(this.receiveAddress, new byte[0])) { - byte[] init = RLP.encodeElement(this.init); - this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, value, receiveAddress, - data, init, v, r, s); - } else { - this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, value, receiveAddress, - data, v, r, s); - } + public byte[] getEncoded() { - } else { + if(rlpEncoded != null) return rlpEncoded; - byte[] result; + /* Temporary order for an RLP encoded transaction in cpp client */ + byte[] nonce = RLP.encodeElement(this.nonce); + byte[] gasPrice = RLP.encodeElement(this.gasPrice); + byte[] gasLimit = RLP.encodeElement(this.gasLimit); + byte[] receiveAddress = RLP.encodeElement(this.receiveAddress); + byte[] value = RLP.encodeElement(this.value); + byte[] data = RLP.encodeElement(this.data); - if(Arrays.equals(this.receiveAddress, new byte[0])) { - byte[] init = RLP.encodeElement(this.init); - result = RLP.encodeList(nonce, gasPrice, gasLimit, value, receiveAddress, - data, init); - } else { - result = RLP.encodeList(nonce, gasPrice, gasLimit, value, receiveAddress, - data); - } - - return result; - } + byte[] v = null; + byte[] r = null; + byte[] s = null; - /* Order of the Yellow Paper / eth-go & pyethereum clients - byte[] nonce = RLP.encodeElement(this.nonce); - byte[] value = RLP.encodeElement(this.value); - byte[] receiveAddress = RLP.encodeElement(this.receiveAddress); - byte[] gasPrice = RLP.encodeElement(this.gasPrice); - byte[] gasLimit = RLP.encodeElement(this.gasLimit); - byte[] data = RLP.encodeElement(this.data); - byte[] init = RLP.encodeElement(this.init); - */ - - - } - return rlpEncoded; + v = RLP.encodeByte( signature.v ); + r = RLP.encodeElement(BigIntegers.asUnsignedByteArray(signature.r)); + 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, + data, init, v, r, s); + } else { + this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, value, receiveAddress, + data, v, r, s); + } + + + + + /* Order of the Yellow Paper / eth-go & pyethereum clients + byte[] nonce = RLP.encodeElement(this.nonce); + byte[] value = RLP.encodeElement(this.value); + byte[] receiveAddress = RLP.encodeElement(this.receiveAddress); + byte[] gasPrice = RLP.encodeElement(this.gasPrice); + byte[] gasLimit = RLP.encodeElement(this.gasLimit); + byte[] data = RLP.encodeElement(this.data); + byte[] init = RLP.encodeElement(this.init); + */ + + return rlpEncoded; } } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Wallet.java b/ethereumj-core/src/main/java/org/ethereum/core/Wallet.java index aea3d900..f07d09d3 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Wallet.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Wallet.java @@ -8,7 +8,6 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; @@ -197,7 +196,7 @@ public class Wallet { raw.setAttributeNode(id); Element addressE = doc.createElement("address"); - addressE.setTextContent(Hex.toHexString(address.getPubKey())); + addressE.setTextContent(Hex.toHexString(address.getAddress())); Attr nonce = doc.createAttribute("nonce"); nonce.setValue("0"); diff --git a/ethereumj-core/src/main/java/org/ethereum/gui/ConnectionConsoleWindow.java b/ethereumj-core/src/main/java/org/ethereum/gui/ConnectionConsoleWindow.java index a64c97af..46289d48 100644 --- a/ethereumj-core/src/main/java/org/ethereum/gui/ConnectionConsoleWindow.java +++ b/ethereumj-core/src/main/java/org/ethereum/gui/ConnectionConsoleWindow.java @@ -69,11 +69,11 @@ public class ConnectionConsoleWindow extends JFrame implements PeerListener{ Thread t = new Thread() { public void run() { -// new ClientPeer(thisConsole).connect("82.217.72.169", 30303); -// new ClientPeer(thisConsole).connect("54.201.28.117", 30303); +// new ClientPeer(thisConsole).connect("54.201.28.117", 30303); // peer discovery // new ClientPeer(thisConsole).connect("82.217.72.169", 30303); // Nick - new ClientPeer(thisConsole).connect("54.204.10.41", 30303); +// new ClientPeer(thisConsole).connect("54.204.10.41", 30303); + new ClientPeer(thisConsole).connect("54.211.14.10", 30303); // new ClientPeer(thisConsole).connect("192.168.1.102", 30303); } }; diff --git a/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java b/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java index d7a63ff9..e41e3931 100644 --- a/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java +++ b/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java @@ -5,18 +5,14 @@ import org.ethereum.core.Transaction; import org.ethereum.crypto.HashUtil; import org.ethereum.manager.MainData; import org.ethereum.net.client.ClientPeer; -import org.ethereum.net.message.TransactionsMessage; import org.spongycastle.util.encoders.Hex; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; import java.math.BigInteger; -import java.util.*; import javax.swing.*; -import javax.swing.text.DefaultEditorKit; /** * www.ethereumJ.com @@ -75,7 +71,7 @@ class PayOutDialog extends JDialog { byte[] gas = Hex.decode("4255"); Transaction tx = new Transaction(null, value.toByteArray(), - receiveAddress.getPubKey(), gasPrice, gas, null); + receiveAddress.getAddress(), gasPrice, gas, null); try { tx.sign(senderPrivKey); diff --git a/ethereumj-core/src/main/java/org/ethereum/gui/WalletAddressPanel.java b/ethereumj-core/src/main/java/org/ethereum/gui/WalletAddressPanel.java index 2f27fe9e..6c22f006 100644 --- a/ethereumj-core/src/main/java/org/ethereum/gui/WalletAddressPanel.java +++ b/ethereumj-core/src/main/java/org/ethereum/gui/WalletAddressPanel.java @@ -35,7 +35,7 @@ public class WalletAddressPanel extends JPanel{ addressField.setBorder(border); addressField.setEnabled(true); addressField.setEditable(false); - addressField.setText(Hex.toHexString(address.getPubKey()).toUpperCase()); + addressField.setText(Hex.toHexString(address.getAddress()).toUpperCase()); addressField.setForeground(new Color(143, 170, 220)); addressField.setFont(new Font("Monospaced", 0, 12)); addressField.setPreferredSize(new Dimension(300, 35)); diff --git a/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java b/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java index 0ec1b9ae..bcac38d4 100644 --- a/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java +++ b/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java @@ -57,8 +57,7 @@ public class MainData { // if it is the first block to add // check that the parent is the genesis if (blockChainDB.isEmpty() && - !"69a7356a245f9dc5b865475ada5ee4e89b18f93c06503a9db3b3630e88e9fb4e". - equals(Hex.toHexString(firstBlockToAdd.getParentHash()))){ + !Arrays.equals(StaticMessages.GENESSIS_HASH, firstBlockToAdd.getParentHash())){ return; } 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 9d9cb1b5..1d6ade64 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 @@ -95,7 +95,7 @@ public class ClientPeer { */ public void sendTransaction(Transaction transaction){ - transaction.getEncoded(true); + transaction.getEncoded(); 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/StaticMessages.java b/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java index 9b9a261e..08631659 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java @@ -50,14 +50,14 @@ public class StaticMessages { public static final byte[] GET_CHAIN = Hex.decode("2240089100000027F82514A069A7356A245F9DC5B865475ADA5EE4E89B18F93C06503A9DB3B3630E88E9FB4E820100"); - public static final byte[] GENESSIS_HASH = Hex.decode("69a7356a245f9dc5b865475ada5ee4e89b18f93c06503a9db3b3630e88e9fb4e"); + public static final byte[] GENESSIS_HASH = Hex.decode("f5232afe32aba6b366f8aa86a6939437c5e13d1fd71a0f51e77735d3456eb1a6"); public static final byte[] MAGIC_PACKET = Hex.decode("22400891"); static { byte[] peerIdBytes = HashUtil.randomPeerId(); - HELLO_MESSAGE = new HelloMessage((byte)0x0F, (byte)0x00, "EthereumJ [v0.0.1] pure java [by Roman Mandeleil]", + HELLO_MESSAGE = new HelloMessage((byte)0x10, (byte)0x00, "EthereumJ [v0.5.1] pure java by RomanJ", (byte)0b00000111, (short)30303, peerIdBytes); /* 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 4b8dc829..b881b5c1 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(true); + byte[] txPayload = tx.getEncoded(); 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(true); + elements[i + 1] = transactionList.get(i).getEncoded(); } payload = RLP.encodeList(elements); diff --git a/ethereumj-core/src/main/java/org/ethereum/util/RLP.java b/ethereumj-core/src/main/java/org/ethereum/util/RLP.java index 08a2566a..fe5569c5 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/RLP.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/RLP.java @@ -98,7 +98,7 @@ public class RLP { * followed by the concatenation of the RLP encodings of the items. The * range of the first byte is thus [0xf8, 0xff]. */ - private static int OFFSET_LONG_LIST = 0xf8; + private static int OFFSET_LONG_LIST = 0xf7; /* ****************************************************** 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 d7810b92..c79c1779 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/TransactionTest.java @@ -31,7 +31,7 @@ public class TransactionTest { assertEquals("c2604bd6eeca76afce4e7775d87960e3d4ed3b69235a3f94d6f1497c9831b50c", tx.getSignature().r); assertEquals("664124a6b323350dd57a650434dc6bf8ddf37cd1a2686fee377e512aa12f1214", tx.getSignature().s); - assertEquals(RLP_ENCODED_TX2, Hex.toHexString(tx.getEncoded(false))); + assertEquals(RLP_ENCODED_TX2, Hex.toHexString(tx.getEncoded())); } @Test @@ -55,7 +55,7 @@ public class TransactionTest { byte[] data = Hex.decode("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"); Transaction tx = new Transaction(nonce, value, recieveAddress, gasPrice, gas, data); - byte[] encoded = tx.getEncoded(false); + byte[] encoded = tx.getEncoded(); String test = Hex.toHexString(encoded); System.out.println(RLP_ENCODED_TX2); 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 d77ad737..86cec0ab 100644 --- a/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/net/MessagesTest.java @@ -7,7 +7,6 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; -import junit.framework.Assert; import org.ethereum.core.Address; import org.ethereum.core.Block; import org.ethereum.core.Transaction; @@ -536,10 +535,10 @@ public class MessagesTest { byte[] gas = Hex.decode("4255"); Transaction tx = new Transaction(null, value.toByteArray(), - receiveAddress.getPubKey(), gasPrice, gas, null); + receiveAddress.getAddress(), gasPrice, gas, null); tx.sign(privKey); - tx.getEncoded(true); + tx.getEncoded(); List txList = new ArrayList(); txList.add(tx); diff --git a/ethereumj-core/src/test/java/org/ethereum/transaction/TransactionTest.java b/ethereumj-core/src/test/java/org/ethereum/transaction/TransactionTest.java index 88e0f1f8..3f9f28c7 100644 --- a/ethereumj-core/src/test/java/org/ethereum/transaction/TransactionTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/transaction/TransactionTest.java @@ -5,13 +5,14 @@ import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; -import java.security.SignatureException; 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 { @@ -53,16 +54,28 @@ public class TransactionTest { byte[] gas = Hex.decode("4255"); Transaction tx = new Transaction(null, value.toByteArray(), - receiveAddress.getPubKey(), gasPrice, gas, null); + receiveAddress.getAddress(), gasPrice, gas, null); tx.sign(senderPrivKey); - System.out.println(tx.toString()); - ECKey key = ECKey.signatureToKey(HashUtil.sha3(tx.getEncoded(true)), tx.getSignature().toBase64()); + + 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()); } @@ -78,10 +91,10 @@ public class TransactionTest { byte[] gas = Hex.decode("4255"); Transaction tx = new Transaction(null, value.toByteArray(), - receiveAddress.getPubKey(), gasPrice, gas, null); + receiveAddress.getAddress(), gasPrice, gas, null); tx.sign(privKey); - byte[] payload = tx.getEncoded(true); + byte[] payload = tx.getEncoded(); System.out.println(Hex.toHexString( payload )); }