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;
|
||||
|
||||
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<String> suggestedBrainKey = new ArrayList<String>();
|
||||
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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))];
|
||||
|
|
|
@ -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.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;
|
||||
}
|
||||
}
|
|
@ -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