Updated on 2026-08-14

This commit is contained in:
Tangem 2019-07-19 13:58:16 +03:00
parent 1a54ecc244
commit 1e1e2932cf
76 changed files with 4011 additions and 102 deletions

View file

@ -1,525 +0,0 @@
package com.tangem.wallet.EOS;
import android.net.Uri;
import android.text.InputFilter;
import android.util.Log;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.tangem.Constant;
import com.tangem.card_common.data.TangemCard;
import com.tangem.card_common.reader.CardProtocol;
import com.tangem.card_common.reader.TLV;
import com.tangem.card_common.tasks.SignTask;
import com.tangem.card_common.util.Util;
import com.tangem.data.Blockchain;
import com.tangem.data.network.ServerApiEos;
import com.tangem.util.CryptoUtil;
import com.tangem.util.DecimalDigitsInputFilter;
import com.tangem.wallet.BTCUtils;
import com.tangem.wallet.BalanceValidator;
import com.tangem.wallet.BuildConfig;
import com.tangem.wallet.CoinData;
import com.tangem.wallet.CoinEngine;
import com.tangem.wallet.R;
import com.tangem.wallet.TangemContext;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import org.joda.time.DateTime;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import io.jafka.jeos.EosApi;
import io.jafka.jeos.EosApiFactory;
import io.jafka.jeos.LocalApi;
import io.jafka.jeos.convert.Packer;
import io.jafka.jeos.core.common.SignArg;
import io.jafka.jeos.core.common.transaction.TransactionAction;
import io.jafka.jeos.core.common.transaction.TransactionAuthorization;
import io.jafka.jeos.core.request.chain.json2bin.TransferArg;
import io.jafka.jeos.core.request.chain.transaction.PushTransactionRequest;
import io.jafka.jeos.core.response.chain.account.Account;
import io.jafka.jeos.core.response.chain.transaction.PushedTransaction;
import io.jafka.jeos.util.Base58;
import io.jafka.jeos.util.KeyUtil;
import io.jafka.jeos.util.Raw;
import io.jafka.jeos.util.ecc.Ripemd160;
import io.reactivex.Observer;
import io.reactivex.observers.DefaultObserver;
public class EosEngine extends CoinEngine {
private static final String TAG = EosEngine.class.getSimpleName();
public EosData coinData = null;
public EosEngine(TangemContext ctx) throws Exception {
super(ctx);
if (ctx.getCoinData() == null) {
coinData = new EosData();
ctx.setCoinData(coinData);
} else if (ctx.getCoinData() instanceof EosData) {
coinData = (EosData) ctx.getCoinData();
} else {
throw new Exception("Invalid type of Blockchain data for " + this.getClass().getSimpleName());
}
}
public EosEngine() {
super();
}
private static int getDecimals() {
return 4;
}
@Override
public boolean awaitingConfirmation() {
return false;
}
@Override
public Amount getBalance() {
if (!hasBalanceInfo()) {
return null;
}
return coinData.getBalance();
}
@Override
public String getBalanceHTML() {
Amount balance = getBalance();
if (balance != null) {
return balance.toDescriptionString(getDecimals());
} else {
return "";
}
}
@Override
public String getBalanceCurrency() {
return Blockchain.Eos.getCurrency();
}
@Override
public String getOfflineBalanceHTML() {
InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance());
Amount offlineAmount = convertToAmount(offlineInternalAmount);
return offlineAmount.toDescriptionString(getDecimals());
}
@Override
public boolean isBalanceNotZero() {
if (coinData == null) return false;
if (coinData.getBalance() == null) return false;
return coinData.getBalance().notZero();
}
@Override
public String getFeeCurrency() {
return Blockchain.Eos.getCurrency();
}
public boolean isNeedCheckNode() {
return false;
}
@Override
public CoinData createCoinData() {
return new EosData();
}
@Override
public String getUnspentInputsDescription() {
return "";
}
public void defineWallet() throws CardProtocol.TangemException {
try {
String wallet = calculateAddress(ctx.getCard().getWalletPublicKeyRar());
ctx.getCoinData().setWallet(wallet);
} catch (Exception e) {
ctx.getCoinData().setWallet("ERROR");
throw new CardProtocol.TangemException("Can't define wallet address");
}
}
// BigDecimal convertToEth(String value) {
// BigInteger m = new BigInteger(value, 10);
// BigDecimal n = new BigDecimal(m);
// BigDecimal d = n.divide(new BigDecimal("1000000000000000000"));
// d = d.setScale(8, RoundingMode.DOWN);
// return d;
// }
@Override
public boolean validateAddress(String address) {
if (address.length() != 12) {
return false;
}
if (!address.toLowerCase().equals(address)) {
return false;
}
if (StringUtils.containsAny(address, "06789")) {
return false;
}
return true;
}
@Override
public String getBalanceEquivalent() {
Amount balance = getBalance();
if (balance == null) return "";
return balance.toEquivalentString(coinData.getRate());
}
@Override
public Amount convertToAmount(InternalAmount internalAmount) {
return new Amount(internalAmount, getBalanceCurrency());
}
@Override
public Amount convertToAmount(String strAmount, String currency) {
return new Amount(strAmount, currency);
}
@Override
public InternalAmount convertToInternalAmount(Amount amount) {
return new InternalAmount(amount, getBalanceCurrency());
}
@Override
public InternalAmount convertToInternalAmount(byte[] bytes) {
//throw new Exception("Not implemented");
return null;
}
@Override
public byte[] convertToByteArray(InternalAmount amount) throws Exception {
throw new Exception("Not implemented");
}
@Override
public boolean hasBalanceInfo() {
return coinData.getBalance() != null;
}
@Override
public Uri getShareWalletUri() { //TODO: check
return Uri.parse(ctx.getCoinData().getWallet());
}
@Override
public Uri getWalletExplorerUri() {
return Uri.parse("https://bloks.io/account/" + ctx.getCoinData().getWallet());
}
@Override
public boolean isExtractPossible() {
if (!hasBalanceInfo()) {
ctx.setMessage(R.string.cannot_obtain_data_from_blockchain);
} else if (!isBalanceNotZero()) {
ctx.setMessage(R.string.wallet_empty);
} else if (awaitingConfirmation()) {
ctx.setMessage(R.string.please_wait_while_previous);
} else {
return true;
}
return false;
}
@Override
public InputFilter[] getAmountInputFilters() {
return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())};
}
@Override
public boolean checkNewTransactionAmount(Amount amount) {
if (BuildConfig.FLAVOR == Constant.FLAVOR_TANGEM_CARDANO) {
return true;
}
Amount balance = getBalance();
if (balance == null || amount.compareTo(balance) > 0) {
return false;
}
return true;
}
@Override
public boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded) {
try {
BigDecimal cardBalance = getBalance();
if (isFeeIncluded && (amount.compareTo(cardBalance) > 0 || amount.compareTo(fee) < 0))
return false;
if (!isFeeIncluded && amount.add(fee).compareTo(cardBalance) > 0)
return false;
} catch (NumberFormatException e) {
e.printStackTrace();
}
return true;
}
@Override //TODO: check
public boolean validateBalance(BalanceValidator balanceValidator) {
if (getBalance() == null) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Unknown balance");
balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh.");
return false;
}
if (coinData.isBalanceReceived()) {
balanceValidator.setScore(100);
balanceValidator.setFirstLine("Verified balance");
balanceValidator.setSecondLine("Balance confirmed in blockchain");
if (getBalance().isZero()) {
balanceValidator.setFirstLine("Empty wallet");
balanceValidator.setSecondLine("");
}
}
if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && getBalance().notZero()) {
balanceValidator.setScore(80);
balanceValidator.setFirstLine("Verified offline balance");
balanceValidator.setSecondLine("Restore internet connection to obtain trusted balance from blockchain");
}
return true;
}
@Override
public String evaluateFeeEquivalent(String fee) {
try {
Amount feeValue = new Amount(fee, ctx.getBlockchain().getCurrency());
return feeValue.toEquivalentString(coinData.getRate());
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
@Override
public String calculateAddress(byte[] pkCompressed) {
String cid = Util.bytesToHex(ctx.getCard().getCID());
String address = "testem" + cid.substring(2, 4) + cid.substring(11, 15);//TODO: change
address = address.replace("0", "o").replace("6","b").replace("7,","t").replace("8","s").replace("9","g");
return address;
// byte[] csum = Ripemd160.from(pkCompressed).bytes();
// csum = Raw.copy(csum, 0, 4);
// byte[] addy = Raw.concat(pkCompressed, csum);
// StringBuffer bf = new StringBuffer("EOS");
// bf.append(Base58.encode(addy));
// return bf.toString() + " " + address;
}
// reference - https://gist.github.com/adyliu/492503b94d0306371298f24e15481da4
@Override
public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws JsonProcessingException {
// get the current state of blockchain
EosApi eosApi = EosApiFactory.create("https://api.eosdetroit.io:443");
SignArg arg = eosApi.getSignArg(120);
System.out.println(eosApi.getObjectMapper().writeValueAsString(arg));
// --- prepare transaction for sign as in LocalApiImpl
String quantity = amountValue.setScale(4).toString();
String memo = "test";
// pack transfer data
TransferArg transferArg = new TransferArg(coinData.getWallet(), targetAddress, quantity, memo);
String transferData = Packer.packTransfer(transferArg);
//
// create the authorization
List<TransactionAuthorization> authorizations = Arrays.asList(new TransactionAuthorization(coinData.getWallet(), "active"));
// build the all actions
List<TransactionAction> actions = Arrays.asList(//
new TransactionAction("eosio.token", "transfer", authorizations, transferData)//
);
String blockTime = arg.getHeadBlockTime().toString();
DateTime time = new DateTime(arg.getHeadBlockTime()).plusSeconds(arg.getExpiredSecond());
String changedTime = time.toString();
long epochSecond = time.getMillis() / 1000;
// build the packed transaction
EosPackedTransaction packedTransaction = new EosPackedTransaction();
packedTransaction.setExpirationSec(epochSecond);
packedTransaction.setRefBlockNum(arg.getLastIrreversibleBlockNum());
packedTransaction.setRefBlockPrefix(arg.getRefBlockPrefix());
packedTransaction.setMaxNetUsageWords(0);
packedTransaction.setMaxCpuUsageMs(0);
packedTransaction.setDelaySec(0);
packedTransaction.setActions(actions);
Raw raw = EosPacker.packPackedTransaction(arg.getChainId(), packedTransaction);
raw.pack(ByteBuffer.allocate(33).array());// TODO: what's this?
Sha256Hash hashForSign = Sha256Hash.of(raw.bytes());
return new SignTask.TransactionToSign() {
@Override
public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) {
return signingMethod == TangemCard.SigningMethod.Sign_Hash;
}
@Override
public byte[][] getHashesToSign() throws NoSuchAlgorithmException {
byte[][] hashesForSign = new byte[1][];
hashesForSign[0] = hashForSign.getBytes();
return hashesForSign;
}
@Override
public byte[] getRawDataToSign() throws Exception {
throw new Exception("Signing of raw transaction not supported for " + this.getClass().getSimpleName());
}
@Override
public String getHashAlgToSign() throws Exception {
throw new Exception("Signing of raw transaction not supported for " + this.getClass().getSimpleName());
}
@Override
public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception {
throw new Exception("Transaction validation by issuer not supported in this version");
}
@Override
public byte[] onSignCompleted(byte[] signFromCard) throws Exception {
BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, 32));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32, 64));
s = CryptoUtil.toCanonicalised(s);
boolean f = ECKey.verify(Util.calculateSHA256(raw.bytes()), new ECKey.ECDSASignature(r, s), ctx.getCard().getWalletPublicKey());
if (!f) {
Log.e(this.getClass().getSimpleName() + "-CHECK", "sign Failed.");
}
ECKey.ECDSASignature ecdsaSig = new ECKey.ECDSASignature(r, s);
int v = BruteRecoveryID(ecdsaSig, hashForSign, ctx.getCard().getWalletPublicKeyRar());
v += 4; // compressed
v += 27; // compact // 24 or 27 :( forcing odd-y 2nd key candidate)
byte[] pub_buf = new byte[65];
pub_buf[0] = (byte) v;
System.arraycopy(r.toByteArray(), 0, pub_buf, 1, r.toByteArray().length);
System.arraycopy(s.toByteArray(), 0, pub_buf, r.toByteArray().length + 1, s.toByteArray().length);
byte[] checksum = Ripemd160.from(Raw.concat(pub_buf, "K1".getBytes())).bytes();
byte[] signatureBytes = Raw.concat(pub_buf, Raw.copy(checksum, 0, 4));
String signatureString = "SIG_K1_" + Base58.encode(signatureBytes);
EosPushTransactionRequest req = new EosPushTransactionRequest();
req.setTransaction(packedTransaction);
req.setSignatures(Arrays.asList(signatureString));
//serialize
byte[] txForSend = SerializationUtils.serialize(req);
notifyOnNeedSendTransaction(txForSend);
return txForSend;
}
};
}
private int BruteRecoveryID(ECKey.ECDSASignature sig, Sha256Hash messageHash, byte[] thisKey) {
Log.e("EOS_KZ", BTCUtils.toHex(thisKey));
int recId = -1;
for (int i = 0; i < 4; i++) {
ECKey k = ECKey.recoverFromSignature(i, sig, messageHash, true);
if (k == null)
continue;
byte[] recK = k.getPubKey();
Log.e("EOS_k " + String.valueOf(i), BTCUtils.toHex(recK));
if (k != null && Arrays.equals(recK, thisKey)) {
recId = i;
break;
}
}
return recId;
}
@Override
public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) {
Observer<Account> accountObserver = new DefaultObserver<Account>() {
@Override
public void onNext(Account account) {
String[] balanceStrings = account.getCoreLiquidBalance().split(" ");
coinData.setBalanceReceived(true);
coinData.setBalance(new Amount(balanceStrings[0], balanceStrings[1]));
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "requestBalanceAndUnspentTransactions error" + e.getMessage());
ctx.setError(e.getMessage());
blockchainRequestsCallbacks.onComplete(false);
}
@Override
public void onComplete() {
blockchainRequestsCallbacks.onComplete(true);
}
};
ServerApiEos.getBalance(coinData.getWallet(), accountObserver);
}
@Override
public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) {
// no fee in EOS
blockchainRequestsCallbacks.onComplete(true);
}
@Override
public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws IOException, ClassNotFoundException {
// deserialize
EosPushTransactionRequest req = SerializationUtils.deserialize(txForSend);
try {
EosApi eosApi = EosApiFactory.create("https://api.eosdetroit.io:443");
PushedTransaction pts = eosApi.pushTransaction(req);
LocalApi localApi = EosApiFactory.createLocalApi();
System.out.println(localApi.getObjectMapper().writeValueAsString(pts));
blockchainRequestsCallbacks.onComplete(true);
} catch (Exception e) {
Log.e(TAG, "requestSendTransaction error" + e.getMessage());
ctx.setError(e.getMessage());
blockchainRequestsCallbacks.onComplete(false);
}
}
public int pendingTransactionTimeoutInSeconds() {
return 10;
}
@Override
public boolean needMultipleLinesForBalance() {
return true;
}
}