Implemented broadcastAndStore and sendTrytes

This commit is contained in:
davassi 2016-12-18 12:33:26 +01:00
parent 548b311eed
commit 937a2b2e38
18 changed files with 514 additions and 68 deletions

View File

@ -2,6 +2,8 @@ package jota;
import jota.dto.request.*;
import jota.dto.response.*;
import jota.model.Transaction;
import jota.utils.Converter;
import jota.utils.IotaAPIUtils;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
@ -15,10 +17,12 @@ import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* IotaAPIProxy Builder. Usage:
@ -193,10 +197,6 @@ public class IotaAPIProxy {
// end of proxied calls.
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
@ -240,8 +240,72 @@ public class IotaAPIProxy {
return GetNewAddressResponse.create(allAddresses);
}
/*
* newAddress
* broadcastAndStore
* sendTrytes
*
getTransactionsObjects
findTransactionObjects
getLatestInclusion
getInputs
prepareTransfers
sendTransfer
replayBundle
broadcastBundle
getBundle
getTransfers
getAccountData
*/
/**
*
* @param trytes
* @return a StoreTransactionsResponse
*/
public StoreTransactionsResponse broadcastAndStore(final String ... trytes) {
try {
broadcastTransactions(trytes);
} catch (Exception e) {
log.error("Impossible to broadcastAndStore, aborting.", e);
throw new IllegalStateException("BroadcastAndStore Illegal state Exception");
}
return storeTransactions(trytes);
}
/**
* Facade method: Gets transactions to approve, attaches to Tangle, broadcasts and stores
* @param {array} trytes
* @param {int} depth
* @param {int} minWeightMagnitude
* @return
*/
public List<Transaction> sendTrytes(final String trytes, final int minWeightMagnitude) {
final GetTransactionsToApproveResponse txs = getTransactionsToApprove(minWeightMagnitude);
// attach to tangle - do pow
final GetAttachToTangleResponse res = attachToTangle(txs.getTrunkTransaction(), txs.getBranchTransactionToApprove(), minWeightMagnitude, trytes);
try {
broadcastAndStore(res.getTrytes());
} catch (Exception e) {
log.error("Impossible to sendTrytes, aborting.", e);
throw new IllegalStateException("sendTrytes Illegal state Exception");
}
return Arrays.stream(res.getTrytes())
.map(Converter::transactionObject)
.collect(Collectors.toList());
}
public GetBundleResponse getBundle(String transaction) {
return null; //IotaAPIUtils.getBundle(transaction);
}
public static class Builder {
String protocol, host, port;

View File

@ -4,7 +4,10 @@ package jota.error;
* Created by Adrian on 09.12.2016.
*/
public class ArgumentException extends BaseException {
private static final long serialVersionUID = -7850044681919575720L;
public ArgumentException() {
super("wrong arguments passed to function");
super("Wrong arguments passed to function");
}
}

View File

@ -1,31 +1,29 @@
package jota.error;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.Collection;
/**
* Created by Adrian on 09.12.2016.
*/
public class BaseException extends Exception {
private static final long serialVersionUID = 5617085097507773343L;
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;
@ -33,19 +31,6 @@ public class BaseException extends Exception {
@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;
return Arrays.toString(messages.toArray());
}
}

View File

@ -4,7 +4,10 @@ package jota.error;
* Created by Adrian on 09.12.2016.
*/
public class NotEnoughBalanceException extends BaseException {
private static final long serialVersionUID = -3807270816402226476L;
public NotEnoughBalanceException() {
super("not enough balance dude");
super("Not enough balance");
}
}

View File

@ -0,0 +1,162 @@
package jota.model;
import jota.pow.Curl;
import jota.utils.Constants;
import jota.utils.Converter;
import java.util.ArrayList;
import java.util.List;
/**
* Created by pinpong on 09.12.16.
*/
public class Bundle {
private List<Transaction> transactions;
private int length;
public static String EMPTY_HASH = "999999999999999999999999999999999999999999999999999999999999999999999999999999999";
public Bundle() {
this(new ArrayList<Transaction>(), 0);
}
public Bundle(List<Transaction> transactions, int length) {
this.transactions = transactions;
this.length = length;
}
public List<Transaction> getTransactions() {
return transactions;
}
public void setTransactions(List<Transaction> transactions) {
this.transactions = transactions;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public void addEntry(int signatureMessageLength, String slice, long value, String tag, long timestamp) {
for (int i = 0; i < signatureMessageLength; i++) {
//TODO
/* var transactionObject = new Object();
transactionObject.address = address;
transactionObject.value = i == 0 ? value : 0;
transactionObject.tag = tag;
transactionObject.timestamp = timestamp;
this.bundle[this.bundle.length] = transactionObject;
*/
}
}
public void finalize() {
Curl curl = new Curl();
curl.reset();
for (int i = 0; i < this.getTransactions().size(); i++) {
int[] valueTrits = Converter.trits(this.getTransactions().get(i).getValue());
while (valueTrits.length < 81) {
valueTrits[valueTrits.length] = 0;
}
int[] timestampTrits = Converter.trits(this.getTransactions().get(i).getTimestamp());
while (timestampTrits.length < 27) {
timestampTrits[timestampTrits.length] = 0;
}
int[] currentIndexTrits = Converter.trits(this.getTransactions().get(i).setCurrentIndex("" + i));
while (currentIndexTrits.length < 27) {
currentIndexTrits[currentIndexTrits.length] = 0;
}
int[] lastIndexTrits = Converter.trits(this.getTransactions().get(i).setLastIndex("" + (this.getTransactions().size() - 1)));
while (lastIndexTrits.length < 27) {
lastIndexTrits[lastIndexTrits.length] = 0;
}
int[] t = Converter.trits(this.getTransactions().get(i).getAddress() + Converter.trytes(valueTrits) + this.getTransactions().get(i).getTag() + Converter.trytes(timestampTrits) + Converter.trytes(currentIndexTrits) + Converter.trytes(lastIndexTrits));
curl.absorb(t, 0, t.length);
}
int[] hash = new int[90];
curl.squeeze(hash, 0, hash.length);
String hashInTrytes = Converter.trytes(hash);
for (int i = 0; i < this.getTransactions().size(); i++) {
this.getTransactions().get(i).setBundle(hashInTrytes);
}
}
public void addTrytes(List<String> signatureFragments) {
String emptySignatureFragment = "";
String emptyHash = EMPTY_HASH;
for (int j = 0; emptySignatureFragment.length() < 2187; j++) {
emptySignatureFragment += '9';
}
for (int i = 0; i < this.getTransactions().size(); i++) {
// Fill empty signatureMessageFragment
this.getTransactions().get(i).setSignatureFragments(signatureFragments.get(i) == null ? signatureFragments.get(i) : emptySignatureFragment);
// Fill empty trunkTransaction
this.getTransactions().get(i).setTrunkTransaction(emptyHash);
// Fill empty branchTransaction
this.getTransactions().get(i).setBranchTransaction(emptyHash);
// Fill empty nonce
this.getTransactions().get(i).setNonce(emptyHash);
}
}
public int[] normalizedBundle(String bundleHash) {
int[] normalizedBundle = new int[33 * 27 + 27];
for (int i = 0; i < 3; i++) {
long sum = 0;
for (int j = 0; j < 27; j++) {
sum += (normalizedBundle[i * 27 + j] = Converter.value(Converter.trits("" + bundleHash.charAt(i * 27 + j))));
}
if (sum >= 0) {
while (sum-- > 0) {
for (int j = 0; j < 27; j++) {
if (normalizedBundle[i * 27 + j] > -13) {
normalizedBundle[i * 27 + j]--;
break;
}
}
}
} else {
while (sum++ < 0) {
for (int j = 0; j < 27; j++) {
if (normalizedBundle[i * 27 + j] < 13) {
normalizedBundle[i * 27 + j]++;
break;
}
}
}
}
}
return normalizedBundle;
}
}

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

View File

@ -15,15 +15,37 @@ public class Curl {
private int[] state = new int[STATE_LENGTH];
public void absorb(final int[] trits, int offset, int length) {
public Curl absorb(final int[] trits, int offset, int length) {
do {
System.arraycopy(trits, offset, state, 0, length < HASH_LENGTH ? length : HASH_LENGTH);
transform();
offset += HASH_LENGTH;
} while ((length -= HASH_LENGTH) > 0);
return this;
}
public Curl transform() {
final int[] scratchpad = new int[STATE_LENGTH];
int scratchpadIndex = 0;
for (int round = 0; round < NUMBER_OF_ROUNDS; round++) {
System.arraycopy(state, 0, scratchpad, 0, STATE_LENGTH);
for (int stateIndex = 0; stateIndex < STATE_LENGTH; stateIndex++) {
state[stateIndex] = TRUTH_TABLE[scratchpad[scratchpadIndex] + scratchpad[scratchpadIndex += (scratchpadIndex < 365 ? 364 : -365)] * 3 + 4];
}
}
return this;
}
public Curl reset() {
for (int stateIndex = 0; stateIndex < STATE_LENGTH; stateIndex++) {
state[stateIndex] = 0;
}
return this;
}
public int[] squeeze(final int[] trits, int offset, int length) {
do {
@ -35,26 +57,10 @@ public class Curl {
return state;
}
public void transform() {
final int[] scratchpad = new int[STATE_LENGTH];
int scratchpadIndex = 0;
for (int round = 0; round < NUMBER_OF_ROUNDS; round++) {
System.arraycopy(state, 0, scratchpad, 0, STATE_LENGTH);
for (int stateIndex = 0; stateIndex < STATE_LENGTH; stateIndex++) {
state[stateIndex] = TRUTH_TABLE[scratchpad[scratchpadIndex] + scratchpad[scratchpadIndex += (scratchpadIndex < 365 ? 364 : -365)] * 3 + 4];
}
}
}
public void reset() {
for (int stateIndex = 0; stateIndex < STATE_LENGTH; stateIndex++) {
state[stateIndex] = 0;
}
}
public int[] getState() {
return state;
}
public void setState(int[] state) { this.state = state; }
public void setState(int[] state) {
this.state = state;
}
}

View File

@ -11,5 +11,4 @@ public class Constants {
public static int ADDRESS_LENGTH_WITHOUT_CHECKSUM = 81;
public static int ADDRESS_LENGTH_WITH_CHECKSUM = 90;
}

View File

@ -4,8 +4,15 @@ import jota.model.Transaction;
import jota.pow.Curl;
import java.util.Arrays;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Converter {
private static final Logger log = LoggerFactory.getLogger(Converter.class);
private static final int RADIX = 3;
private static final int MAX_TRIT_VALUE = (RADIX - 1) / 2, MIN_TRIT_VALUE = -MAX_TRIT_VALUE;
@ -146,20 +153,26 @@ public class Converter {
}
}
}
public static Transaction transactionObject(String trytes) {
if (trytes == null) return null;
public static Transaction transactionObject(final String trytes) {
if (StringUtils.isEmpty(trytes)) {
log.warn("Warning: empty trytes in input for transactionObject");
return null;
}
// validity check
for (int i = 2279; i < 2295; i++) {
if (trytes.charAt(i) != '9') {
log.warn("Trytes {} does not seem a valid tryte", trytes);
return null;
}
}
int[] transactionTrits = Converter.trits(trytes);
int[] hash = new int[90];
Curl curl = new Curl();
final Curl curl = new Curl(); // we need a fluent Curl.
// generate the correct transaction hash
curl.reset();
@ -183,4 +196,4 @@ public class Converter {
return trx;
}
}
}

View File

@ -1,10 +1,17 @@
package jota.utils;
import jota.dto.response.GetBundleResponse;
import org.apache.commons.lang3.NotImplementedException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jota.model.Bundle;
import jota.model.Input;
import jota.model.Transaction;
/**
* Client Side computation service
*
@ -36,8 +43,112 @@ public class IotaAPIUtils {
return address;
}
public static GetBundleResponse getBundle(final String transaction) {
throw new NotImplementedException("Not yet implemented");
public static String transactionTrytes(Transaction trx) {
int[] valueTrits = Converter.trits(trx.getValue());
while (valueTrits.length < 81) {
valueTrits[valueTrits.length] = 0;
}
int[] timestampTrits = Converter.trits(trx.getTimestamp());
while (timestampTrits.length < 27) {
timestampTrits[timestampTrits.length] = 0;
}
int[] currentIndexTrits = Converter.trits(trx.getTimestamp());
while (currentIndexTrits.length < 27) {
currentIndexTrits[currentIndexTrits.length] = 0;
}
int[] lastIndexTrits = Converter.trits(trx.getCurrentIndex());
while (lastIndexTrits.length < 27) {
lastIndexTrits[lastIndexTrits.length] = 0;
}
return trx.getSignatureFragments()
+ trx.getAddress()
+ Converter.trytes(valueTrits)
+ trx.getTag()
+ Converter.trytes(timestampTrits)
+ Converter.trytes(currentIndexTrits)
+ Converter.trytes(lastIndexTrits)
+ trx.getBundle()
+ trx.getTrunkTransaction()
+ trx.getBranchTransaction()
+ trx.getNonce();
}
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
int[] firstFragment = Arrays.copyOfRange(key, 0, 6561);
// Get the normalized bundle hash
int[] normalizedBundleHash = bundle.normalizedBundle(bundleHash);
// First bundle fragment uses 27 trytes
int[] firstBundleFragment = Arrays.copyOfRange(normalizedBundleHash, 0, 27);
// Calculate the new signatureFragment with the first bundle fragment
int[] firstSignedFragment = Signing.signatureFragment(firstBundleFragment, firstFragment);
// Convert signature to trytes and assign the new signatureFragment
bundle.getTransactions().get(i).setSignatureFragments(Converter.trytes(firstSignedFragment));
// Because the signature is > 2187 trytes, we need to
// find the second transaction to add the remainder of the signature
for (int j = 0; j < bundle.getTransactions().size(); j++) {
// Same address as well as value = 0 (as we already spent the input)
if (bundle.getTransactions().get(j).getAddress() == thisAddress && Long.parseLong(bundle.getTransactions().get(j).getValue()) == 0) {
// Use the second 6562 trits
int[] secondFragment = Arrays.copyOfRange(key, 6561, 6561 * 2);
// The second 27 to 54 trytes of the bundle hash
int[] secondBundleFragment = Arrays.copyOfRange(normalizedBundleHash, 27, 27 * 2);
// Calculate the new signature
int[] secondSignedFragment = Signing.signatureFragment(secondBundleFragment, secondFragment);
// Convert signature to trytes and assign it again to this bundle entry
bundle.getTransactions().get(j).setSignatureFragments(Converter.trytes(secondSignedFragment));
}
}
}
}
List<String> bundleTrytes = new ArrayList<>();
// Convert all bundle entries into trytes
for (Transaction tx : bundle.getTransactions()) {
bundleTrytes.add(IotaAPIUtils.transactionTrytes(tx));
}
Collections.reverse(bundleTrytes);
return bundleTrytes;
}
}

View File

@ -43,8 +43,9 @@ public class IotaUnitConverter {
public static IotaUnits findOptimalIotaUnitToDisplay(long amount) {
int length = String.valueOf(amount).length();
if (amount < 0) // do not count "-" sign
if (amount < 0) {// do not count "-" sign
length -= 1;
}
IotaUnits units = IotaUnits.IOTA;

View File

@ -8,6 +8,7 @@ package jota.utils;
* Table of IOTA units based off of the standard system of Units
**/
public enum IotaUnits {
IOTA("i", 0),
KILO_IOTA("Ki", 3),
MEGA_IOTA("Mi", 6),

View File

@ -1,6 +1,6 @@
package jota.utils;
import java.util.Random;
import java.security.SecureRandom;
/**
* Created by pinpong on 13.12.16.
@ -10,7 +10,7 @@ public class SeedRandomGenerator {
public static String generateNewSeed() {
char[] chars = Constants.TRYTE_ALPHABET.toCharArray();
StringBuilder builder = new StringBuilder();
Random random = new Random();
SecureRandom random = new SecureRandom();
for (int i = 0; i < Constants.SEED_LENGTH_MAX; i++) {
char c = chars[random.nextInt(chars.length)];
builder.append(c);

View File

@ -34,7 +34,6 @@ public class Signing {
while (length-- > 0) {
for (int i = 0; i < 27; i++) {
curl.squeeze(buffer, offset, buffer.length);
for (int j = 0; j < 243; j++) {
key.add(buffer[j]);
@ -81,6 +80,31 @@ public class Signing {
}
return digests;
}
public static int[] signatureFragment(int[] normalizedBundleFragment, int[] keyFragment) {
int[] signatureFragment = keyFragment;
int[] hash;
Curl curl = new Curl();
for (int i = 0; i < 27; i++) {
hash = Arrays.copyOfRange(signatureFragment, i * 243, (i + 1) * 243);
for (int j = 0; j < 13 - normalizedBundleFragment[i]; j++) {
curl.reset()
.absorb(hash, 0, hash.length)
.squeeze(hash, 0, hash.length);
}
for (int j = 0; j < 243; j++) {
signatureFragment[i * 243 + j] = hash[j];
}
}
return signatureFragment;
}
public static int[] address(int[] digests) {
final Curl curl = new Curl();

View File

@ -4,7 +4,7 @@ package jota.utils;
* Created by pinpong on 01.12.16.
*/
public class TrytesConverter {
/**
* Conversion of ascii encoded bytes to trytes.
* Input is a string (can be stringified JSON object), return value is Trytes
@ -19,7 +19,9 @@ public class TrytesConverter {
* b. The second value is the remainder (decimal value - first value), divided by 27
* 3. The two values returned from Step 2. are then input as indices into the available values list ('9ABCDEFGHIJKLMNOPQRSTUVWXYZ') to get the correct tryte value
* <p>
* EXAMPLES
*
* EXAMPLE
*
* Lets say we want to convert the ASCII character "Z".
* 1. 'Z' has a decimal value of 90.
* 2. 90 can be represented as 9 + 3 * 27. To make it simpler:
@ -30,10 +32,11 @@ public class TrytesConverter {
* b. The second tryte value is '9ABCDEFGHIJKLMNOPQRSTUVWXYZ'[3] === "C"
* Our tryte pair is "IC"
* <p>
* RESULT:
* The ASCII char "Z" is represented as "IC" in trytes.
*
* @param inputString
* @return
* The ASCII char "Z" is represented as "IC" in trytes.
*/
public static String toTrytes(String inputString) {
StringBuilder trytes = new StringBuilder();
@ -67,7 +70,6 @@ public class TrytesConverter {
* Last character = }
* Everything after that is 9's padding
*/
public static String toString(String inputTrytes) {
StringBuilder string = new StringBuilder();
@ -81,7 +83,6 @@ public class TrytesConverter {
int decimalValue = firstValue + secondValue * 27;
String character = Character.toString((char) decimalValue);
string.append(character);
}

View File

@ -0,0 +1,12 @@
package jota;
import org.junit.Test;
public class SendMessageTest {
@Test
public void sendMessage() {
}
}

View File

@ -3,6 +3,9 @@ package jota;
import jota.utils.TrytesConverter;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.apache.commons.lang3.RandomStringUtils;
/**
* Created by pinpong on 01.12.16.
@ -17,5 +20,14 @@ public class TrytesConverterTest {
public void shouldConvertTrytesToString() {
assertEquals(TrytesConverter.toString("IC"), "Z");
}
@Test
public void shouldConvertBackAndForth() {
String str = RandomStringUtils.randomAlphabetic(1000).toUpperCase();
System.err.println(str);
String back = TrytesConverter.toString(TrytesConverter.toTrytes(str));
assertTrue(str.equals(back));
}
}

1
teststore.txt Normal file
View File

@ -0,0 +1 @@
curl http://localhost:14265 -X POST -H Content-Type: application/json -d {'command': 'storeTransactions', 'trytes': ['GYPRVHBEZOOFXSHQBLCYW9ICTCISLHDBNMMVYD9JJHQMPQCTIQAQTJNNNJ9IDXLRCCOYOXYPCLR9PBEY9ORZIEPPDNTI9CQWYZUOTAVBXPSBOFEQAPFLWXSWUIUSJMSJIIIZWIKIRH9GCOEVZFKNXEVCUCIIWZQCQEUVRZOCMEL9AMGXJNMLJCIA9UWGRPPHCEOPTSVPKPPPCMQXYBHMSODTWUOABPKWFFFQJHCBVYXLHEWPD9YUDFTGNCYAKQKVEZYRBQRBXIAUX9SVEDUKGMTWQIYXRGSWYRK9SRONVGTW9YGHSZRIXWGPCCUCDRMAXBPDFVHSRYWHGB9DQSQFQKSNICGPIPTRZINYRXQAFSWSEWIFRMSBMGTNYPRWFSOIIWWT9IDSELM9JUOOWFNCCSHUSMGNROBFJX9JQ9XT9PKEGQYQAWAFPRVRRVQPUQBHLSNTEFCDKBWRCDX9EYOBB9KPMTLNNQLADBDLZPRVBCKVCYQEOLARJYAGTBFR9QLPKZBOYWZQOVKCVYRGYI9ZEFIQRKYXLJBZJDBJDJVQZCGYQMROVHNDBLGNLQODPUXFNTADDVYNZJUVPGB9LVPJIYLAPBOEHPMRWUIAJXVQOEM9ROEYUOTNLXVVQEYRQWDTQGDLEYFIYNDPRAIXOZEBCS9P99AZTQQLKEILEVXMSHBIDHLXKUOMMNFKPYHONKEYDCHMUNTTNRYVMMEYHPGASPZXASKRUPWQSHDMU9VPS99ZZ9SJJYFUJFFMFORBYDILBXCAVJDPDFHTTTIYOVGLRDYRTKHXJORJVYRPTDH9ZCPZ9ZADXZFRSFPIQKWLBRNTWJHXTOAUOL9FVGTUMMPYGYICJDXMOESEVDJWLMCVTJLPIEKBE9JTHDQWV9MRMEWFLPWGJFLUXI9BXPSVWCMUWLZSEWHBDZKXOLYNOZAPOYLQVZAQMOHGTTQEUAOVKVRRGAHNGPUEKHFVPVCOYSJAWHZU9DRROHBETBAFTATVAUGOEGCAYUXACLSSHHVYDHMDGJP9AUCLWLNTFEVGQGHQXSKEMVOVSKQEEWHWZUDTYOBGCURRZSJZLFVQQAAYQO9TRLFFN9HTDQXBSPPJYXMNGLLBHOMNVXNOWEIDMJVCLLDFHBDONQJCJVLBLCSMDOUQCKKCQJMGTSTHBXPXAMLMSXRIPUBMBAWBFNLHLUJTRJLDERLZFUBUSMF999XNHLEEXEENQJNOFFPNPQ9PQICHSATPLZVMVIWLRTKYPIXNFGYWOJSQDAXGFHKZPFLPXQEHCYEAGTIWIJEZTAVLNUMAFWGGLXMBNUQTOFCNLJTCDMWVVZGVBSEBCPFSM99FLOIDTCLUGPSEDLOKZUAEVBLWNMODGZBWOVQT9DPFOTSKRABQAVOQ9RXWBMAKFYNDCZOJGTCIDMQSQQSODKDXTPFLNOKSIZEOY9HFUTLQRXQMEPGOXQGLLPNSXAUCYPGZMNWMQWSWCKAQYKXJTWINSGPPZG9HLDLEAWUWEVCTVRCBDFOXKUROXH9HXXAXVPEJFRSLOGRVGYZASTEBAQNXJJROCYRTDPYFUIQJVDHAKEG9YACV9HCPJUEUKOYFNWDXCCJBIFQKYOXGRDHVTHEQUMHO999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999RKWEEVD99A99999999A99999999NFDPEEZCWVYLKZGSLCQNOFUSENIXRHWWTZFBXMPSQHEDFWZULBZFEOMNLRNIDQKDNNIELAOXOVMYEI9PGTKORV9IKTJZQUBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSKUCUEMD9M9SQJ999999TKORV9IKTJZQUBQAWTKBKZ9NEZHBFIMCLV9TTNJNQZUIJDFPTTCTKBJRHAITVSKUCUEMD9M9SQJ999999999999999999999999999999999999999999999999999999999999999999999999999999999999999']}