From 4f64391e00b350ef052ecec713eff1246a16decc Mon Sep 17 00:00:00 2001 From: "Nelson R. Perez" Date: Tue, 20 Dec 2016 18:35:17 -0500 Subject: [PATCH] Fixed memo --- .../bitsharesmunich/graphenej/BrainKey.java | 5 +- .../de/bitsharesmunich/graphenej/Main.java | 5 +- .../de/bitsharesmunich/graphenej/Test.java | 39 ++- .../de/bitsharesmunich/graphenej/Util.java | 24 ++ .../graphenej/crypto/Random.java | 15 + .../graphenej/errors/ChecksumException.java | 10 + .../graphenej/objects/Memo.java | 312 ++++++++++-------- .../graphenej/objects/MemoBuilder.java | 74 ----- .../graphenej/objects/MemoTest.java | 105 ++++++ 9 files changed, 364 insertions(+), 225 deletions(-) create mode 100644 src/main/java/de/bitsharesmunich/graphenej/crypto/Random.java create mode 100644 src/main/java/de/bitsharesmunich/graphenej/errors/ChecksumException.java delete mode 100644 src/main/java/de/bitsharesmunich/graphenej/objects/MemoBuilder.java create mode 100644 src/test/java/de/bitsharesmunich/graphenej/objects/MemoTest.java diff --git a/src/main/java/de/bitsharesmunich/graphenej/BrainKey.java b/src/main/java/de/bitsharesmunich/graphenej/BrainKey.java index 4fcfdae..c6b1873 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/BrainKey.java +++ b/src/main/java/de/bitsharesmunich/graphenej/BrainKey.java @@ -1,6 +1,7 @@ package de.bitsharesmunich.graphenej; import de.bitsharesmunich.graphenej.crypto.AndroidRandomSource; +import de.bitsharesmunich.graphenej.crypto.Random; import de.bitsharesmunich.graphenej.crypto.SecureRandomStrengthener; import org.bitcoinj.core.DumpedPrivateKey; import org.bitcoinj.core.ECKey; @@ -43,9 +44,7 @@ public class BrainKey { String[] wordArray = words.split(","); ArrayList suggestedBrainKey = new ArrayList(); assert (wordArray.length == DICT_WORD_COUNT); - SecureRandomStrengthener randomStrengthener = SecureRandomStrengthener.getInstance(); - randomStrengthener.addEntropySource(new AndroidRandomSource()); - SecureRandom secureRandom = randomStrengthener.generateAndSeedRandomNumberGenerator(); + SecureRandom secureRandom = Random.getSecureRandom(); int index; for (int i = 0; i < BRAINKEY_WORD_COUNT; i++) { index = secureRandom.nextInt(DICT_WORD_COUNT - 1); diff --git a/src/main/java/de/bitsharesmunich/graphenej/Main.java b/src/main/java/de/bitsharesmunich/graphenej/Main.java index 4167dde..c8f65d7 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/Main.java +++ b/src/main/java/de/bitsharesmunich/graphenej/Main.java @@ -42,7 +42,7 @@ public class Main { // test.testGetDynamicParams(); // test.testGetRequiredFeesSerialization(); // test.testRequiredFeesResponse(); -// test.testTransactionBroadcastSequence(); + test.testTransactionBroadcastSequence(); // test.testAccountLookupDeserialization(); // test.testPrivateKeyManipulations(); // test.testPublicKeyManipulations(); @@ -67,6 +67,7 @@ public class Main { // test.testLookupAssetSymbols(); // test.testGetBlockHeader(); //test.testGetLimitOrders(); - test.testGetTradeHistory(); +// test.testGetTradeHistory(); +// test.testAssetSerialization(); } } diff --git a/src/main/java/de/bitsharesmunich/graphenej/Test.java b/src/main/java/de/bitsharesmunich/graphenej/Test.java index 2109eec..5053a5e 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/Test.java +++ b/src/main/java/de/bitsharesmunich/graphenej/Test.java @@ -6,12 +6,10 @@ import com.google.common.primitives.UnsignedLong; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; -import com.google.gson.JsonElement; import com.google.gson.reflect.TypeToken; import de.bitsharesmunich.graphenej.errors.MalformedAddressException; import de.bitsharesmunich.graphenej.errors.MalformedTransactionException; import de.bitsharesmunich.graphenej.interfaces.WitnessResponseListener; -import de.bitsharesmunich.graphenej.objects.MemoBuilder; import de.bitsharesmunich.graphenej.test.NaiveSSLContext; import com.neovisionaries.ws.client.*; import de.bitsharesmunich.graphenej.api.*; @@ -300,15 +298,18 @@ public class Test { }; try { - // Creating memo ECKey from = new BrainKey(Main.BILTHON_83_BRAIN_KEY, 0).getPrivateKey(); PublicKey to = new PublicKey(ECKey.fromPublicOnly(new BrainKey(Main.BILTHON_5_BRAIN_KEY, 0).getPublicKey())); - Memo memo = new MemoBuilder().setFromKey(from).setToKey(to).setMessage("sample message").build(); + + // Creating memo + long nonce = 1; + byte[] encryptedMessage = Memo.encryptMessage(from, to, nonce, "another message"); + Memo memo = new Memo(new Address(ECKey.fromPublicOnly(from.getPubKey())), new Address(to.getKey()), nonce, encryptedMessage); // Creating transaction Transaction transaction = new TransferTransactionBuilder() - .setSource(new UserAccount("1.2.139270")) // bilthon-83 - .setDestination(new UserAccount("1.2.142115")) // bilthon-5 + .setSource(new UserAccount("1.2.138632")) // bilthon-83 + .setDestination(new UserAccount("1.2.139313")) // bilthon-5 .setAmount(new AssetAmount(UnsignedLong.valueOf(1), new Asset("1.3.0"))) .setFee(new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0"))) .setPrivateKey(new BrainKey(Main.BILTHON_83_BRAIN_KEY, 0).getPrivateKey()) @@ -458,9 +459,9 @@ public class Test { String suggestion = BrainKey.suggest(words); brainKey = new BrainKey(suggestion, 0); } else { - System.out.println("Using brain key: " + Main.BILTHON_83_BRAIN_KEY); -// brainKey = new BrainKey(Main.BILTHON_83_BRAIN_KEY, 0); - brainKey = new BrainKey("AMIDIC SKELIC CARLOAD CAPSULE FACY WONNED STICH BULBULE MESOLE SEMEED STRAVE PREBORN", 0); + System.out.println("Using brain key: " + Main.BILTHON_5_BRAIN_KEY); + brainKey = new BrainKey(Main.BILTHON_5_BRAIN_KEY, 0); + } ECKey key = brainKey.getPrivateKey(); System.out.println("Private key..................: " + Util.bytesToHex(key.getSecretBytes())); @@ -761,11 +762,11 @@ public class Test { ECKey from = new BrainKey(Main.BILTHON_83_BRAIN_KEY, 0).getPrivateKey(); PublicKey to = new PublicKey(ECKey.fromPublicOnly(new BrainKey(Main.BILTHON_5_BRAIN_KEY, 0).getPublicKey())); - Memo sendMemo = new MemoBuilder().setFromKey(from).setToKey(to).setMessage("test message").build(); +// Memo sendMemo = new MemoBuilder().setFromKey(from).setToKey(to).setMessage("test message").build(); - JsonElement memoJson = sendMemo.toJsonObject(); - System.out.println("generated Json : " + memoJson.toString()); -// System.out.println("Decode Memo : " + Memo.decodeMessage(from, to, memoJson.getAsJsonObject().get("message").getAsString(), memoJson.getAsJsonObject().get("nonce").getAsString())); +// JsonElement memoJson = sendMemo.toJsonObject(); +// System.out.println("generated Json : " + memoJson.toString()); +// System.out.println("Decode Memo : " + Memo.decryptMessage(from, to, memoJson.getAsJsonObject().get("message").getAsString(), memoJson.getAsJsonObject().get("nonce").getAsString())); } public void testGetRelativeAccountHistory(){ @@ -974,4 +975,16 @@ public class Test { System.out.println("IOException. Msg: " + e.getMessage()); } } + + public void testAssetSerialization(){ + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + DataOutput out = new DataOutputStream(byteArrayOutputStream); + try { + Varint.writeUnsignedVarLong(120, out); + } catch (IOException e) { + e.printStackTrace(); + } + byte[] bytes = byteArrayOutputStream.toByteArray(); + System.out.println("serialized: "+Util.bytesToHex(bytes)); + } } diff --git a/src/main/java/de/bitsharesmunich/graphenej/Util.java b/src/main/java/de/bitsharesmunich/graphenej/Util.java index fa49cc0..7ad37c9 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/Util.java +++ b/src/main/java/de/bitsharesmunich/graphenej/Util.java @@ -33,6 +33,11 @@ public class Util { public static final int LZMA = 0; public static final int XZ = 1; + /** + * Converts an hexadecimal string to its corresponding byte[] value. + * @param s: String with hexadecimal numbers representing a byte array. + * @return: The actual byte array. + */ public static byte[] hexToBytes(String s) { int len = s.length(); byte[] data = new byte[len / 2]; @@ -43,6 +48,11 @@ public class Util { return data; } + /** + * Converts a byte array, into a user-friendly hexadecimal string. + * @param bytes: A byte array. + * @return: A string with the representation of the byte array. + */ public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for ( int j = 0; j < bytes.length; j++ ) { @@ -53,6 +63,19 @@ public class Util { return new String(hexChars); } + /** + * Decodes an ascii string to a byte array. + * @param data: Arbitrary ascii-encoded string. + * @return: Array of bytes. + */ + public static byte[] hexlify(String data){ + ByteBuffer buffer = ByteBuffer.allocate(data.length()); + for(char letter : data.toCharArray()){ + buffer.put((byte) letter); + } + return buffer.array(); + } + /** * Utility function that compresses data using the LZMA algorithm. * @param inputBytes Input bytes of the data to be compressed. @@ -168,6 +191,7 @@ public class Util { System.arraycopy(result, 32, ivBytes, 0, 16); byte[] sksBytes = new byte[32]; System.arraycopy(result, 0, sksBytes, 0, 32); + PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine())); cipher.init(true, new ParametersWithIV(new KeyParameter(sksBytes), ivBytes)); byte[] temp = new byte[input.length + (16 - (input.length % 16))]; diff --git a/src/main/java/de/bitsharesmunich/graphenej/crypto/Random.java b/src/main/java/de/bitsharesmunich/graphenej/crypto/Random.java new file mode 100644 index 0000000..33dacc2 --- /dev/null +++ b/src/main/java/de/bitsharesmunich/graphenej/crypto/Random.java @@ -0,0 +1,15 @@ +package de.bitsharesmunich.graphenej.crypto; + +import java.security.SecureRandom; + +/** + * Created by nelson on 12/20/16. + */ +public class Random { + + public static SecureRandom getSecureRandom(){ + SecureRandomStrengthener randomStrengthener = SecureRandomStrengthener.getInstance(); +// randomStrengthener.addEntropySource(new AndroidRandomSource()); + return randomStrengthener.generateAndSeedRandomNumberGenerator(); + } +} diff --git a/src/main/java/de/bitsharesmunich/graphenej/errors/ChecksumException.java b/src/main/java/de/bitsharesmunich/graphenej/errors/ChecksumException.java new file mode 100644 index 0000000..394002b --- /dev/null +++ b/src/main/java/de/bitsharesmunich/graphenej/errors/ChecksumException.java @@ -0,0 +1,10 @@ +package de.bitsharesmunich.graphenej.errors; + +/** + * Created by nelson on 12/20/16. + */ +public class ChecksumException extends Exception { + public ChecksumException(String message){ + super(message); + } +} diff --git a/src/main/java/de/bitsharesmunich/graphenej/objects/Memo.java b/src/main/java/de/bitsharesmunich/graphenej/objects/Memo.java index e055091..130eedb 100644 --- a/src/main/java/de/bitsharesmunich/graphenej/objects/Memo.java +++ b/src/main/java/de/bitsharesmunich/graphenej/objects/Memo.java @@ -3,17 +3,18 @@ package de.bitsharesmunich.graphenej.objects; import com.google.common.primitives.Bytes; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import de.bitsharesmunich.graphenej.Address; import de.bitsharesmunich.graphenej.PublicKey; import de.bitsharesmunich.graphenej.Util; -import de.bitsharesmunich.graphenej.crypto.SecureRandomStrengthener; +import de.bitsharesmunich.graphenej.errors.ChecksumException; import de.bitsharesmunich.graphenej.interfaces.ByteSerializable; import de.bitsharesmunich.graphenej.interfaces.JsonSerializable; import org.bitcoinj.core.ECKey; +import org.spongycastle.math.ec.ECPoint; -import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; +import java.util.Arrays; /** * Created by nelson on 11/9/16. @@ -25,9 +26,9 @@ public class Memo implements ByteSerializable, JsonSerializable { public static final String KEY_NONCE = "nonce"; public static final String KEY_MESSAGE = "message"; - private ECKey from; - private PublicKey to; - private final byte[] nonce = new byte[8]; + private Address from; + private Address to; + private long nonce; private byte[] message; /** @@ -39,134 +40,177 @@ public class Memo implements ByteSerializable, JsonSerializable { this.message = null; } + /** + * Constructor used for private memos. + * @param from: Address of sender + * @param to: Address of recipient. + * @param nonce: Nonce used in the encryption. + * @param message: Message in ciphertext. + */ + public Memo(Address from, Address to, long nonce, byte[] message){ + this.from = from; + this.to = to; + this.nonce = nonce; + this.message = message; + } + + /** + * Constructor intended to be used with public memos + * @param message: Message in plaintext. + */ + public Memo(String message){ + this.message = message.getBytes(); + } + + /** + * Method used to decrypt memo data. + * @param privateKey: Private key of the sender. + * @param publicKey: Public key of the recipient. + * @param nonce: The nonce. + * @param message: Plaintext message. + * @return: The encrypted version of the message. + */ + public static byte[] encryptMessage(ECKey privateKey, PublicKey publicKey, long nonce, String message){ + byte[] encrypted = null; + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); + + // Getting nonce bytes + String stringNonce = String.format("%d", nonce); + byte[] nonceBytes = Arrays.copyOfRange(Util.hexlify(stringNonce), 0, stringNonce.length()); + + // Getting shared secret + byte[] secret = publicKey.getKey().getPubKeyPoint().multiply(privateKey.getPrivKey()).normalize().getXCoord().getEncoded(); + + // SHA-512 of shared secret + byte[] ss = sha512.digest(secret); + + byte[] seed = Bytes.concat(nonceBytes, Util.hexlify(Util.bytesToHex(ss))); + + // Calculating checksum + byte[] sha256Msg = sha256.digest(message.getBytes()); + byte[] checksum = Arrays.copyOfRange(sha256Msg, 0, 4); + + // Concatenating checksum + message bytes + byte[] msgFinal = Bytes.concat(checksum, message.getBytes()); + + // Applying encryption + encrypted = Util.encryptAES(msgFinal, seed); + } catch (NoSuchAlgorithmException ex) { + System.out.println("NoSuchAlgotithmException. Msg:"+ ex.getMessage()); + } + return encrypted; + } + + /** + * Method used to encrypt memo data. + * @param privateKey: Private key of the sender. + * @param destinationAddress: Address of the recipient. + * @param nonce: The nonce. + * @param message: Plaintext message. + * @return: The encrypted version of the message. + */ + public static byte[] encryptMessage(ECKey privateKey, Address destinationAddress, long nonce, String message){ + return encryptMessage(privateKey, destinationAddress.getPublicKey(), nonce, message); + } + + + /** + * Method used to decrypt memo data. + * @param privateKey: The private key of the recipient. + * @param publicKey: The public key of the sender. + * @param nonce: The nonce. + * @param message: The encrypted message. + * @return: The plaintext version of the enrcrypted message. + * @throws ChecksumException + */ + public static String decryptMessage(ECKey privateKey, PublicKey publicKey, long nonce, byte[] message) throws ChecksumException { + String plaintext = ""; + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); + + // Getting nonce bytes + String stringNonce = String.format("%d", nonce); + byte[] nonceBytes = Arrays.copyOfRange(Util.hexlify(stringNonce), 0, stringNonce.length()); + + // Getting shared secret + byte[] secret = publicKey.getKey().getPubKeyPoint().multiply(privateKey.getPrivKey()).normalize().getXCoord().getEncoded(); + + // SHA-512 of shared secret + byte[] ss = sha512.digest(secret); + + byte[] seed = Bytes.concat(nonceBytes, Util.hexlify(Util.bytesToHex(ss))); + + // Calculating checksum + byte[] sha256Msg = sha256.digest(message); + + + // Applying decryption + byte[] temp = Util.decryptAES(message, seed); + byte[] checksum = Arrays.copyOfRange(temp, 0, 4); + byte[] decrypted = Arrays.copyOfRange(temp, 4, temp.length); + plaintext = new String(decrypted); + byte[] checksumConfirmation = Arrays.copyOfRange(sha256.digest(decrypted), 0, 4); + boolean checksumVerification = Arrays.equals(checksum, checksumConfirmation); + if(!checksumVerification){ + throw new ChecksumException("Invalid checksum found while performing decryption"); + } + } catch (NoSuchAlgorithmException e) { + System.out.println("NoSuchAlgotithmException. Msg:"+ e.getMessage()); + } + + return plaintext; + } + + /** + * Method used to decrypt memo data. + * @param privateKey: The private key of the recipient. + * @param sourceAddress: The public address key of the sender. + * @param nonce: The nonce. + * @param message: The encrypted message. + * @return: The plaintext version of the enrcrypted message. + * @throws ChecksumException + */ + public static String decryptMessage(ECKey privateKey, Address sourceAddress, long nonce, byte[] message) throws ChecksumException { + return decryptMessage(privateKey, sourceAddress.getPublicKey(), nonce, message); + } + + /** * Implement metod, serialized this Object * @return the byte array of this object serialized */ @Override public byte[] toBytes() { - if ((this.from == null) || (this.to == null) || (this.message == null)) { + if ((this.from == null) && (this.to == null) && (this.message == null)) { return new byte[]{(byte) 0}; + } else if(this.from == null && this.to == null & this.message != null){ + return Bytes.concat(new byte[]{1}, + new byte[]{(byte)0}, + new byte[]{(byte)0}, + new byte[]{(byte)0}, + new byte[]{(byte) this.message.length}, + this.message); } else { - byte[] nonceformat = new byte[nonce.length]; - for (int i = 0; i < nonceformat.length; i++) { - nonceformat[i] = nonce[nonce.length - i - 1]; - } - org.spongycastle.math.ec.ECPoint point = ECKey.compressPoint(from.getPubKeyPoint()); - PublicKey senderPublicKey = new PublicKey(ECKey.fromPublicOnly(point)); + byte[] nonceBytes = Util.revertLong(nonce); - return Bytes.concat(new byte[]{1}, senderPublicKey.toBytes(), this.to.toBytes(), nonceformat, new byte[]{(byte) this.message.length}, this.message); + ECPoint senderPoint = ECKey.compressPoint(from.getPublicKey().getKey().getPubKeyPoint()); + PublicKey senderPublicKey = new PublicKey(ECKey.fromPublicOnly(senderPoint)); + + ECPoint recipientPoint = ECKey.compressPoint(to.getPublicKey().getKey().getPubKeyPoint()); + PublicKey recipientPublicKey = new PublicKey(ECKey.fromPublicOnly(recipientPoint)); + + return Bytes.concat(new byte[]{1}, + senderPublicKey.toBytes(), + recipientPublicKey.toBytes(), + nonceBytes, + new byte[]{(byte) this.message.length}, + this.message); } } - /** - * Encode a memo message using the input source key and destination key - * @param fromKey The source destination, need to have a private key accesss - * @param toKey The destination, needs only the public key access - * @param msg The message in bytes - * @return a Memo corresponding with the message encoding - */ - public static Memo encodeMessage(ECKey fromKey, PublicKey toKey, byte[] msg) { - return encodeMessage(fromKey, toKey, msg, 0); - } - - /** - * Encode a message a return a memo with that message encoded - * - * @param fromKey The source destination, need to have a private key accesss - * @param toKey The destination, needs only the public key access - * @param msg The message in bytes - * @param custom_nonce the custom nonce to be use or 0 to create a new one - * @return a Memo corresponding with the message encoding - */ - public static Memo encodeMessage(ECKey fromKey, PublicKey toKey, byte[] msg, long custom_nonce) { - Memo memo = new Memo(); - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - - memo.from = fromKey; - memo.to = toKey; - - if (custom_nonce == 0) { - SecureRandomStrengthener randomStrengthener = SecureRandomStrengthener.getInstance(); -// randomStrengthener.addEntropySource(new AndroidRandomSource()); - SecureRandom secureRandom = randomStrengthener.generateAndSeedRandomNumberGenerator(); - secureRandom.nextBytes(memo.nonce); - - long time = System.currentTimeMillis(); - - for (int i = 7; i >= 1; i--) { - memo.nonce[i] = (byte) (time & 0xff); - time = time / 0x100; - } - } else { - for (int i = 7; i >= 0; i--) { - memo.nonce[i] = (byte) (custom_nonce & 0xff); - custom_nonce = custom_nonce / 0x100; - } - } - byte[] secret = toKey.getKey().getPubKeyPoint().multiply(fromKey.getPrivKey()).normalize().getXCoord().getEncoded(); - byte[] finalKey = new byte[secret.length + memo.nonce.length]; - System.arraycopy(secret, 0, finalKey, 0, secret.length); - System.arraycopy(memo.nonce, 0, finalKey, secret.length, memo.nonce.length); - - byte[] sha256Msg = md.digest(msg); - byte[] serialChecksum = new byte[4]; - System.arraycopy(sha256Msg, 0, serialChecksum, 0, 4); - byte[] msgFinal = new byte[serialChecksum.length + msg.length]; - System.arraycopy(serialChecksum, 0, msgFinal, 0, serialChecksum.length); - System.arraycopy(msg, 0, msgFinal, serialChecksum.length, msg.length); - memo.message = Util.encryptAES(msgFinal, finalKey); - } catch (NoSuchAlgorithmException ex) { - } - return memo; - } - - /** - * returns the string coreesponding a encode memo - * - * @param fromKey The soruce key, need to have public key only - * @param toKey The destination key, need to have private key access - * @param msg The message to be decoded - * @param nonce The nonce used in the decoded message - * @return The message - */ - public static String decodeMessage(PublicKey fromKey, ECKey toKey, byte[] msg, byte[] nonce) { - - byte[] secret = fromKey.getKey().getPubKeyPoint().multiply(toKey.getPrivKey()).normalize().getXCoord().getEncoded(); - byte[] finalKey = new byte[secret.length + nonce.length]; - System.arraycopy(secret, 0, finalKey, 0, secret.length); - System.arraycopy(nonce, 0, finalKey, secret.length, nonce.length); - - byte[] msgFinal = Util.decryptAES(msg, finalKey); - byte[] decodedMsg = new byte[msgFinal.length - 4]; - //TODO verify checksum for integrity - System.arraycopy(msgFinal, 4, decodedMsg, 0, decodedMsg.length); - return new String(decodedMsg); - } - - /** - * returns the string corresponding a encode memo - * - * @param fromKey The source key, need to have public key only - * @param toKey The destination key, need to have private key access - * @param message The message to be decoded - * @param nonce The nonce used in the decoded message - * @return The message - */ - public static String decodeMessage(PublicKey fromKey, ECKey toKey, String message, String nonce) { - byte[] msg = new BigInteger(message, 16).toByteArray(); - if (msg[0] == 0) { - byte[] temp = new byte[msg.length - 1]; - System.arraycopy(msg, 1, temp, 0, temp.length); - msg = temp; - } - byte[] firstNonce = new BigInteger(nonce, 10).toByteArray(); - byte[] nonceByte = new byte[8]; - System.arraycopy(firstNonce, firstNonce.length - 8, nonceByte, 0, nonceByte.length); - return decodeMessage(fromKey, toKey, msg, nonceByte); - } - @Override public String toJsonString() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. @@ -174,17 +218,19 @@ public class Memo implements ByteSerializable, JsonSerializable { @Override public JsonElement toJsonObject() { - if ((this.from == null) || (this.to == null) || (this.nonce == null) || (this.message == null)) { - return null; - } - org.spongycastle.math.ec.ECPoint point = ECKey.compressPoint(from.getPubKeyPoint()); - PublicKey publicKey = new PublicKey(ECKey.fromPublicOnly(point)); - JsonObject memoObject = new JsonObject(); - memoObject.addProperty(KEY_FROM, publicKey.getAddress()); - memoObject.addProperty(KEY_TO, this.to.getAddress()); - memoObject.addProperty(KEY_NONCE, new BigInteger(1, this.nonce).toString(10)); - memoObject.addProperty(KEY_MESSAGE, new BigInteger(1, this.message).toString(16)); + if ((this.from == null) && (this.to == null)) { + // Public memo + memoObject.addProperty(KEY_FROM, ""); + memoObject.addProperty(KEY_TO, ""); + memoObject.addProperty(KEY_NONCE, ""); + memoObject.addProperty(KEY_MESSAGE, Util.bytesToHex(this.message)); + }else{ + memoObject.addProperty(KEY_FROM, this.from.toString()); + memoObject.addProperty(KEY_TO, this.to.toString()); + memoObject.addProperty(KEY_NONCE, String.format("%d", this.nonce)); + memoObject.addProperty(KEY_MESSAGE, Util.bytesToHex(this.message)); + } return memoObject; } -} +} \ No newline at end of file diff --git a/src/main/java/de/bitsharesmunich/graphenej/objects/MemoBuilder.java b/src/main/java/de/bitsharesmunich/graphenej/objects/MemoBuilder.java deleted file mode 100644 index 31d816c..0000000 --- a/src/main/java/de/bitsharesmunich/graphenej/objects/MemoBuilder.java +++ /dev/null @@ -1,74 +0,0 @@ -package de.bitsharesmunich.graphenej.objects; - -import de.bitsharesmunich.graphenej.PublicKey; -import org.bitcoinj.core.ECKey; - -/** - * Class to build a Memo Object - * @author henry 10/12/2016 - */ -public class MemoBuilder { - - private ECKey fromKey; - private PublicKey toKey; - private String message; - private long nonce = 0; - - /** - * Empty Constructor - */ - public MemoBuilder() { - } - - /** - * Set the key of the Source, needs to have a private Key access - * @param fromKey The Public Key of the sender - * @return The MemoBuilder - */ - public MemoBuilder setFromKey(ECKey fromKey) { - this.fromKey = fromKey; - return this; - } - - /** - * Set the key of the destination, only need the public key. - * @param toKey The Public Key of the receiver - * @return The MemoBuilder - */ - public MemoBuilder setToKey(PublicKey toKey) { - this.toKey = toKey; - return this; - } - - /** - * Set the message to be send - * @param message The message as a String - * @return The MemoBuilder - */ - public MemoBuilder setMessage(String message) { - this.message = message; - return this; - } - - /** - * (Optional) Sets a custom nonce - * @param nonce The custom nonce - * @return The MemoBuilder - */ - public MemoBuilder setNone(Long nonce) { - this.nonce = nonce; - return this; - } - - /** - * Biulds the memo object - * @return The Memo object - */ - public Memo build() { - //Todo unencode key - if (nonce == 0) { - return Memo.encodeMessage(fromKey, toKey, message.getBytes()); - } - return Memo.encodeMessage(fromKey, toKey, message.getBytes(), nonce); - } -} \ No newline at end of file diff --git a/src/test/java/de/bitsharesmunich/graphenej/objects/MemoTest.java b/src/test/java/de/bitsharesmunich/graphenej/objects/MemoTest.java new file mode 100644 index 0000000..33cd015 --- /dev/null +++ b/src/test/java/de/bitsharesmunich/graphenej/objects/MemoTest.java @@ -0,0 +1,105 @@ +package de.bitsharesmunich.graphenej.objects; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import de.bitsharesmunich.graphenej.Address; +import de.bitsharesmunich.graphenej.Util; +import de.bitsharesmunich.graphenej.errors.ChecksumException; +import org.bitcoinj.core.DumpedPrivateKey; +import org.bitcoinj.core.ECKey; +import org.junit.*; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +/** + * Created by nelson on 12/19/16. + */ +public class MemoTest { + + private ECKey sourcePrivate; + private Address sourceAddress; + + private ECKey destinationPrivate; + private Address destinationAddress; + + private long nonce; + private String shortMessage = "test"; + private String longerMessage = "testing now longer string!!"; + + private byte[] shortEncryptedMessage = Util.hexToBytes("4c81c2db6ebc61e3f9e0ead65c0559dd"); + private byte[] longerEncryptedMessage = Util.hexToBytes("1f8a08f1ff53dcefd48eeb052d26fba425f2a917f508ce61fc3d5696b10efa17"); + + private String decodedMessage; + + @Before + public void setUp() throws Exception { + sourcePrivate = DumpedPrivateKey.fromBase58(null, "5J96pne45qWM1WpektoeazN6k9Mt93jQ7LyueRxFfEMTiy6yxjM").getKey(); + destinationPrivate = DumpedPrivateKey.fromBase58(null, "5HuGQT8qwHScBgD4XsGbQUmXQF18MrbzxaQDiGGXFNRrCtqgT5Q").getKey(); + + sourceAddress = new Address("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY"); + destinationAddress = new Address("BTS8ADjGaswhfFoxMGxqCdBtzhTBJsrGadCLoc9Ey5AGc8eoVZ5bV"); + + nonce = 5; + } + + @Test + public void shouldMatchPredefinedChiphertext(){ + byte[] encrypted = Memo.encryptMessage(sourcePrivate, destinationAddress, 1, shortMessage); + assertArrayEquals("Testing with short message and nonce 1", encrypted, shortEncryptedMessage); + + byte[] encryptedLong = Memo.encryptMessage(sourcePrivate, destinationAddress, 1, longerMessage); + assertArrayEquals("Testing with longer message and nonce 1", encryptedLong, longerEncryptedMessage); + } + + @Test + public void shouldEncryptAndDecryptShortMessage(){ + try { + byte[] encrypted = Memo.encryptMessage(sourcePrivate, destinationAddress, nonce, shortMessage); + String decrypted = decrypted = Memo.decryptMessage(destinationPrivate, sourceAddress, nonce, encrypted); + assertEquals("Decrypted message must be equal to original", decrypted, shortMessage); + } catch (ChecksumException e) { + e.printStackTrace(); + } + } + + @Test + public void shouldEncryptAndDecryptLongerMessage(){ + try{ + byte[] longEncrypted = Memo.encryptMessage(sourcePrivate, destinationAddress, nonce, longerMessage); + String longDecrypted = Memo.decryptMessage(destinationPrivate, sourceAddress, nonce, longEncrypted); + assertEquals("The longer message must be equal to the original", longerMessage, longDecrypted); + } catch (ChecksumException e) { + e.printStackTrace(); + } + } + + @Test(expected = ChecksumException.class) + public void shouldThrowException() throws ChecksumException { + byte[] corrupted = Memo.encryptMessage(sourcePrivate, destinationAddress, nonce, longerMessage); + corrupted[0] = 0; + String longDecrypted = Memo.decryptMessage(destinationPrivate, sourceAddress, nonce, corrupted); + } + + @Test + public void shouldBeJsonObjectSerializable(){ + byte[] encrypted = Memo.encryptMessage(sourcePrivate, destinationAddress, 1, shortMessage); + Memo memo = new Memo(sourceAddress, destinationAddress, 1, encrypted); + JsonElement jsonObject = memo.toJsonObject(); + JsonObject reference = new JsonObject(); + reference.addProperty("from", "BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY"); + reference.addProperty("to", "BTS8ADjGaswhfFoxMGxqCdBtzhTBJsrGadCLoc9Ey5AGc8eoVZ5bV"); + reference.addProperty("nonce", "1"); + reference.addProperty("message", "4c81c2db6ebc61e3f9e0ead65c0559dd"); + assertEquals("Memo instance should generate a valid JsonObject",jsonObject, reference); + } + + @Test + public void shouldBeByteSerializable(){ + String byteReference = "0103d1fb8c7421db64d46fba7e36f428854ca06eff65698b293f37c7ffaa54e2c2b203aece7c31616c02fcc96b50d3397c0e8d33d6384655d477c300d9196c728a5ee20100000000000000104c81c2db6ebc61e3f9e0ead65c0559dd"; + byte[] encrypted = Memo.encryptMessage(sourcePrivate, destinationAddress, 1, shortMessage); + Memo memo = new Memo(sourceAddress, destinationAddress, 1, encrypted); + byte[] memoBytes = memo.toBytes(); + assertEquals("Memo instance should generate a valid byte array", byteReference, Util.bytesToHex(memoBytes)); + } +} \ No newline at end of file