Fixed memo
This commit is contained in:
parent
783f48d42b
commit
4f64391e00
9 changed files with 364 additions and 225 deletions
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))];
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
105
src/test/java/de/bitsharesmunich/graphenej/objects/MemoTest.java
Normal file
105
src/test/java/de/bitsharesmunich/graphenej/objects/MemoTest.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue