Initial steps to add account_update operation support

master
Nelson R. Perez 2016-12-02 14:05:36 -05:00
parent e218ba09d6
commit 07dbbca371
10 changed files with 176 additions and 87 deletions

View File

@ -1,57 +1,66 @@
package com.luminiasoft.bitshares;
import com.google.common.primitives.Bytes;
import com.google.gson.internal.LinkedTreeMap;
import static com.luminiasoft.bitshares.Test.OPENLEDGER_WITNESS_URL;
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
import com.luminiasoft.bitshares.models.BaseResponse;
import com.luminiasoft.bitshares.models.WitnessResponse;
import com.luminiasoft.bitshares.test.NaiveSSLContext;
import com.luminiasoft.bitshares.ws.GetAccountNameById;
import com.luminiasoft.bitshares.ws.GetAccountsByAddress;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFactory;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import com.luminiasoft.bitshares.errors.MalformedAddressException;
import org.bitcoinj.core.Base58;
import org.bitcoinj.core.ECKey;
import org.spongycastle.crypto.digests.RIPEMD160Digest;
import org.spongycastle.math.ec.ECPoint;
import java.util.Arrays;
import java.util.List;
import javafx.util.Pair;
import javax.net.ssl.SSLContext;
import java.util.IllegalFormatException;
/**
* Class used to encapsulate address-related operations.
*/
public class Address {
public final static String DEFAULT_PREFIX = "BTS";
public final static String BITSHARES_PREFIX = "BTS";
private ECKey key;
private PublicKey publicKey;
private String prefix;
public Address(ECKey key) {
this.key = key;
this.prefix = DEFAULT_PREFIX;
this.publicKey = new PublicKey(key);
this.prefix = BITSHARES_PREFIX;
}
public Address(ECKey key, String prefix) {
this.key = key;
this.publicKey = new PublicKey(key);
this.prefix = prefix;
}
public Address(String address) throws MalformedAddressException {
this.prefix = address.substring(0, 3);
byte[] decoded = Base58.decode(address.substring(3, address.length()));
byte[] pubKey = Arrays.copyOfRange(decoded, 0, decoded.length - 4);
byte[] checksum = Arrays.copyOfRange(decoded, decoded.length - 4, decoded.length);
publicKey = new PublicKey(ECKey.fromPublicOnly(pubKey));
byte[] calculatedChecksum = calculateChecksum(pubKey);
for(int i = 0; i < calculatedChecksum.length; i++){
if(checksum[i] != calculatedChecksum[i]){
throw new MalformedAddressException("Checksum error");
}
}
}
public PublicKey getPublicKey(){
return this.publicKey;
}
@Override
public String toString() {
byte[] pubKey = key.getPubKey();
byte[] checksum = new byte[160 / 8];
RIPEMD160Digest ripemd160Digest = new RIPEMD160Digest();
ripemd160Digest.update(pubKey, 0, pubKey.length);
ripemd160Digest.doFinal(checksum, 0);
byte[] pubKeyChecksummed = Bytes.concat(pubKey, Arrays.copyOfRange(checksum, 0, 4));
byte[] pubKey = this.publicKey.toBytes();
byte[] checksum = calculateChecksum(pubKey);
byte[] pubKeyChecksummed = Bytes.concat(pubKey, checksum);
return this.prefix + Base58.encode(pubKeyChecksummed);
}
private byte[] calculateChecksum(byte[] data){
byte[] checksum = new byte[160 / 8];
RIPEMD160Digest ripemd160Digest = new RIPEMD160Digest();
ripemd160Digest.update(data, 0, data.length);
ripemd160Digest.doFinal(checksum, 0);
return Arrays.copyOfRange(checksum, 0, 4);
}
}

View File

