[WIP ]tryfix getInputs, start to finish the god damn java lib

This commit is contained in:
AZ
2016-12-09 18:29:22 +01:00
parent 58874ca391
commit 84b60fbe69
9 changed files with 660 additions and 94 deletions
+373 -93
View File
@@ -2,8 +2,12 @@ package jota;
import jota.dto.request.*;
import jota.dto.response.*;
import jota.error.ArgumentException;
import jota.error.NotEnoughBalanceException;
import jota.model.*;
import jota.utils.IotaAPIUtils;
import okhttp3.OkHttpClient;
import org.apache.commons.lang3.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import retrofit2.Call;
@@ -14,21 +18,18 @@ import retrofit2.converter.gson.GsonConverterFactory;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* IotaAPIProxy Builder. Usage:
*
* <p>
* IotaApiProxy api = IotaApiProxy.Builder
* .protocol("http")
* .nodeAddress("localhost")
* .port(12345)
* .build();
*
* <p>
* GetNodeInfoResponse response = api.getNodeInfo();
*
* @author davassi
@@ -196,7 +197,7 @@ public class IotaAPIProxy {
public GetBundleResponse getBundle(String transaction) {
return IotaAPIUtils.getBundle(transaction);
}
/**
* Generates a new address from a seed and returns the remainderAddress.
* This is either done deterministically, or by providing the index of the new remainderAddress
@@ -208,117 +209,396 @@ public class IotaAPIProxy {
* @param returnAll If true, it returns all addresses which were deterministically generated (until findTransactions returns null)
* @return an array of strings with the specifed number of addresses
*/
public GetNewAddressResponse getNewAddress(final String seed, final int index, final boolean checksum, final int total, final boolean returnAll) {
final List<String> allAddresses = new ArrayList<>();
// Case 1: total
//
// If total number of addresses to generate is supplied, simply generate
// and return the list of all addresses
if (total != 0) {
// Increase index with each iteration
for (int i = index; i < index + total; i++) {
allAddresses.add(IotaAPIUtils.newAddress(seed, i, checksum));
}
return GetNewAddressResponse.create(allAddresses);
}
// Case 2: no total provided
//
// Continue calling findTransactions to see if address was already created
// if null, return list of addresses
// No total provided: Continue calling findTransactions to see if address was
// already created if null, return list of addresses
for (int i = index; ; i++) {
String newAddress = IotaAPIUtils.newAddress(seed, i, checksum);
final String newAddress = IotaAPIUtils.newAddress(seed, i, checksum);
final FindTransactionResponse response = findTransactionsByAddresses(new String[]{newAddress});
allAddresses.add(newAddress);
if (response.getHashes().length == 0) {
break;
}
}
// If returnAll, return list of allAddresses
// else return only the last address that was generated
// If !returnAll return only the last address that was generated
if (!returnAll) {
allAddresses.subList(0, allAddresses.size()-1).clear();
allAddresses.subList(0, allAddresses.size() - 1).clear();
}
return GetNewAddressResponse.create(allAddresses);
return GetNewAddressResponse.create(allAddresses);
}
public static class Builder {
public Transaction[] sendTrytes(String[] trytes, int depth, int minWeightMagnitude) {
GetTransactionsToApproveResponse transactionsToApproveResponse = getTransactionsToApprove(depth);
String protocol, host, port;
GetAttachToTangleResponse attachToTangleResponse =
attachToTangle(transactionsToApproveResponse.getTrunkTransaction(),
transactionsToApproveResponse.getBranchTransactionToApprove(), minWeightMagnitude, trytes);
public IotaAPIProxy build() {
if (protocol == null || host == null || port == null) {
// check properties files.
if (!checkPropertiesFiles()) {
// last resort: best effort on enviroment variable,
// before assigning default values.
checkEnviromentVariables();
}
}
return new IotaAPIProxy(this);
}
private boolean checkPropertiesFiles() {
try {
FileReader fileReader = new FileReader("node_config.properties");
BufferedReader bufferedReader = new BufferedReader(fileReader);
final Properties nodeConfig = new Properties();
nodeConfig.load(bufferedReader);
if (nodeConfig.getProperty("iota.node.protocol") != null) {
protocol = nodeConfig.getProperty("iota.node.protocol");
}
if (nodeConfig.getProperty("iota.node.host") != null) {
host = nodeConfig.getProperty("iota.node.host");
}
if (nodeConfig.getProperty("iota.node.port") != null) {
port = nodeConfig.getProperty("iota.node.port");
}
} catch (IOException e1) {
log.debug("node_config.properties not found. Rolling back for another solution...");
}
return (port != null && protocol != null && host != null);
}
private void checkEnviromentVariables() {
protocol = env("IOTA_NODE_PROTOCOL", "http");
host = env("IOTA_NODE_HOST", "localhost");
port = env("IOTA_NODE_PORT", "14265");
}
public Builder host(String host) {
this.host = host;
return this;
}
public Builder port(String port) {
this.port = port;
return this;
}
public Builder protocol(String protocol) {
this.protocol = protocol;
return this;
}
broadcastTransactions(attachToTangleResponse.getTrytes());
return analyzeTransactions(attachToTangleResponse.getTrytes());
}
}
private Transaction[] analyzeTransactions(String[] trytes) {
throw new NotImplementedException("MISSING");
}
/*
public Transaction[] SendTransfer(String seed, int depth, int minWeightMagnitude, Transfer[] transactions, int[] inputs, String address) {
// todo: check what to do with the optional arguments
String[] trytes = prepareTransfers(seed, transactions, inputs, address);
return sendTrytes(trytes, depth, minWeightMagnitude);
}*/
public Inputs GetInputs(String seed, Integer start, Integer end, int threshold) throws ArgumentException, NotEnoughBalanceException {
if (start < 0)
start = 0;
if (end < 0)
end = 0;
// If start value bigger than end, return error
if (start > end)
throw new ArgumentException();
// or if difference between end and start is bigger than 500 keys
if (end - start > 500)
throw new ArgumentException();
// Case 1: start and end
//
// If start and end is defined by the user, simply iterate through the keys
// and call getBalances
if (end != 0) {
String[] addresses = new String[end - start];
for (int i = start; i < end; i++) {
String address = IotaAPIUtils.newAddress(seed, i, false);
addresses[i] = address;
}
return getBalancesaAndFormat(addresses, start, end, threshold);
}
// Case 2: iterate till threshold || end
//
// Either start from index: 0 or start (if defined) until threshold is reached.
// Calls getNewAddress and deterministically generates and returns all addresses
// We then do getBalance, format the output and return it
else {
List<String> addressList = getNewAddress(seed, start, true, 0,true).getAddresses();
String[] addresses = addressList.toArray(new String[addressList.size()]);
return getBalancesaAndFormat(addresses, start, end, threshold);
}
}
private Inputs getBalancesaAndFormat(String[] addresses) throws NotEnoughBalanceException{
return getBalancesaAndFormat(addresses, null, null, null);
}
private Inputs getBalancesaAndFormat(String[] addresses, Integer start, Integer end, Integer threshold) throws NotEnoughBalanceException {
GetBalancesResponse getBalancesResponse = getBalances(threshold, addresses);
String[] balances = getBalancesResponse.getBalances();
Inputs inputs = new Inputs(new ArrayList<Input>(), 0);
boolean threshholdReached = false;
for (int i = 0; i < addresses.length; i++) {
if (Long.parseLong(balances[i]) > 0) {
inputs.getInputsList().add(new Input(addresses[i], Long.parseLong(balances[i]), start + i));
inputs.setTotalBalance(inputs.getTotalBalance() + inputs.getInputsList().get(i).getBalance());
if (inputs.getTotalBalance() >= threshold) {
threshholdReached = true;
break;
}
}
}
if (threshholdReached)
return inputs;
else {
throw new NotEnoughBalanceException();
}
}
//public String[] prepareTransfers(String seed, Transfer[] transfers, int[] inputs, String remainderAddress) {
//InputValidator.checkTransferArray(transfers);
/*
// If message or tag is not supplied, provide it
for (Transfer transfer : transfers) {
if (transfer.getAddress() == null)
transfer.getMessage().isEmpty();
if (transfer.getTag() == null)
transfer.getMessage().isEmpty();
}
// Create a new bundle
Bundle bundle = new Bundle();
long totalValue = 0;
List signatureFragments = new ArrayList();
String tag = "";
//
// Iterate over all transfers, get totalValue
// and prepare the signatureFragments, message and tag
//
for (Transfer transfer : transfers) {
int signatureMessageLength = 1;
// If message longer than 2187 trytes, increase signatureMessageLength (add 2nd transaction)
if (transfer.getMessage().length() > 2187) {
// Get total length, message / maxLength (2187 trytes)
signatureMessageLength += (int) Math.floor(((double) transfer.getMessage().length() / 2187));
String msgCopy = transfer.getMessage();
// While there is still a message, copy it
while (msgCopy != null) {
String fragment = msgCopy.substring(0, 2187);
msgCopy = msgCopy.substring(2187, msgCopy.length());
// Pad remainder of fragment
for (int j = 0; fragment.length() < 2187; j++) {
fragment += '9';
}
signatureFragments.add(fragment);
}
} else {
// Else, get single fragment with 2187 of 9's trytes
String fragment = "";
if (transfer.getMessage() != null) {
fragment = transfer.getMessage().substring(0, 2187);
}
for (int j = 0; fragment.length() < 2187; j++) {
fragment += '9';
}
signatureFragments.add(fragment);
}
// get current timestamp in seconds
// var timestamp = Math.floor(Date.now() / 1000);
long millis = System.currentTimeMillis() / 1000;
// If no tag defined, get 27 tryte tag.
tag = transfer.getTag() != null ? transfer.getTag() : "999999999999999999999999999";
// Pad for required 27 tryte length
for (int j = 0; tag.length() < 27; j++) {
tag += '9';
}
// Add first entries to the bundle
// Slice the address in case the user provided a checksummed one
bundle.addEntry(signatureMessageLength, transfer.getAddress().substring(0, 81), transfer.getValue(), tag, millis);
// Sum up total value
totalValue += transfer.getValue();
}
// Get inputs if we are sending tokens
if (totalValue != 0) {
// Case 1: user provided inputs
//
// Validate the inputs by calling getBalances
if (inputs != null) {
// Get list if addresses of the provided inputs
List<int> inputAddresses = new ArrayList();
for (int input : inputs) {
inputAddresses.add(input);
}
GetBalancesResponse balances = getBalances(100, inputAddresses);
List<Input> confirmedInputs = new ArrayList<Input>();
long totalBalance = 0;
for (int i = 0; i < balances.getBalances().length; i++) {
long thisBalance = Long.parseLong(balances.getBalances()[i]);
totalBalance += thisBalance;
// If input has balance, add it to confirmedInputs
if (thisBalance > 0) {
long inputEl = inputs[i];
inputEl = thisBalance;
confirmedInputs.add(inputEl);
}
}
// Return not enough balance error
if (totalValue > totalBalance) {
throw new NotEnoughBalanceException(totalBalance, totalValue);
}
addRemainder(seed, confirmedInputs, totalValue, bundle, tag, remainderAddress, signatureFragments);
}
// Case 2: Get inputs deterministically
//
// If no inputs provided, derive the addresses from the seed and
// confirm that the inputs exceed the threshold
else {
// todo getInputs should trow an exception if not enough balance
addRemainder(seed, GetInputs(seed, null, null, (int) totalValue).InputsList,
totalValue, bundle, tag, remainderAddress, signatureFragments);
}
} else {
// If no input required, don't sign and simply finalize the bundle
bundle.finalize();
bundle.addTrytes(signatureFragments);
List<String> bundleTrytes = null;
// todo not sure what to add here
bundle.getLength().forEach();
tx =>bundleTrytes.add(null);
bundleTrytes.Reverse();
return bundleTrytes.toArray();
}
// todo not sure what to return here too
return null;
}
private void addRemainder(String seed, List<Input> inputs, long totalValue, Bundle bundle, String tag,
String remainderAddress, List<String> signatureFragments) {
for (Input input : inputs) {
long thisBalance = input.getBalance();
long totalTransferValue = totalValue;
long toSubtract = 0 - thisBalance;
long timestamp = (new Date()).getTime();
// Add input as bundle entry
bundle.addEntry(2, input.getAddress(), toSubtract, tag, timestamp);
// If there is a remainder value
// Add extra output to send remaining funds to
if (thisBalance >= totalTransferValue) {
long remainder = thisBalance - totalTransferValue;
// If user has provided remainder address
// Use it to send remaining funds to
if (remainder > 0 && remainderAddress != null) {
// Remainder bundle entry
bundle.addEntry(1, remainderAddress, remainder, tag, timestamp);
// Final function for signing inputs
IotaAPIUtils.signInputsAndReturn(seed, inputs, bundle, signatureFragments);
} else if (remainder > 0) {
// Generate a new Address by calling getNewAddress
String address = getNewAddress(seed, 0, false, 0, false).getAddresses().get(0);
// Remainder bundle entry
bundle.addEntry(1, address, remainder, tag, timestamp);
// Final function for signing inputs
IotaAPIUtils.signInputsAndReturn(seed, inputs, bundle, signatureFragments);
} else {
// If there is no remainder, do not add transaction to bundle
// simply sign and return
IotaAPIUtils.signInputsAndReturn(seed, inputs, bundle, signatureFragments);
}
// If multiple inputs provided, subtract the totalTransferValue by
// the inputs balance
} else {
totalTransferValue -= thisBalance;
}
}
}
*/
public static class Builder {
String protocol, host, port;
public IotaAPIProxy build() {
if (protocol == null || host == null || port == null) {
// check properties files.
if (!checkPropertiesFiles()) {
// last resort: best effort on enviroment variable,
// before assigning default values.
checkEnviromentVariables();
}
}
return new IotaAPIProxy(this);
}
private boolean checkPropertiesFiles() {
try {
FileReader fileReader = new FileReader("node_config.properties");
BufferedReader bufferedReader = new BufferedReader(fileReader);
final Properties nodeConfig = new Properties();
nodeConfig.load(bufferedReader);
if (nodeConfig.getProperty("iota.node.protocol") != null) {
protocol = nodeConfig.getProperty("iota.node.protocol");
}
if (nodeConfig.getProperty("iota.node.host") != null) {
host = nodeConfig.getProperty("iota.node.host");
}
if (nodeConfig.getProperty("iota.node.port") != null) {
port = nodeConfig.getProperty("iota.node.port");
}
} catch (IOException e1) {
log.debug("node_config.properties not found. Rolling back for another solution...");
}
return (port != null && protocol != null && host != null);
}
private void checkEnviromentVariables() {
protocol = env("IOTA_NODE_PROTOCOL", "http");
host = env("IOTA_NODE_HOST", "localhost");
port = env("IOTA_NODE_PORT", "14265");
}
public Builder host(String host) {
this.host = host;
return this;
}
public Builder port(String port) {
this.port = port;
return this;
}
public Builder protocol(String protocol) {
this.protocol = protocol;
return this;
}
}
}
@@ -12,7 +12,7 @@ public class GetNewAddressResponse extends AbstractResponse {
return res;
}
public List<String> getAddress() {
public List<String> getAddresses() {
return addresses;
}
}
@@ -0,0 +1,14 @@
package jota.error;
import org.apache.commons.lang3.StringUtils;
import java.util.Collection;
/**
* Created by Adrian on 09.12.2016.
*/
public class ArgumentException extends BaseException {
public ArgumentException() {
super("wrong arguments passed to function");
}
}
@@ -0,0 +1,51 @@
package jota.error;
import org.apache.commons.lang3.StringUtils;
import java.util.Collection;
/**
* Created by Adrian on 09.12.2016.
*/
public class BaseException extends Exception {
protected Collection<String> messages;
public BaseException(String msg) {
super(msg);
}
public BaseException(String msg, Exception cause) {
super(msg, cause);
}
public BaseException(Collection<String> messages) {
super();
this.messages = messages;
}
public BaseException(Collection<String> messages, Exception cause) {
super(cause);
this.messages = messages;
}
@Override
public String getMessage() {
String msg;
if (this.messages != null && !this.messages.isEmpty()) {
msg = "[";
for (String message : this.messages) {
msg += message + ",";
}
msg = StringUtils.removeEnd(msg, ",") + "]";
} else msg = super.getMessage();
return msg;
}
}
@@ -0,0 +1,10 @@
package jota.error;
/**
* Created by Adrian on 09.12.2016.
*/
public class NotEnoughBalanceException extends BaseException {
public NotEnoughBalanceException() {
super("not enough balance dude");
}
}
+48
View File
@@ -0,0 +1,48 @@
package jota.model;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
/**
* Created by Adrian on 09.12.2016.
*/
public class Input {
private String address;
private long balance;
private int keyIndex;
public Input(String address, long balance, int keyIndex) {
this.address = address;
this.balance = balance;
this.keyIndex = keyIndex;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public long getBalance() {
return balance;
}
public void setBalance(long balance) {
this.balance = balance;
}
public int getKeyIndex() {
return keyIndex;
}
public void setKeyIndex(int keyIndex) {
this.keyIndex = keyIndex;
}
}
+39
View File
@@ -0,0 +1,39 @@
package jota.model;
import com.google.gson.Gson;
import java.util.List;
/**
* Created by Adrian on 09.12.2016.
*/
public class Inputs {
private List<Input> inputsList;
private long totalBalance;
public Inputs(List<Input> inputsList, long totalBalance) {
this.inputsList = inputsList;
this.totalBalance = totalBalance;
}
@Override
public String toString() {
return new Gson().toJson(this);
}
public List<Input> getInputsList() {
return inputsList;
}
public void setInputsList(List<Input> inputsList) {
inputsList = inputsList;
}
public long getTotalBalance() {
return totalBalance;
}
public void setTotalBalance(long totalBalance) {
totalBalance = totalBalance;
}
}
+53
View File
@@ -44,6 +44,59 @@ public class Transaction {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
public void setSignatureMessageChunk(String signatureMessageChunk) {
this.signatureMessageChunk = signatureMessageChunk;
}
public void setIndex(String index) {
this.index = index;
}
public void setApprovalNonce(String approvalNonce) {
this.approvalNonce = approvalNonce;
}
public void setHash(String hash) {
this.hash = hash;
}
public void setDigest(String digest) {
this.digest = digest;
}
public void setType(String type) {
this.type = type;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public void setTrunkTransaction(String trunkTransaction) {
this.trunkTransaction = trunkTransaction;
}
public void setBranchTransaction(String branchTransaction) {
this.branchTransaction = branchTransaction;
}
public void setSignatureNonce(String signatureNonce) {
this.signatureNonce = signatureNonce;
}
public void setAddress(String address) {
this.address = address;
}
public void setValue(String value) {
this.value = value;
}
public void setBundle(String bundle) {
this.bundle = bundle;
}
public String getValue() {
return value;
}
@@ -1,10 +1,14 @@
package jota.utils;
import jota.dto.response.GetBundleResponse;
import jota.model.Bundle;
import jota.model.Input;
import org.apache.commons.lang3.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* Client Side computation service
*
@@ -39,5 +43,72 @@ public class IotaAPIUtils {
public static GetBundleResponse getBundle(final String transaction) {
throw new NotImplementedException("Not yet implemented");
}
/*
public static List<String> signInputsAndReturn(String seed, List<Input> inputs, Bundle bundle,
List<String> signatureFragments) {
bundle.finalize();
bundle.addTrytes(signatureFragments);
// SIGNING OF INPUTS
//
// Here we do the actual signing of the inputs
// Iterate over all bundle transactions, find the inputs
// Get the corresponding private key and calculate the signatureFragment
for (int i = 0; i < bundle.getTransactions().size(); i++) {
if (Long.parseLong(bundle.getTransactions().get(i).getValue()) < 0) {
String thisAddress = bundle.getTransactions().get(i).getAddress();
// Get the corresponding keyIndex of the address
int keyIndex = 0;
for (int k = 0; k < inputs.size(); k++) {
if (inputs.get(k).getAddress().equals(thisAddress)) {
keyIndex = inputs.get(k).getKeyIndex();
break;
}
}
String bundleHash = bundle.getTransactions().get(i).getBundle();
// Get corresponding private key of address
int[] key = Signing.key(Converter.trits(seed), keyIndex, 2);
// First 6561 trits for the firstFragment
var firstFragment = key.Take(6561);
// Get the normalized bundle hash
String normalizedBundleHash = bundle.normalizedBundle(bundleHash);
/*
// First bundle fragment uses 27 trytes
var firstBundleFragment = normalizedBundleHash(27);
// Calculate the new signatureFragment with the first bundle fragment
var firstSignedFragment = Signing.signatureFragment(firstBundleFragment, firstFragment);
// Convert signature to trytes and assign the new signatureFragment
bundle.Transactions[i].signatureMessageFragment = Converter.trytes(firstSignedFragment);
// Because the signature is > 2187 trytes, we need to
// find the second transaction to add the remainder of the signature
for (var j = 0; j < bundle.Transactions.Count; j++)
{
// Same address as well as value = 0 (as we already spent the input)
if (bundle.Transactions[j].Address == thisAddress && bundle.Transactions[j].Value == 0)
{
// Use the second 6562 trits
var secondFragment = key.Skip(6561).Take(6561);
// The second 27 to 54 trytes of the bundle hash
var secondBundleFragment = normalizedBundleHash.slice(27, 27*2);
// Calculate the new signature
var secondSignedFragment = Signing.signatureFragment(secondBundleFragment, secondFragment);
// Convert signature to trytes and assign it again to this bundle entry
bundle.Transactions[j].signatureMessageFragment = Converter.trytes(secondSignedFragment);
}
}*/
// }
// }
}