Fixed memo

This commit is contained in:
Nelson R. Perez 2016-12-20 18:35:17 -05:00
parent 783f48d42b
commit 4f64391e00
9 changed files with 364 additions and 225 deletions

View file

@ -1,6 +1,7 @@
package de.bitsharesmunich.graphenej; package de.bitsharesmunich.graphenej;
import de.bitsharesmunich.graphenej.crypto.AndroidRandomSource; import de.bitsharesmunich.graphenej.crypto.AndroidRandomSource;
import de.bitsharesmunich.graphenej.crypto.Random;
import de.bitsharesmunich.graphenej.crypto.SecureRandomStrengthener; import de.bitsharesmunich.graphenej.crypto.SecureRandomStrengthener;
import org.bitcoinj.core.DumpedPrivateKey; import org.bitcoinj.core.DumpedPrivateKey;
import org.bitcoinj.core.ECKey; import org.bitcoinj.core.ECKey;
@ -43,9 +44,7 @@ public class BrainKey {
String[] wordArray = words.split(","); String[] wordArray = words.split(",");
ArrayList<String> suggestedBrainKey = new ArrayList<String>(); ArrayList<String> suggestedBrainKey = new ArrayList<String>();
assert (wordArray.length == DICT_WORD_COUNT); assert (wordArray.length == DICT_WORD_COUNT);
SecureRandomStrengthener randomStrengthener = SecureRandomStrengthener.getInstance(); SecureRandom secureRandom = Random.getSecureRandom();
randomStrengthener.addEntropySource(new AndroidRandomSource());
SecureRandom secureRandom = randomStrengthener.generateAndSeedRandomNumberGenerator();
int index; int index;
for (int i = 0; i < BRAINKEY_WORD_COUNT; i++) { for (int i = 0; i < BRAINKEY_WORD_COUNT; i++) {
index = secureRandom.nextInt(DICT_WORD_COUNT - 1); index = secureRandom.nextInt(DICT_WORD_COUNT - 1);

View file

@ -42,7 +42,7 @@ public class Main {
// test.testGetDynamicParams(); // test.testGetDynamicParams();
// test.testGetRequiredFeesSerialization(); // test.testGetRequiredFeesSerialization();
// test.testRequiredFeesResponse(); // test.testRequiredFeesResponse();
// test.testTransactionBroadcastSequence(); test.testTransactionBroadcastSequence();
// test.testAccountLookupDeserialization(); // test.testAccountLookupDeserialization();
// test.testPrivateKeyManipulations(); // test.testPrivateKeyManipulations();
// test.testPublicKeyManipulations(); // test.testPublicKeyManipulations();
@ -67,6 +67,7 @@ public class Main {
// test.testLookupAssetSymbols(); // test.testLookupAssetSymbols();
// test.testGetBlockHeader(); // test.testGetBlockHeader();
//test.testGetLimitOrders(); //test.testGetLimitOrders();
test.testGetTradeHistory(); // test.testGetTradeHistory();
// test.testAssetSerialization();
} }
} }

View file

@ -6,12 +6,10 @@ import com.google.common.primitives.UnsignedLong;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import de.bitsharesmunich.graphenej.errors.MalformedAddressException; import de.bitsharesmunich.graphenej.errors.MalformedAddressException;
import de.bitsharesmunich.graphenej.errors.MalformedTransactionException; import de.bitsharesmunich.graphenej.errors.MalformedTransactionException;
import de.bitsharesmunich.graphenej.interfaces.WitnessResponseListener; import de.bitsharesmunich.graphenej.interfaces.WitnessResponseListener;
import de.bitsharesmunich.graphenej.objects.MemoBuilder;
import de.bitsharesmunich.graphenej.test.NaiveSSLContext; import de.bitsharesmunich.graphenej.test.NaiveSSLContext;
import com.neovisionaries.ws.client.*; import com.neovisionaries.ws.client.*;
import de.bitsharesmunich.graphenej.api.*; import de.bitsharesmunich.graphenej.api.*;
@ -300,15 +298,18 @@ public class Test {
}; };
try { try {
// Creating memo
ECKey from = new BrainKey(Main.BILTHON_83_BRAIN_KEY, 0).getPrivateKey(); 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())); 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 // Creating transaction
Transaction transaction = new TransferTransactionBuilder() Transaction transaction = new TransferTransactionBuilder()
.setSource(new UserAccount("1.2.139270")) // bilthon-83 .setSource(new UserAccount("1.2.138632")) // bilthon-83
.setDestination(new UserAccount("1.2.142115")) // bilthon-5 .setDestination(new UserAccount("1.2.139313")) // bilthon-5
.setAmount(new AssetAmount(UnsignedLong.valueOf(1), new Asset("1.3.0"))) .setAmount(new AssetAmount(UnsignedLong.valueOf(1), new Asset("1.3.0")))
.setFee(new AssetAmount(UnsignedLong.valueOf(264174), 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()) .setPrivateKey(new BrainKey(Main.BILTHON_83_BRAIN_KEY, 0).getPrivateKey())
@ -458,9 +459,9 @@ public class Test {
String suggestion = BrainKey.suggest(words); String suggestion = BrainKey.suggest(words);
brainKey = new BrainKey(suggestion, 0); brainKey = new BrainKey(suggestion, 0);
} else { } else {
System.out.println("Using brain key: " + Main.BILTHON_83_BRAIN_KEY); System.out.println("Using brain key: " + Main.BILTHON_5_BRAIN_KEY);
// brainKey = new BrainKey(Main.BILTHON_83_BRAIN_KEY, 0); brainKey = new BrainKey(Main.BILTHON_5_BRAIN_KEY, 0);
brainKey = new BrainKey("AMIDIC SKELIC CARLOAD CAPSULE FACY WONNED STICH BULBULE MESOLE SEMEED STRAVE PREBORN", 0);
} }
ECKey key = brainKey.getPrivateKey(); ECKey key = brainKey.getPrivateKey();
System.out.println("Private key..................: " + Util.bytesToHex(key.getSecretBytes())); 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(); 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())); 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(); // JsonElement memoJson = sendMemo.toJsonObject();
System.out.println("generated Json : " + memoJson.toString()); // 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())); // System.out.println("Decode Memo : " + Memo.decryptMessage(from, to, memoJson.getAsJsonObject().get("message").getAsString(), memoJson.getAsJsonObject().get("nonce").getAsString()));
} }
public void testGetRelativeAccountHistory(){ public void testGetRelativeAccountHistory(){
@ -974,4 +975,16 @@ public class Test {
System.out.println("IOException. Msg: " + e.getMessage()); 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));
}
} }