@ -0,0 +1,35 @@
package com.luminiasoft.bitshares;
import com.google.gson.JsonElement;
import com.luminiasoft.bitshares.errors.MalformedAddressException;
import com.luminiasoft.bitshares.interfaces.JsonSerializable;
import java.util.HashMap;
/**
* Created by nelson on 11/30/16.
*/
public class Authority implements JsonSerializable {
private long weight_threshold;
private HashMap<Address, Long> address_auths;
private HashMap<UserAccount, Long> account_auths;
private HashMap<PublicKey, Long> key_auths;
public Authority(HashMap<String, Long> keyAuths) throws MalformedAddressException {
key_auths = new HashMap<PublicKey, Long>();
for(String key : keyAuths.keySet()){
Address address = new Address(key);
key_auths.put(address.getPublicKey(), keyAuths.get(key));
}
}
@Override
public String toJsonString() {
return null;
}
@Override
public JsonElement toJsonObject() {
return null;
}
}

View File

@ -38,7 +38,7 @@ public class Main {
// e.printStackTrace();
// }
// test.testCustomSerializer();
test.testUserAccountSerialization();
// test.testUserAccountSerialization();
// test.testTransactionSerialization();
// test.testLoginSerialization();
// test.testNetworkBroadcastSerialization();
@ -49,6 +49,7 @@ public class Main {
// test.testTransactionBroadcastSequence();
// test.testAccountLookupDeserialization();
// test.testPrivateKeyManipulations();
test.testPublicKeyManipulations();
// test.testGetAccountByName();
// test.testGetRequiredFees();
// test.testRandomNumberGeneration();

View File

@ -0,0 +1,20 @@
package com.luminiasoft.bitshares;
import com.luminiasoft.bitshares.interfaces.ByteSerializable;
import org.bitcoinj.core.ECKey;
/**
* Created by nelson on 11/30/16.
*/
public class PublicKey implements ByteSerializable {
private ECKey publicKey;
public PublicKey(ECKey key) {
this.publicKey = key;
}
@Override
public byte[] toBytes() {
return publicKey.getPubKey();
}
}

View File

@ -4,6 +4,7 @@ import com.google.common.primitives.UnsignedLong;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.luminiasoft.bitshares.errors.MalformedAddressException;
import com.luminiasoft.bitshares.errors.MalformedTransactionException;
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
import com.luminiasoft.bitshares.models.*;
@ -12,20 +13,14 @@ import com.luminiasoft.bitshares.ws.*;
import com.neovisionaries.ws.client.*;
import org.bitcoinj.core.*;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.digests.RIPEMD128Digest;
import org.spongycastle.crypto.digests.RIPEMD160Digest;
import org.spongycastle.crypto.digests.SHA512Digest;
import org.spongycastle.crypto.prng.DigestRandomGenerator;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.Level;
@ -40,10 +35,10 @@ public class Test {
public static final String OPENLEDGER_WITNESS_URL = "wss://bitshares.openledger.info/ws";
// public static final String WITNESS_URL = "wss://fr.blockpay.ch:8089";
private Transaction transaction;
private TransferOperation transferOperation;
public Transaction getTransaction() {
return transaction;
public TransferOperation getTransferOperation() {
return transferOperation;
}
private WitnessResponseListener mListener = new WitnessResponseListener() {
@ -114,27 +109,27 @@ public class Test {
};
public ECKey.ECDSASignature testSigning() {
byte[] serializedTransaction = this.transaction.toBytes();
byte[] serializedTransaction = this.transferOperation.toBytes();
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
byte[] bytesDigest = hash.getBytes();
ECKey sk = transaction.getPrivateKey();
ECKey sk = transferOperation.getPrivateKey();
ECKey.ECDSASignature signature = sk.sign(hash);
return signature;
}
public String testSigningMessage() {
byte[] serializedTransaction = this.transaction.toBytes();
byte[] serializedTransaction = this.transferOperation.toBytes();
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
ECKey sk = transaction.getPrivateKey();
ECKey sk = transferOperation.getPrivateKey();
return sk.signMessage(hash.toString());
}
public byte[] signMessage() {
byte[] serializedTransaction = this.transaction.toBytes();
byte[] serializedTransaction = this.transferOperation.toBytes();
Sha256Hash hash = Sha256Hash.wrap(Sha256Hash.hash(serializedTransaction));
System.out.println(">> digest <<");
System.out.println(Util.bytesToHex(hash.getBytes()));
ECKey sk = transaction.getPrivateKey();
ECKey sk = transferOperation.getPrivateKey();
System.out.println("Private key bytes");
System.out.println(Util.bytesToHex(sk.getPrivKeyBytes()));
boolean isCanonical = false;
@ -189,9 +184,9 @@ public class Test {
AssetAmount amount = new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120"));
AssetAmount fee = new AssetAmount(UnsignedLong.valueOf(264174), new Asset("1.3.0"));
operations.add(new Transfer(from, to, amount, fee));
this.transaction = new Transaction(Main.WIF, blockData, operations);
byte[] serializedTransaction = this.transaction.toBytes();
System.out.println("Serialized transaction");
this.transferOperation = new TransferOperation(Main.WIF, blockData, operations);
byte[] serializedTransaction = this.transferOperation.toBytes();
System.out.println("Serialized transferOperation");
System.out.println(Util.bytesToHex(serializedTransaction));
}
@ -340,7 +335,7 @@ public class Test {
public void testTransactionSerialization() {
try {
Transaction transaction = new TransferTransactionBuilder()
TransferOperation transferOperation = new TransferTransactionBuilder()
.setSource(new UserAccount("1.2.138632"))
.setDestination(new UserAccount("1.2.129848"))
.setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")))
@ -350,9 +345,9 @@ public class Test {
.build();
ArrayList<Serializable> transactionList = new ArrayList<>();
transactionList.add(transaction);
transactionList.add(transferOperation);
byte[] signature = transaction.getGrapheneSignature();
byte[] signature = transferOperation.getGrapheneSignature();
System.out.println(Util.bytesToHex(signature));
ApiCall call = new ApiCall(4, "call", "broadcast_transaction", transactionList, "2.0", 1);
String jsonCall = call.toJsonString();
@ -425,7 +420,7 @@ public class Test {
};
try {
Transaction transaction = new TransferTransactionBuilder()
TransferOperation transferOperation = new TransferTransactionBuilder()
.setSource(new UserAccount("1.2.138632"))
.setDestination(new UserAccount("1.2.129848"))
.setAmount(new AssetAmount(UnsignedLong.valueOf(100), new Asset("1.3.120")))
@ -435,9 +430,9 @@ public class Test {
.build();
ArrayList<Serializable> transactionList = new ArrayList<>();
transactionList.add(transaction);
transactionList.add(transferOperation);
transactionList.add(transaction);
transactionList.add(transferOperation);
SSLContext context = null;
context = NaiveSSLContext.getInstance("TLS");
@ -448,7 +443,7 @@ public class Test {
WebSocket mWebSocket = factory.createSocket(OPENLEDGER_WITNESS_URL);
mWebSocket.addListener(new TransactionBroadcastSequence(transaction, listener));
mWebSocket.addListener(new TransactionBroadcastSequence(transferOperation, listener));
mWebSocket.connect();
} catch (MalformedTransactionException e) {
@ -474,14 +469,29 @@ public class Test {
}
public void testPrivateKeyManipulations() {
ECKey privateKey = DumpedPrivateKey.fromBase58(null, Main.WIF).getKey();
String brainKeyWords = "UNMATE AURIGAL NAVET WAVICLE REWOVE ABBOTCY COWHERB OUTKICK STOPPER JUSSORY BEAMLET WIRY";
BrainKey brainKey = new BrainKey(brainKeyWords, 0);
ECKey privateKey = DumpedPrivateKey.fromBase58(null, brainKey.getWalletImportFormat()).getKey();
System.out.println("private key..............: " + Util.bytesToHex(privateKey.getSecretBytes()));
System.out.println("public key uncompressed..: " + Util.bytesToHex(privateKey.getPubKey()));
System.out.println("public key compressed....: " + Util.bytesToHex(privateKey.getPubKeyPoint().getEncoded(true)));
System.out.println("base58...................: " + Base58.encode(privateKey.getPubKeyPoint().getEncoded(true)));
System.out.println("base58...................: " + Base58.encode(privateKey.getPubKey()));
String brainKeyWords = "PUMPER ISOTOME SERE STAINER CLINGER MOONLIT CHAETA UPBRIM AEDILIC BERTHER NIT SHAP SAID SHADING JUNCOUS CHOUGH";
BrainKey brainKey = new BrainKey(brainKeyWords, 0);
}
public void testPublicKeyManipulations(){
// PublicKey publicKey = new PublicKey("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY");
// System.out.println("Public key bytes");
// System.out.println(Util.bytesToHex(publicKey.toBytes()));
Address address = null;
try {
address = new Address("BTS8RiFgs8HkcVPVobHLKEv6yL3iXcC9SWjbPVS15dDAXLG9GYhnY");
System.out.println("Public key");
System.out.println(Util.bytesToHex(address.getPublicKey().toBytes()));
} catch (MalformedAddressException e) {
e.printStackTrace();
}
}
public void testGetAccountByName() {

View File

@ -11,5 +11,5 @@ public abstract class TransactionBuilder {
protected ECKey privateKey;
protected BlockData blockData;
public abstract Transaction build() throws MalformedTransactionException;
public abstract TransferOperation build() throws MalformedTransactionException;
}

View File

@ -25,7 +25,7 @@ import java.util.TimeZone;
/**
* Class used to represent a generic graphene transaction.
*/
public class Transaction implements ByteSerializable, JsonSerializable {
public class TransferOperation extends BaseOperation implements ByteSerializable, JsonSerializable {
private final String TAG = this.getClass().getName();
public static final String KEY_EXPIRATION = "expiration";
@ -41,12 +41,13 @@ public class Transaction implements ByteSerializable, JsonSerializable {
private List<Extension> extensions;
/**
* Transaction constructor.
* TransferOperation constructor.
* @param wif: The user's private key in the base58 format.
* @param block_data: Block data containing important information used to sign a transaction.
* @param operation_list: List of operations to include in the transaction.
*/
public Transaction(String wif, BlockData block_data, List<BaseOperation> operation_list){
public TransferOperation(String wif, BlockData block_data, List<BaseOperation> operation_list){
super(OperationType.transfer_operation);
this.privateKey = DumpedPrivateKey.fromBase58(null, wif).getKey();
this.blockData = block_data;
this.operations = operation_list;
@ -54,12 +55,13 @@ public class Transaction implements ByteSerializable, JsonSerializable {
}
/**
* Transaction constructor.
* TransferOperation constructor.
* @param privateKey : Instance of a ECKey containing the private key that will be used to sign this transaction.
* @param blockData : Block data containing important information used to sign a transaction.
* @param operationList : List of operations to include in the transaction.
*/
public Transaction(ECKey privateKey, BlockData blockData, List<BaseOperation> operationList){
public TransferOperation(ECKey privateKey, BlockData blockData, List<BaseOperation> operationList){
super(OperationType.transfer_operation);
this.privateKey = privateKey;
this.blockData = blockData;
this.operations = operationList;
@ -117,6 +119,12 @@ public class Transaction implements ByteSerializable, JsonSerializable {
}
return sigData;
}
@Override
public byte getId() {
return 0;
}
/**
* Method that creates a serialized byte array with compact information about this transaction
* that is needed for the creation of a signature.
@ -157,7 +165,7 @@ public class Transaction implements ByteSerializable, JsonSerializable {
@Override
public String toJsonString() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Transaction.class, new TransactionSerializer());
gsonBuilder.registerTypeAdapter(TransferOperation.class, new TransactionSerializer());
return gsonBuilder.create().toJson(this);
}
@ -200,11 +208,11 @@ public class Transaction implements ByteSerializable, JsonSerializable {
}
class TransactionSerializer implements JsonSerializer<Transaction> {
class TransactionSerializer implements JsonSerializer<TransferOperation> {
@Override
public JsonElement serialize(Transaction transaction, Type type, JsonSerializationContext jsonSerializationContext) {
return transaction.toJsonObject();
public JsonElement serialize(TransferOperation transferOperation, Type type, JsonSerializationContext jsonSerializationContext) {
return transferOperation.toJsonObject();
}
}
}

View File

@ -54,7 +54,7 @@ public class TransferTransactionBuilder extends TransactionBuilder {
}
@Override
public Transaction build() throws MalformedTransactionException {
public TransferOperation build() throws MalformedTransactionException {
if(privateKey == null){
throw new MalformedTransactionException("Missing private key information");
}else if(blockData == null){
@ -81,6 +81,6 @@ public class TransferTransactionBuilder extends TransactionBuilder {
}
operations.add(transferOperation);
}
return new Transaction(privateKey, blockData, operations);
return new TransferOperation(privateKey, blockData, operations);
}
}

View File

@ -0,0 +1,10 @@
package com.luminiasoft.bitshares.errors;
/**
* Created by nelson on 12/1/16.
*/
public class MalformedAddressException extends Exception {
public MalformedAddressException(String message){
super(message);
}
}

View File

@ -2,12 +2,8 @@ package com.luminiasoft.bitshares.ws;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.luminiasoft.bitshares.BaseOperation;
import com.luminiasoft.bitshares.BlockData;
import com.luminiasoft.bitshares.RPC;
import com.luminiasoft.bitshares.Transaction;
import com.luminiasoft.bitshares.Transfer;
import com.luminiasoft.bitshares.TransferTransactionBuilder;
import com.luminiasoft.bitshares.*;
import com.luminiasoft.bitshares.TransferOperation;
import com.luminiasoft.bitshares.interfaces.WitnessResponseListener;
import com.luminiasoft.bitshares.models.ApiCall;
import com.luminiasoft.bitshares.models.BaseResponse;
@ -28,7 +24,7 @@ import java.util.Map;
import java.util.TimeZone;
/**
* Class that will handle the transaction publication procedure.
* Class that will handle the transferOperation publication procedure.
*/
public class TransactionBroadcastSequence extends WebSocketAdapter {
private final String TAG = this.getClass().getName();
@ -39,7 +35,7 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
private final static int BROADCAST_TRANSACTION = 4;
public final static int EXPIRATION_TIME = 30;
private Transaction transaction;
private TransferOperation transferOperation;
private long expirationTime;
private String headBlockId;
private long headBlockNumber;
@ -51,13 +47,13 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
/**
* Constructor of this class. The ids required
* @param transaction: The transaction to be broadcasted.
* @param transferOperation: The transferOperation to be broadcasted.
* @param listener: A class implementing the WitnessResponseListener interface. This should
* be implemented by the party interested in being notified about the success/failure
* of the transaction broadcast operation.
* of the transferOperation broadcast operation.
*/
public TransactionBroadcastSequence(Transaction transaction, WitnessResponseListener listener){
this.transaction = transaction;
public TransactionBroadcastSequence(TransferOperation transferOperation, WitnessResponseListener listener){
this.transferOperation = transferOperation;
this.mListener = listener;
}
@ -106,14 +102,14 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
headBlockNumber = dynamicProperties.head_block_number;
ArrayList<Serializable> transactionList = new ArrayList<>();
transactionList.add(transaction);
transactionList.add(transferOperation);
ApiCall call = new ApiCall(broadcastApiId,
RPC.CALL_BROADCAST_TRANSACTION,
transactionList,
"2.0",
currentId);
// Finally sending transaction
// Finally sending transferOperation
websocket.sendText(call.toJsonString());
}else if(baseResponse.id >= BROADCAST_TRANSACTION){
Type WitnessResponseType = new TypeToken<WitnessResponse<String>>(){}.getType();
@ -135,18 +131,18 @@ public class TransactionBroadcastSequence extends WebSocketAdapter {
* with ONE transfer operation.
*/
retries++;
List<BaseOperation> operations = this.transaction.getOperations();
List<BaseOperation> operations = this.transferOperation.getOperations();
Transfer transfer = (Transfer) operations.get(0);
transaction = new TransferTransactionBuilder()
transferOperation = new TransferTransactionBuilder()
.setSource(transfer.getFrom())
.setDestination(transfer.getTo())
.setAmount(transfer.getAmount())
.setFee(transfer.getFee())
.setBlockData(new BlockData(headBlockNumber, headBlockId, expirationTime + EXPIRATION_TIME))
.setPrivateKey(transaction.getPrivateKey())
.setPrivateKey(transferOperation.getPrivateKey())
.build();
ArrayList<Serializable> transactionList = new ArrayList<>();
transactionList.add(transaction);
transactionList.add(transferOperation);
ApiCall call = new ApiCall(broadcastApiId,
RPC.CALL_BROADCAST_TRANSACTION,
transactionList,