From b3e44f7eeef73d20aa238cb71a2d3b08a901d35a Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Sun, 27 Nov 2016 16:46:55 -0500 Subject: [PATCH 1/3] Fixing the transaction signature rejection issue --- .../luminiasoft/bitshares/Transaction.java | 59 +++++++++++-------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/luminiasoft/bitshares/Transaction.java b/src/main/java/com/luminiasoft/bitshares/Transaction.java index c1efffa..58ad0b8 100644 --- a/src/main/java/com/luminiasoft/bitshares/Transaction.java +++ b/src/main/java/com/luminiasoft/bitshares/Transaction.java @@ -73,24 +73,24 @@ public class Transaction implements ByteSerializable, JsonSerializable { public List getOperations(){ return this.operations; } /** - * Obtains a signature of this transaction. + * Obtains a signature of this transaction. Please note that due to the current reliance on + * bitcoinj to generate the signatures, and due to the fact that it uses deterministic + * ecdsa signatures, we are slightly modifying the expiration time of the transaction while + * we look for a signature that will be accepted by the graphene network. + * + * This should then be called before any other serialization method. * @return: A valid signature of the current transaction. */ - public byte[] getSignature(){ - byte[] serializedTransaction = this.toBytes(); - Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); - boolean isCanonical = false; - int recId = -1; - ECKey.ECDSASignature sig = null; - while(!isCanonical) { - sig = privateKey.sign(hash); - if(!sig.isCanonical()){ - // Signature was not canonical, retrying - continue; - }else{ - // Signature is canonical - isCanonical = true; - } + public byte[] getGrapheneSignature(){ + boolean isGrapheneCanonical = false; + byte[] sigData = null; + + while(!isGrapheneCanonical) { + byte[] serializedTransaction = this.toBytes(); + Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction)); + int recId = -1; + ECKey.ECDSASignature sig = privateKey.sign(hash); + // Now we have to work backwards to figure out the recId needed to recover the signature. for (int i = 0; i < 4; i++) { ECKey k = ECKey.recoverFromSignature(i, sig, hash, privateKey.isCompressed()); @@ -99,15 +99,24 @@ public class Transaction implements ByteSerializable, JsonSerializable { break; } } + + sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S + int headerByte = recId + 27 + (privateKey.isCompressed() ? 4 : 0); + sigData[0] = (byte) headerByte; + System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32); + System.arraycopy(Utils.bigIntegerToBytes(sig.s, 32), 0, sigData, 33, 32); + + // Further "canonicality" tests + if(((sigData[0] & 0x80) != 0) || (sigData[0] == 0) || + ((sigData[1] & 0x80) != 0) || ((sigData[32] & 0x80) != 0) || + (sigData[32] == 0) || ((sigData[33] & 0x80) != 0)){ + this.blockData.setRelativeExpiration(this.blockData.getRelativeExpiration() + 1); + }else{ + isGrapheneCanonical = true; + } } - int headerByte = recId + 27 + (privateKey.isCompressed() ? 4 : 0); - byte[] sigData = new byte[65]; // 1 header + 32 bytes for R + 32 bytes for S - sigData[0] = (byte)headerByte; - System.arraycopy(Utils.bigIntegerToBytes(sig.r, 32), 0, sigData, 1, 32); - System.arraycopy(Utils.bigIntegerToBytes(sig.s, 32), 0, sigData, 33, 32); return sigData; } - /** * Method that creates a serialized byte array with compact information about this transaction * that is needed for the creation of a signature. @@ -156,6 +165,10 @@ public class Transaction implements ByteSerializable, JsonSerializable { public JsonObject toJsonObject() { JsonObject obj = new JsonObject(); + // Getting the signature before anything else, + // since this might change the transaction expiration data slightly + byte[] signature = getGrapheneSignature(); + // Formatting expiration time Date expirationTime = new Date(blockData.getRelativeExpiration() * 1000); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); @@ -166,7 +179,7 @@ public class Transaction implements ByteSerializable, JsonSerializable { // Adding signatures JsonArray signatureArray = new JsonArray(); - signatureArray.add(Util.bytesToHex(getSignature())); + signatureArray.add(Util.bytesToHex(signature)); obj.add(KEY_SIGNATURES, signatureArray); JsonArray operationsArray = new JsonArray(); From 8968b50f48be692c30b5b29013e3779c19f73820 Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Sun, 27 Nov 2016 18:13:23 -0500 Subject: [PATCH 2/3] Fixing response parse --- .../bitshares/ws/GetAccountsByAddress.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/luminiasoft/bitshares/ws/GetAccountsByAddress.java b/src/main/java/com/luminiasoft/bitshares/ws/GetAccountsByAddress.java index 3ffadf3..abf8ade 100644 --- a/src/main/java/com/luminiasoft/bitshares/ws/GetAccountsByAddress.java +++ b/src/main/java/com/luminiasoft/bitshares/ws/GetAccountsByAddress.java @@ -51,10 +51,8 @@ public class GetAccountsByAddress extends WebSocketAdapter { System.out.println("<<< "+frame.getPayloadText()); String response = frame.getPayloadText(); Gson gson = new Gson(); - - Type GetAccountByAddressResponse = new TypeToken>>(){}.getType(); - WitnessResponse>> witnessResponse = gson.fromJson(response, GetAccountByAddressResponse); - + Type GetAccountByAddressResponse = new TypeToken>>>(){}.getType(); + WitnessResponse>>> witnessResponse = gson.fromJson(response, GetAccountByAddressResponse); if (witnessResponse.error != null) { this.mListener.onError(witnessResponse.error); } else { @@ -63,6 +61,12 @@ public class GetAccountsByAddress extends WebSocketAdapter { websocket.disconnect(); } + @Override + public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception { + if(frame.isTextFrame()) + System.out.println(">>> "+frame.getPayloadText()); + } + @Override public void onError(WebSocket websocket, WebSocketException cause) throws Exception { mListener.onError(new BaseResponse.Error(cause.getMessage())); From e1f9f1c023175db59bae9f61150904286dcdfde6 Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Sun, 27 Nov 2016 18:13:49 -0500 Subject: [PATCH 3/3] Minor test-related changes --- .../java/com/luminiasoft/bitshares/Main.java | 4 +- .../java/com/luminiasoft/bitshares/Test.java | 56 +++++++++++++------ 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/luminiasoft/bitshares/Main.java b/src/main/java/com/luminiasoft/bitshares/Main.java index 4099f23..42d31e7 100644 --- a/src/main/java/com/luminiasoft/bitshares/Main.java +++ b/src/main/java/com/luminiasoft/bitshares/Main.java @@ -72,9 +72,9 @@ public class Main { // test.testBip39Opertion(); -// test.testAccountNamebyAddress(); + test.testAccountNamebyAddress(); - test.testAccountNameById(); +// test.testAccountNameById(); // test.testRelativeAccountHistory(); } diff --git a/src/main/java/com/luminiasoft/bitshares/Test.java b/src/main/java/com/luminiasoft/bitshares/Test.java index bef0a8d..c24b665 100644 --- a/src/main/java/com/luminiasoft/bitshares/Test.java +++ b/src/main/java/com/luminiasoft/bitshares/Test.java @@ -25,6 +25,7 @@ import java.lang.reflect.Type; import java.net.URL; import java.nio.file.Paths; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.text.SimpleDateFormat; import java.util.*; import java.util.logging.Level; @@ -174,7 +175,7 @@ public class Test { // return new String(Base64.encode(sigData), Charset.forName("UTF-8")); } - public byte[] testTransactionSerialization(long head_block_number, String head_block_id, long relative_expiration) { + public void testTransactionSerialization(long head_block_number, String head_block_id, long relative_expiration) { BlockData blockData = new BlockData(head_block_number, head_block_id, relative_expiration); ArrayList operations = new ArrayList(); @@ -187,7 +188,6 @@ public class Test { byte[] serializedTransaction = this.transaction.toBytes(); System.out.println("Serialized transaction"); System.out.println(Util.bytesToHex(serializedTransaction)); - return serializedTransaction; } public void testWebSocketTransfer() throws IOException { @@ -335,12 +335,15 @@ public class Test { .setDestination(new UserAccount("1.2.129848")) .setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"))) .setFee(new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0"))) - .setBlockData(new BlockData(43408, 1430521623, 1479231969)) + .setBlockData(new BlockData(Main.REF_BLOCK_NUM, Main.REF_BLOCK_PREFIX, Main.RELATIVE_EXPIRATION)) .setPrivateKey(DumpedPrivateKey.fromBase58(null, Main.WIF).getKey()) .build(); ArrayList transactionList = new ArrayList<>(); transactionList.add(transaction); + + byte[] signature = transaction.getGrapheneSignature(); + System.out.println(Util.bytesToHex(signature)); ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, "2.0", 1); String jsonCall = call.toJsonString(); System.out.println("json call"); @@ -397,7 +400,7 @@ public class Test { } public void testTransactionBroadcastSequence() { - String url = "ws://api.devling.xyz:8088"; + String url = Test.OPENLEDGER_WITNESS_URL; WitnessResponseListener listener = new WitnessResponseListener() { @Override public void onSuccess(WitnessResponse response) { @@ -423,20 +426,29 @@ public class Test { ArrayList transactionList = new ArrayList<>(); transactionList.add(transaction); - ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, "2.0", 1); - WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(5000); - try { - WebSocket mWebSocket = factory.createSocket(url); - mWebSocket.addListener(new TransactionBroadcastSequence(transaction, listener)); - mWebSocket.connect(); - } catch (IOException e) { - System.out.println("IOException. Msg: " + e.getMessage()); - } catch (WebSocketException e) { - System.out.println("WebSocketException. Msg: " + e.getMessage()); - } + transactionList.add(transaction); + + SSLContext context = null; + context = NaiveSSLContext.getInstance("TLS"); + WebSocketFactory factory = new WebSocketFactory(); + + // Set the custom SSL context. + factory.setSSLContext(context); + + WebSocket mWebSocket = factory.createSocket(OPENLEDGER_WITNESS_URL); + + mWebSocket.addListener(new TransactionBroadcastSequence(transaction, listener)); + mWebSocket.connect(); + } catch (MalformedTransactionException e) { System.out.println("MalformedTransactionException. Msg: " + e.getMessage()); + } catch (IOException e) { + System.out.println("IOException. Msg: " + e.getMessage()); + } catch (WebSocketException e) { + System.out.println("WebSocketException. Msg: " + e.getMessage()); + } catch (NoSuchAlgorithmException e) { + System.out.println("NoSuchAlgoritmException. Msg: "+e.getMessage()); } } @@ -609,6 +621,18 @@ public class Test { } public void testAccountNamebyAddress() { + WitnessResponseListener listener = new WitnessResponseListener() { + @Override + public void onSuccess(WitnessResponse response) { + System.out.println("onSuccess"); + } + + @Override + public void onError(BaseResponse.Error error) { + System.out.println("onError"); + } + }; + BrainKey brainKey = new BrainKey(Main.BRAIN_KEY, 0); Address address = new Address(brainKey.getPrivateKey()); try { @@ -621,7 +645,7 @@ public class Test { factory.setSSLContext(context); WebSocket mWebSocket = factory.createSocket(OPENLEDGER_WITNESS_URL); - mWebSocket.addListener(new GetAccountsByAddress(address, mListener)); + mWebSocket.addListener(new GetAccountsByAddress(address, listener)); mWebSocket.connect(); } catch (IOException e) { System.out.println("IOException. Msg: " + e.getMessage());