View file

@ -33,6 +33,11 @@ public class Util {
public static final int LZMA = 0; public static final int LZMA = 0;
public static final int XZ = 1; 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) { public static byte[] hexToBytes(String s) {
int len = s.length(); int len = s.length();
byte[] data = new byte[len / 2]; byte[] data = new byte[len / 2];
@ -43,6 +48,11 @@ public class Util {
return data; 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) { public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2]; char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) { for ( int j = 0; j < bytes.length; j++ ) {
@ -53,6 +63,19 @@ public class Util {
return new String(hexChars); 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. * Utility function that compresses data using the LZMA algorithm.
* @param inputBytes Input bytes of the data to be compressed. * @param inputBytes Input bytes of the data to be compressed.
@ -168,6 +191,7 @@ public class Util {
System.arraycopy(result, 32, ivBytes, 0, 16); System.arraycopy(result, 32, ivBytes, 0, 16);
byte[] sksBytes = new byte[32]; byte[] sksBytes = new byte[32];
System.arraycopy(result, 0, sksBytes, 0, 32); System.arraycopy(result, 0, sksBytes, 0, 32);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine())); PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
cipher.init(true, new ParametersWithIV(new KeyParameter(sksBytes), ivBytes)); cipher.init(true, new ParametersWithIV(new KeyParameter(sksBytes), ivBytes));
byte[] temp = new byte[input.length + (16 - (input.length % 16))]; byte[] temp = new byte[input.length + (16 - (input.length % 16))];

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -3,17 +3,18 @@ package de.bitsharesmunich.graphenej.objects;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import de.bitsharesmunich.graphenej.Address;
import de.bitsharesmunich.graphenej.PublicKey; import de.bitsharesmunich.graphenej.PublicKey;
import de.bitsharesmunich.graphenej.Util; 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.ByteSerializable;
import de.bitsharesmunich.graphenej.interfaces.JsonSerializable; import de.bitsharesmunich.graphenej.interfaces.JsonSerializable;
import org.bitcoinj.core.ECKey; import org.bitcoinj.core.ECKey;
import org.spongycastle.math.ec.ECPoint;
import java.math.BigInteger;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.util.Arrays;
/** /**
* Created by nelson on 11/9/16. * 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_NONCE = "nonce";
public static final String KEY_MESSAGE = "message"; public static final String KEY_MESSAGE = "message";
private ECKey from; private Address from;
private PublicKey to; private Address to;
private final byte[] nonce = new byte[8]; private long nonce;
private byte[] message; private byte[] message;
/** /**
@ -39,132 +40,175 @@ public class Memo implements ByteSerializable, JsonSerializable {
this.message = null; 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 * Implement metod, serialized this Object
* @return the byte array of this object serialized * @return the byte array of this object serialized
*/ */
@Override @Override
public byte[] toBytes() { 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}; 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 { } else {
byte[] nonceformat = new byte[nonce.length]; byte[] nonceBytes = Util.revertLong(nonce);
for (int i = 0; i < nonceformat.length; i++) {
nonceformat[i] = nonce[nonce.length - i - 1]; 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);
} }
org.spongycastle.math.ec.ECPoint point = ECKey.compressPoint(from.getPubKeyPoint());
PublicKey senderPublicKey = new PublicKey(ECKey.fromPublicOnly(point));
return Bytes.concat(new byte[]{1}, senderPublicKey.toBytes(), this.to.toBytes(), nonceformat, 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 @Override
@ -174,17 +218,19 @@ public class Memo implements ByteSerializable, JsonSerializable {
@Override @Override
public JsonElement toJsonObject() { 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(); JsonObject memoObject = new JsonObject();
memoObject.addProperty(KEY_FROM, publicKey.getAddress()); if ((this.from == null) && (this.to == null)) {
memoObject.addProperty(KEY_TO, this.to.getAddress()); // Public memo
memoObject.addProperty(KEY_NONCE, new BigInteger(1, this.nonce).toString(10)); memoObject.addProperty(KEY_FROM, "");
memoObject.addProperty(KEY_MESSAGE, new BigInteger(1, this.message).toString(16)); 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; return memoObject;
} }
} }

View file

@ -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);
}
}

View file

@ -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));
}
}