diff --git a/app/build.gradle b/app/build.gradle index ef7f92e63e..9caa3ca724 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,6 +54,11 @@ android { } packagingOptions { pickFirst('META-INF/proguard/okhttp3.pro') + // for one.block:eosiojava:0.1.0 + exclude 'lib/x86_64/darwin/libscrypt.dylib' + exclude 'lib/x86_64/freebsd/libscrypt.so' + exclude 'lib/x86_64/linux/libscrypt.so' + exclude 'org.slf4j:slf4j-jdk14:1.7.25' } buildToolsVersion '28.0.3' } @@ -103,8 +108,8 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0" - implementation 'org.bitcoinj:bitcoinj-parent:0.14.7' - implementation 'org.bitcoinj:bitcoinj-core:0.14.7' +// implementation 'org.bitcoinj:bitcoinj-parent:0.14.7' //TODO: is this needed? + implementation 'org.bitcoinj:bitcoinj-core:0.15.2' implementation 'org.greenrobot:eventbus:3.1.1' implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'info.hoang8f:android-segmented:1.0.6' @@ -117,7 +122,7 @@ dependencies { implementation files('libs/ripple-core-0.0.1.jar') //4 dependencies for ripple-core TODO: move to module? implementation 'net.i2p.crypto:eddsa:0.3.0' - implementation 'org.bouncycastle:bcprov-jdk15on:1.58' + implementation 'org.bouncycastle:bcprov-jdk15on:1.61' //noinspection DuplicatePlatformClasses implementation 'org.json:json:20180813' implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' @@ -131,4 +136,9 @@ dependencies { implementation 'com.fasterxml.jackson.datatype:jackson-datatype-joda:2.9.8' implementation 'joda-time:joda-time:2.10.1' testImplementation 'junit:junit:4.12' + + //dependencies for EOS + implementation 'io.jafka:jeos:0.9.15' +// implementation 'one.block:eosiojava:0.1.0' + implementation 'org.bouncycastle:bcpkix-jdk15on:1.61' } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java index 4ab21c3565..98b451c54e 100644 --- a/app/src/main/java/com/tangem/data/Blockchain.java +++ b/app/src/main/java/com/tangem/data/Blockchain.java @@ -24,7 +24,8 @@ public enum Blockchain { Matic("MATIC", "MTX", 1.0, R.drawable.tangem2, "Matic"), MaticTestNet("MATIC/test", "MTX", 1.0, R.drawable.tangem2, "Matic Testnet"), Stellar("XLM", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar"), - StellarTestNet("XLM/test", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar Testnet"); + StellarTestNet("XLM/test", "XLM", 1000000.0, R.drawable.ic_logo_stellar, "Stellar Testnet"), + Eos("EOS", "EOS", 10000.0, R.drawable.tangem2, "EOS"); Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) { mID = ID; diff --git a/app/src/main/java/com/tangem/data/network/ServerApiEos.java b/app/src/main/java/com/tangem/data/network/ServerApiEos.java new file mode 100644 index 0000000000..81f666b50b --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/ServerApiEos.java @@ -0,0 +1,48 @@ +package com.tangem.data.network; + +import android.util.Log; + +import com.tangem.wallet.eos.EosApiPush; +import com.tangem.wallet.eos.EosPushTransactionRequest; + +import io.jafka.jeos.EosApi; +import io.jafka.jeos.EosApiFactory; +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.impl.EosApiServiceGenerator; +import io.jafka.jeos.impl.EosChainApiService; +import io.reactivex.Observable; +import io.reactivex.Observer; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +public class ServerApiEos { + private static String TAG = ServerApiBinance.class.getSimpleName(); + + public static void getBalance(String wallet, Observer accountObserver) { + Log.i(TAG, "new getBalance request"); + EosApi eosApi = EosApiFactory.create("https://api.eosdetroit.io:443"); //TODO: add random server request + + Observable accountObservable = Observable.just(new Account()) + .map(account -> eosApi.getAccount(wallet)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + + accountObservable.subscribe(accountObserver); + } + + public static void sendTransaction(EosPushTransactionRequest req, Observer sendObserver) { + Log.i(TAG, "new getBalance request"); +// EosApi eosApi = EosApiFactory.create("https://api.eosdetroit.io:443"); //TODO: add random server request + EosApiPush eosApiPush = EosApiServiceGenerator.createService(EosApiPush.class, "https://api.eosdetroit.io:443"); //TODO: add random server request + + Observable sendObservable = Observable.just(new PushedTransaction()) + .map(pushedTransaction -> EosApiServiceGenerator.executeSync(eosApiPush.pushTransaction(req))) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()); + + sendObservable.subscribe(sendObserver); + } + +} diff --git a/app/src/main/java/com/tangem/util/CryptoUtil.java b/app/src/main/java/com/tangem/util/CryptoUtil.java index cde631f591..7c1162da1d 100644 --- a/app/src/main/java/com/tangem/util/CryptoUtil.java +++ b/app/src/main/java/com/tangem/util/CryptoUtil.java @@ -5,22 +5,22 @@ import android.util.Log; import com.tangem.wallet.ECDSASignatureETH; import com.tangem.card_common.util.Util; -import org.spongycastle.asn1.ASN1EncodableVector; -import org.spongycastle.asn1.ASN1Integer; -import org.spongycastle.asn1.DERSequence; -import org.spongycastle.asn1.sec.SECNamedCurves; -import org.spongycastle.asn1.x9.X9ECParameters; -import org.spongycastle.asn1.x9.X9IntegerConverter; -import org.spongycastle.crypto.params.ECDomainParameters; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.spongycastle.crypto.signers.ECDSASigner; -import org.spongycastle.jce.ECNamedCurveTable; -import org.spongycastle.jce.spec.ECNamedCurveParameterSpec; -import org.spongycastle.jce.spec.ECPublicKeySpec; -import org.spongycastle.math.ec.ECAlgorithms; -import org.spongycastle.math.ec.ECCurve; -import org.spongycastle.math.ec.ECPoint; +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.asn1.x9.X9IntegerConverter; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; +import org.bouncycastle.jce.spec.ECPublicKeySpec; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/app/src/main/java/com/tangem/util/DerEncodingUtil.java b/app/src/main/java/com/tangem/util/DerEncodingUtil.java index 3ff4a66391..f3a88131df 100644 --- a/app/src/main/java/com/tangem/util/DerEncodingUtil.java +++ b/app/src/main/java/com/tangem/util/DerEncodingUtil.java @@ -87,46 +87,46 @@ public class DerEncodingUtil { return bos.toByteArray(); } - public static byte[] DerEncoding(byte[] sign) - { - byte[] r = sign; - byte[] s = new byte[32]; - for(int i =0; i < 32; ++i) - { - s[i] = sign[i+32]; - } - - byte[] newR = PackInteger(r); - byte[] newS = PackInteger(s); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - baos.write((byte)(newR.length+newS.length+2)); - baos.write((byte)newR.length); - baos.write(newR, 0, newR.length); - baos.write((byte)newS.length); - baos.write(newS, 0, newS.length); - - return baos.toByteArray(); - } - - public static byte[] DerEncodingBI(BigInteger[] sign) - { - byte[] r = sign[0].toByteArray(); - byte[] s = sign[1].toByteArray(); - - byte[] newR = PackInteger(r); - byte[] newS = PackInteger(s); - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - - baos.write((byte)(newR.length+newS.length+2)); - - baos.write((byte)newR.length); - baos.write(newR, 0, newR.length); - - baos.write((byte)newS.length); - baos.write(newS, 0, newS.length); - - return baos.toByteArray(); - } +// public static byte[] DerEncoding(byte[] sign) +// { +// byte[] r = sign; +// byte[] s = new byte[32]; +// for(int i =0; i < 32; ++i) +// { +// s[i] = sign[i+32]; +// } +// +// byte[] newR = PackInteger(r); +// byte[] newS = PackInteger(s); +// +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// baos.write((byte)(newR.length+newS.length+2)); +// baos.write((byte)newR.length); +// baos.write(newR, 0, newR.length); +// baos.write((byte)newS.length); +// baos.write(newS, 0, newS.length); +// +// return baos.toByteArray(); +// } +// +// public static byte[] DerEncodingBI(BigInteger[] sign) +// { +// byte[] r = sign[0].toByteArray(); +// byte[] s = sign[1].toByteArray(); +// +// byte[] newR = PackInteger(r); +// byte[] newS = PackInteger(s); +// +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// +// baos.write((byte)(newR.length+newS.length+2)); +// +// baos.write((byte)newR.length); +// baos.write(newR, 0, newR.length); +// +// baos.write((byte)newS.length); +// baos.write(newS, 0, newS.length); +// +// return baos.toByteArray(); +// } } diff --git a/app/src/main/java/com/tangem/wallet/CoinEngine.java b/app/src/main/java/com/tangem/wallet/CoinEngine.java index ea0a0dfb6d..11de555ac1 100644 --- a/app/src/main/java/com/tangem/wallet/CoinEngine.java +++ b/app/src/main/java/com/tangem/wallet/CoinEngine.java @@ -165,6 +165,11 @@ public abstract class CoinEngine { public boolean isZero() { return compareTo(BigDecimal.ZERO) == 0; } + + @Override + public Amount setScale(int newScale) { + return new Amount(super.setScale(newScale), currency); + } } protected TangemContext ctx; diff --git a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt index 21bf4593cf..b289fac550 100644 --- a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt +++ b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt @@ -7,6 +7,7 @@ import com.tangem.wallet.eth.EthEngine import com.tangem.wallet.token.TokenEngine import com.tangem.wallet.bch.BtcCashEngine import com.tangem.data.Blockchain +import com.tangem.wallet.eos.EosEngine import com.tangem.wallet.binance.BinanceEngine import com.tangem.wallet.cardano.CardanoData import com.tangem.wallet.cardano.CardanoEngine @@ -45,6 +46,7 @@ object CoinEngineFactory { Blockchain.Binance, Blockchain.BinanceTestNet -> BinanceEngine() Blockchain.Matic, Blockchain.MaticTestNet -> MaticTokenEngine() Blockchain.StellarTestNet, Blockchain.Stellar -> XlmEngine() + Blockchain.Eos -> EosEngine() else -> null } } @@ -78,6 +80,8 @@ object CoinEngineFactory { MaticTokenEngine(context) else if (Blockchain.Stellar == context.blockchain || Blockchain.StellarTestNet == context.blockchain) XlmEngine(context) + else if (Blockchain.Eos == context.blockchain) + EosEngine(context) else return null } catch (e: Exception) { diff --git a/app/src/main/java/com/tangem/wallet/eos/EosApiPush.java b/app/src/main/java/com/tangem/wallet/eos/EosApiPush.java new file mode 100644 index 0000000000..8bb011bbc6 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/EosApiPush.java @@ -0,0 +1,13 @@ +package com.tangem.wallet.eos; + +import com.tangem.wallet.eos.EosPushTransactionRequest; + +import io.jafka.jeos.core.response.chain.transaction.PushedTransaction; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.POST; + +public interface EosApiPush { + @POST("/v1/chain/push_transaction") + Call pushTransaction(@Body EosPushTransactionRequest eosPushTransactionRequest); +} diff --git a/app/src/main/java/com/tangem/wallet/eos/EosData.java b/app/src/main/java/com/tangem/wallet/eos/EosData.java new file mode 100644 index 0000000000..dc438865b1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/EosData.java @@ -0,0 +1,53 @@ +package com.tangem.wallet.eos; + +import android.os.Bundle; +import android.util.Log; + +import com.tangem.wallet.CoinData; +import com.tangem.wallet.CoinEngine; + +public class EosData extends CoinData { + private CoinEngine.Amount balance = null; + + @Override + public void clearInfo() { + super.clearInfo(); + balance = null; + } + + public CoinEngine.Amount getBalance() { + return balance; + } + + public void setBalance(CoinEngine.Amount value) { + balance = value; + } + + @Override + public void loadFromBundle(Bundle B) { + super.loadFromBundle(B); + + if (B.containsKey("BalanceCurrency") && B.containsKey("BalanceDecimal")) { + String currency = B.getString("BalanceCurrency"); + balance = new CoinEngine.Amount(B.getString("BalanceDecimal"), currency); + } else { + balance = null; + } + } + + @Override + public void saveToBundle(Bundle B) { + super.saveToBundle(B); + try { + if (balance != null) { + B.putString("BalanceCurrency", balance.getCurrency()); + B.putString("BalanceDecimal", balance.toValueString()); + } + + } catch (Exception e) { + Log.e("Can't save to bundle ", e.getMessage()); + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/eos/EosEngine.java b/app/src/main/java/com/tangem/wallet/eos/EosEngine.java new file mode 100644 index 0000000000..8dc387cafd --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/EosEngine.java @@ -0,0 +1,630 @@ +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.google.common.primitives.Bytes; +import com.google.gson.Gson; +import com.tangem.Constant; +import com.tangem.card_common.data.TangemCard; +import com.tangem.card_common.reader.CardProtocol; +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.util.DerEncodingUtil; +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 com.tangem.wallet.eos.utilities.EOSFormatter; + +import org.apache.commons.lang3.SerializationUtils; +import org.apache.commons.lang3.StringUtils; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.core.Utils; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; + +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.response.chain.account.Account; +import io.jafka.jeos.core.response.chain.transaction.PushedTransaction; +import io.jafka.jeos.util.Base58; +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; + + private final int signRetries = 10; + + 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()).toLowerCase(); + String address = cid.substring(0, 4) + cid.substring(8, 16); + address = address.replace("0", "o").replace("6", "b").replace("7,", "t").replace("8", "s").replace("9", "g"); + return address; + +// String cid = Util.bytesToHex(ctx.getCard().getCID()); +// String address = "testem" + cid.substring(2, 4) + cid.substring(11, 15); +// 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; + } + + public String calculateEosPubKey(byte[] pkCompressed) { + 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(); + } + + // 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 = ""; + + // ① pack transfer data + TransferArg transferArg = new TransferArg(coinData.getWallet(), targetAddress, quantity, memo); + String transferData = Packer.packTransfer(transferArg); + // + + // ③ create the authorization + List authorizations = Arrays.asList(new TransactionAuthorization(coinData.getWallet(), "active")); + + // ④ build the all actions + List actions = Arrays.asList(// + new TransactionAction("eosio.token", "transfer", authorizations, transferData)// + ); + + long expMillis = System.currentTimeMillis() + (arg.getExpiredSecond() * 1000); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.5", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + String stringTime = format.format(new Date(expMillis)); + + // ⑤ build the packed transaction + EosPackedTransaction packedTransaction = new EosPackedTransaction(); + packedTransaction.setExpiration(stringTime); + 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()); //black magic + 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[signRetries][]; + for (int i = 0; i < signRetries; i++) { + hashesForSign[i] = 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 = null, s = null; + for (int i = 0; i < signRetries; ++i) { + r = new BigInteger(1, Arrays.copyOfRange(signFromCard, i * 64, 32 + i * 64)); + + if (r.toByteArray().length == 33) { + Log.e(TAG, "33 bytes R: " + Util.bytesToHex(r.toByteArray())); + } else { + s = new BigInteger(1, Arrays.copyOfRange(signFromCard, 32 + i * 64, 64 + i * 64)); + s = CryptoUtil.toCanonicalised(s); + break; + } + } + if (s == null) { + throw new Exception("All signatures not canonical"); + } + + 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[] rbytes = Utils.bigIntegerToBytes(r, 32); + byte[] sbytes = Utils.bigIntegerToBytes(s, 32); + + //TODO: not every signature works for EOS, if r.toByteArray length is 33, even if first 0x00 byte is cut, + //TODO: signature would be counted non canonical because first bit is not zero. Need to check it on card or there will be multiple security delays +// if (r.toByteArray().length == 33) { +// Log.e(TAG, "33 bytes R: " + Util.bytesToHex(rbytes)); +// throw new Exception("33 byte R"); +// } + + byte[] pub_buf = new byte[65]; + pub_buf[0] = (byte) v; + + System.arraycopy(rbytes, 0, pub_buf, 1, rbytes.length); + System.arraycopy(sbytes, 0, pub_buf, rbytes.length + 1, sbytes.length); + + byte[] checksum = Ripemd160.from(Raw.concat(pub_buf, "K1".getBytes())).bytes(); + + byte[] signatureBytes = Raw.concat(pub_buf, Raw.copy(checksum, 0, 4)); + Log.e(TAG, "Signature hex " + Util.byteArrayToHexString(signatureBytes)); + + String signatureString = "SIG_K1_" + Base58.encode(signatureBytes); + Log.e(TAG, "1st sig" + signatureString); + +// byte[] sigDer = DerEncodingUtil.DerEncoding(newR, s); +// String eosPubKey = calculateEosPubKey(ctx.getCard().getWalletPublicKeyRar()); +// String pemPubKey = EOSFormatter.convertEOSPublicKeyToPEMFormat(eosPubKey); +// String convertedSignature = EOSFormatter.convertDERSignatureToEOSFormat(sigDer, raw.bytes(), pemPubKey); +// Log.e(TAG, "2nd sig" + convertedSignature); +// String convertedBase = convertedSignature.substring(7); +// byte[] convertedBytes = Base58.decode(convertedBase); + + EosPushTransactionRequest req = new EosPushTransactionRequest(); + req.setTransaction(packedTransaction); + req.setSignatures(Arrays.asList(signatureString)); + + //serialize + String reqString = new Gson().toJson(req); + byte[] txForSend = SerializationUtils.serialize(reqString); + + 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 " + i, BTCUtils.toHex(recK)); + if (Arrays.equals(recK, thisKey)) { + recId = i; + break; + } + } + return recId; + } + + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + Observer accountObserver = new DefaultObserver() { + @Override + public void onNext(Account account) { + if (account.getCoreLiquidBalance() != null) { + String[] balanceStrings = account.getCoreLiquidBalance().split(" "); + coinData.setBalanceReceived(true); + coinData.setBalance(new Amount(balanceStrings[0], balanceStrings[1])); + } else { + coinData.setBalanceReceived(true); + coinData.setBalance(new Amount(0L, getBalanceCurrency())); + } + } + + @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 + coinData.minFee = coinData.normalFee = coinData.maxFee = new Amount(0L, "EOS"); + blockchainRequestsCallbacks.onComplete(true); + } + + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) throws IOException, ClassNotFoundException { + // deserialize + String reqString = SerializationUtils.deserialize(txForSend); + EosPushTransactionRequest req = new Gson().fromJson(reqString, EosPushTransactionRequest.class); + + Observer sendObserver = new DefaultObserver() { + @Override + public void onNext(PushedTransaction pushedTransaction) { + if (pushedTransaction.getProcessed().getReceipt().getStatus().equals("executed")) { + ctx.setError(null); + } else { + ctx.setError("Error sending transaction. Transaction rejected"); + try { + LocalApi localApi = EosApiFactory.createLocalApi(); + Log.e(TAG, localApi.getObjectMapper().writeValueAsString(pushedTransaction)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + } + + @Override + public void onError(Throwable e) { + Log.e(TAG, "requestSendTransaction error" + e.getMessage()); + ctx.setError(e.getMessage()); + LocalApi localApi = EosApiFactory.createLocalApi(); + try { + Log.e(TAG, localApi.getObjectMapper().writeValueAsString(req)); + } catch (JsonProcessingException e1) { + e1.printStackTrace(); + } + blockchainRequestsCallbacks.onComplete(false); + } + + @Override + public void onComplete() { + if (!ctx.hasError()) { + blockchainRequestsCallbacks.onComplete(true); + } else { + blockchainRequestsCallbacks.onComplete(false); + } + } + }; + + ServerApiEos.sendTransaction(req, sendObserver); + } + + public int pendingTransactionTimeoutInSeconds() { + return 10; + } + + @Override + public boolean needMultipleLinesForBalance() { + return true; + } + + @Override + public boolean allowSelectFeeLevel() { + return false; + } + + @Override + public boolean allowSelectFeeInclusion() { + return false; + } + +} diff --git a/app/src/main/java/com/tangem/wallet/eos/EosPackedTransaction.kt b/app/src/main/java/com/tangem/wallet/eos/EosPackedTransaction.kt new file mode 100644 index 0000000000..36a7bb9dbd --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/EosPackedTransaction.kt @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos + +import com.fasterxml.jackson.annotation.JsonInclude +import io.jafka.jeos.core.common.transaction.TransactionAction +import java.util.* + +@JsonInclude(JsonInclude.Include.NON_NULL) +data class EosPackedTransaction( + var expiration: String? = null,//"2018-08-30T02:30:49" + var refBlockNum: Long? = null, + var refBlockPrefix: Long? = null, + + var maxNetUsageWords: Int? = null, + var maxCpuUsageMs: Int? = null, + var delaySec: Int? = null, + var contextFreeActions: ArrayList = ArrayList(), + var actions: List = ArrayList(), + + var transactionExtensions: ArrayList = ArrayList(), + //private List signatures; + var contextFreeData: ArrayList = ArrayList(), + + // + var region: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/eos/EosPacker.java b/app/src/main/java/com/tangem/wallet/eos/EosPacker.java new file mode 100644 index 0000000000..88f1081fb8 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/EosPacker.java @@ -0,0 +1,71 @@ +package com.tangem.wallet.eos; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import io.jafka.jeos.convert.Packer; +import io.jafka.jeos.core.common.transaction.TransactionAction; +import io.jafka.jeos.core.common.transaction.TransactionAuthorization; +import io.jafka.jeos.util.Raw; +import io.jafka.jeos.util.ecc.Hex; + +public class EosPacker extends Packer { + public static Raw packPackedTransaction(String chainId, EosPackedTransaction t) { + Raw raw = new Raw(); + //chain + raw.pack(Hex.toBytes(chainId)); + //expiration + try { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.5", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + Date date = format.parse(t.getExpiration()); + raw.packUint32(date.getTime() / 1000); + } catch (ParseException e) { + e.printStackTrace(); + } + //ref_block_num + raw.packUint16(t.getRefBlockNum().intValue()); + //ref_block_prefix + raw.packUint32(t.getRefBlockPrefix()); + //max_net_usage_words + raw.packVarint32(t.getMaxNetUsageWords()); + //max_cpu_usage_ms + raw.packUint8(t.getMaxCpuUsageMs());//TODO: what the type? + //delay_sec + raw.packVarint32(t.getDelaySec()); + //context_free_actions + raw.packVarint32(t.getContextFreeActions().size()); + //TODO: getContextFreeActions + + //actions + raw.packVarint32(t.getActions().size()); + + for (TransactionAction a : t.getActions()) { + //action.account + raw.packName(a.getAccount())// + .packName(a.getName())// + .packVarint32(a.getAuthorization().size())// + ; + //action.authorization + for (TransactionAuthorization au : a.getAuthorization()) { + raw.packName(au.getActor())// + .packName(au.getPermission()); + } + + //action.data + byte[] dat = Hex.toBytes(a.getData()); + raw.packVarint32(dat.length); + raw.pack(dat); + } + //transaction_extensions + //raw.packVarint32(t.getTransactionExtensions().size()); + //TODO: getTransactionExtensions + + //context_free_data + //raw.packVarint32(t.getContextFreeActions().size()); + return raw; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/eos/EosPushTransactionRequest.kt b/app/src/main/java/com/tangem/wallet/eos/EosPushTransactionRequest.kt new file mode 100644 index 0000000000..0120b64557 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/EosPushTransactionRequest.kt @@ -0,0 +1,7 @@ +package com.tangem.wallet.eos + +data class EosPushTransactionRequest( + var compression: String = "none", + var transaction: EosPackedTransaction? = null, + var signatures: List? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/eos/enums/AlgorithmEmployed.java b/app/src/main/java/com/tangem/wallet/eos/enums/AlgorithmEmployed.java new file mode 100644 index 0000000000..92d13358df --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/enums/AlgorithmEmployed.java @@ -0,0 +1,39 @@ +package com.tangem.wallet.eos.enums; + +/** + * Enum of supported algorithms which are employed in eosio-java library + */ +public enum AlgorithmEmployed { + /** + * Supported SECP256r1 (prime256v1) algorithm curve + */ + SECP256R1("secp256r1"), + + /** + * Supported SECP256k1 algorithm curve + */ + SECP256K1("secp256k1"), + + /** + * Supported prime256v1 algorithm curve + */ + PRIME256V1("prime256v1"); + + private String str; + + /** + * Initialize AlgorithmEmployed enum object with a String value + * @param str - input String value of enums in AlgorithmEmployed + */ + AlgorithmEmployed(String str) { + this.str = str; + } + + /** + * Gets string value of AlgorithmEmployed's enum + * @return string value of AlgorithmEmployed's enum + */ + public String getString() { + return str; + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/EosioError.java b/app/src/main/java/com/tangem/wallet/eos/error/EosioError.java new file mode 100644 index 0000000000..bb00d03e4f --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/EosioError.java @@ -0,0 +1,69 @@ +package com.tangem.wallet.eos.error; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to process anything inside the + * Eosio-java library + */ +public class EosioError extends Exception { + + /** + * Create an EosioError with a null message and original exception. + */ + public EosioError() { + super(); + } + + /** + * Construct an EosioError with the given message. + * + * @param message - Message text for the exception. + */ + public EosioError(@NotNull String message) { + super(message); + } + + /** + * Construct an EosioError with the given message and original exception. + * + * @param message - Message text for the exception. + * @param exception - Original root exception for the error. + */ + public EosioError(@NotNull String message, @NotNull Exception exception) { + super(message, exception); + } + + /** + * Construct an EosioError with the given original exception. + * + * @param exception - Original root exception for the error. + */ + public EosioError(@NotNull Exception exception) { + super(exception); + } + + /** + * Construct a JSON formatted string describing the error code and reason. + * + * @return A JSON formatted string + */ + @NotNull + public String asJsonString() { + JsonObject errInfo = new JsonObject(); + errInfo.addProperty("errorCode", this.getClass().getSimpleName()); + errInfo.addProperty("reason", this.getLocalizedMessage()); + JsonObject err = new JsonObject(); + err.addProperty("errorType", "EosioError"); + err.add("errorInfo", errInfo); + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String jsonString = gson.toJson(err); + return jsonString; + } + +} + diff --git a/app/src/main/java/com/tangem/wallet/eos/error/ErrorConstants.java b/app/src/main/java/com/tangem/wallet/eos/error/ErrorConstants.java new file mode 100644 index 0000000000..fdf06b04b7 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/ErrorConstants.java @@ -0,0 +1,314 @@ + + +package com.tangem.wallet.eos.error; + +import java.util.List; + +@SuppressWarnings("ALL") +public class ErrorConstants { + private ErrorConstants(){ + + } + + //EOSFormatter() Errors + /** + * The private key provided is not in the EOS format. + */ + public static final String INVALID_EOS_PRIVATE_KEY = "The EOS private key provided is invalid!"; + /** + * The public key provided is not in the EOS format. + */ + public static final String INVALID_EOS_PUBLIC_KEY = "The EOS public key provided is invalid!"; + /** + * An error occurred while Base58 decoding the EOS key. + */ + public static final String BASE58_DECODING_ERROR = "An error occurred while Base58 decoding the EOS key!"; + /** + * The key provided for Base58 decoding was empty. + */ + public static final String BASE58_EMPTY_KEY = "Input key to decode can't be empty!"; + /** + * Input key, checksum or key type were empty and are needed for validation. + */ + public static final String BASE58_EMPTY_CHECKSUM_OR_KEY = "Input key, checksum and key type to validate can't be empty!"; + /** + * Input key, checksum or key type were empty and are needed for validation. + */ + public static final String BASE58_EMPTY_CHECKSUM_OR_KEY_OR_KEY_TYPE = "Input key, checksum and key type to validate can't be empty!"; + /** + * Input key has invalid checksum. + */ + public static final String BASE58_INVALID_CHECKSUM = "Input key has invalid checksum!"; + /** + * Error converting DER encoded key to PEM format. + */ + public static final String DER_TO_PEM_CONVERSION = "Error converting DER encoded key to PEM format!"; + /** + * The algorithm used to generate the object is unsupported. + */ + public static final String UNSUPPORTED_ALGORITHM = "Unsupported algorithm!"; + /** + * The private key is not in PEM format. + */ + public static final String INVALID_PEM_PRIVATE_KEY = "This is not a PEM formatted private key!"; + /** + * The private key is not in DER format. + */ + public static final String INVALID_DER_PRIVATE_KEY = "DER format of private key is incorrect!"; + /** + * Checksum generation failed. + */ + public static final String CHECKSUM_GENERATION_ERROR = "Could not generate checksum!"; + /** + * The object could not be Base58 encoded. + */ + public static final String BASE58_ENCODING_ERROR = "Unable to Base58 encode object!"; + /** + * The public key could not be decompressed. + */ + public static final String PUBLIC_KEY_DECOMPRESSION_ERROR = "Problem decompressing public key!"; + /** + * The public key could not be compressed. + */ + public static final String PUBLIC_KEY_COMPRESSION_ERROR = "Problem compressing public key!"; + /** + * The public key provided for decoding was empty. + */ + public static final String PUBLIC_KEY_IS_EMPTY = "Input key to decode can't be empty!"; + /** + * Chain id or serialized transaction parameter was empty. + */ + public static final String EMPTY_INPUT_PREPARE_SERIALIZIED_TRANS_FOR_SIGNING = "Chain id and serialized transaction can't be empty!"; + /** + * The signable transaction parameter was empty. + */ + public static final String EMPTY_INPUT_EXTRACT_SERIALIZIED_TRANS_FROM_SIGNABLE = "Signable transaction can't be empty!"; + /** + * The length of the signable transaction was incorrect and the serialized transaction could not + * be extracted. + */ + public static final String INVALID_INPUT_SIGNABLE_TRANS_LENGTH_EXTRACT_SERIALIZIED_TRANS_FROM_SIGNABLE = "Length of the signable transaction must be larger than %s"; + /** + * The signable transaction was improperly formatted. + */ + public static final String INVALID_INPUT_SIGNABLE_TRANS_EXTRACT_SERIALIZIED_TRANS_FROM_SIGNABLE = "Signable transaction has to have this structure: chainId (64 characters) + serialized transaction + 32 bytes of 0!"; + /** + * Unable to extract the serialized transaction from the signable transaction. + */ + public static final String EXTRACT_SERIALIZIED_TRANS_FROM_SIGNABLE_ERROR = "Something went wrong when trying to extract serialized transaction from signable transaction."; + /** + * Signature formatting failed. + */ + public static final String SIGNATURE_FORMATTING_ERROR = "An error occured formating the signature!"; + /** + * A public key could not be recovered from the signature. + */ + public static final String COULD_NOT_RECOVER_PUBLIC_KEY_FROM_SIG = "Could not recover public key from Signature."; + /** + * The signature provided failed the canonical check. + */ + public static final String NON_CANONICAL_SIGNATURE = "Input signature is not canonical."; + /** + * The public key could not be extracted from the provided private key. The private key is most + * likely invalid. + */ + public static final String PUBLIC_KEY_COULD_NOT_BE_EXTRACTED_FROM_PRIVATE_KEY = "This is not a private key!"; + + // ABIProviderImpl Errors + public static final String NO_RESPONSE_RETRIEVING_ABI = "No response retrieving ABI."; + public static final String MISSING_ABI_FROM_RESPONSE = "Missing ABI from GetRawAbiResponse."; + public static final String CALCULATED_HASH_NOT_EQUAL_RETURNED = "Calculated ABI hash does not match returned hash."; + public static final String REQUESTED_ACCCOUNT_NOT_EQUAL_RETURNED = "Requested account name does not match returned account name."; + public static final String NO_ABI_FOUND = "No ABI found for requested account name."; + public static final String ERROR_RETRIEVING_ABI = "Error retrieving ABI from the chain."; + + //PEMProcessor Errors + /** + * The object provided is not in the PEM format. + */ + public static final String ERROR_READING_PEM_OBJECT = "Error reading PEM object!"; + /** + * The PEM object could not be parsed. + */ + public static final String ERROR_PARSING_PEM_OBJECT = "Error parsing PEM object!"; + /** + * There was no key data in the PEM object. + */ + public static final String KEY_DATA_NOT_FOUND = "Key data not found in PEM object!"; + /** + * PEM object could not be read. + */ + public static final String INVALID_PEM_OBJECT = "Cannot read PEM object!"; + + //TransactionProcessor Errors + /** + * Error message get thrown if actions list is empty during processes of {@link TransactionProcessor}. + */ + public static final String TRANSACTION_PROCESSOR_ACTIONS_EMPTY_ERROR_MSG = "Action list can't be empty!"; + + /** + * Error message get thrown if {@link IRPCProvider#getInfo()} thrown exception during processes of {@link TransactionProcessor} + */ + public static final String TRANSACTION_PROCESSOR_RPC_GET_INFO = "Error happened on calling GetInfo RPC."; + + /** + * Error message get thrown if {@link IRPCProvider#getBlock(GetBlockRequest)} thrown exception during process of {@link TransactionProcessor#prepare(List)} + */ + public static final String TRANSACTION_PROCESSOR_PREPARE_RPC_GET_BLOCK = "Error happened on calling GetBlock RPC."; + + /** + * Error message get thrown if chain id from {@link GetInfoResponse#getChainId()} does not match with the input chain id + */ + public static final String TRANSACTION_PROCESSOR_PREPARE_CHAINID_NOT_MATCH = "Provided chain id %s does not match chain id %s"; + + /** + * Error message get thrown if chain id from {@link GetInfoResponse#getChainId()} is empty. + */ + public static final String TRANSACTION_PROCESSOR_PREPARE_CHAINID_RPC_EMPTY = "Chain id from back end is empty!"; + + /** + * Error message get thrown if parsing head block time from {@link GetInfoResponse#getHeadBlockTime()} get error + */ + public static final String TRANSACTION_PROCESSOR_HEAD_BLOCK_TIME_PARSE_ERROR = "Failed to parse head block time"; + + /** + * Error message get thrown if making clone version of transaction is failed. + */ + public static final String TRANSACTION_PROCESSOR_PREPARE_CLONE_ERROR = "Error happened on cloning transaction."; + + /** + * Error message get thrown if making clone version of transaction is failed by {@link ClassNotFoundException} + */ + public static final String TRANSACTION_PROCESSOR_PREPARE_CLONE_CLASS_NOT_FOUND = "Transaction class was not found"; + + /** + * Error message get thrown if the current transaction inside {@link TransactionProcessor} has not been initialized or empty. + */ + public static final String TRANSACTION_PROCESSOR_TRANSACTION_HAS_TO_BE_INITIALIZED = "Transaction must be initialized before this method could be called! call prepare for initialize Transaction"; + + /** + * Error message get thrown if {@link IABIProvider#getAbi(String, EOSIOName)} get error. + */ + public static final String TRANSACTION_PROCESSOR_GET_ABI_ERROR = "Error happened on getting abi for contract [%s]"; + + /** + * Error message get thrown if Action's serialization process execute successfully but its result is empty. + */ + public static final String TRANSACTION_PROCESSOR_SERIALIZE_ACTION_WORKED_BUT_EMPTY_RESULT = "Serialization of action worked fine but got back empty result!"; + + /** + * Error message get thrown if Transaction's serialization process execute successfully but its result is empty. + */ + public static final String TRANSACTION_PROCESSOR_SERIALIZE_TRANSACTION_WORKED_BUT_EMPTY_RESULT = "Serialization of transaction worked fine but got back empty result!"; + + /** + * Error message get thrown if Action's serialization process get error by calling {@link ISerializationProvider#serialize(AbiEosSerializationObject)} + */ + public static final String TRANSACTION_PROCESSOR_SERIALIZE_ACTION_ERROR = "Error happened on serializing action [%s]"; + + /** + * Error message get thrown if Transaction's serialization process get error by calling {@link ISerializationProvider#serializeTransaction(String)} + */ + public static final String TRANSACTION_PROCESSOR_SERIALIZE_TRANSACTION_ERROR = "Error happened on serializing transaction"; + + /** + * Error message get thrown if {@link ISignatureProvider#getAvailableKeys()} returns error. + */ + public static final String TRANSACTION_PROCESSOR_GET_AVAILABLE_KEY_ERROR = "Error happened on getAvailableKeys from SignatureProvider!"; + + /** + * Error message get thrown if {@link ISignatureProvider#getAvailableKeys()} returns no key. + */ + public static final String TRANSACTION_PROCESSOR_GET_AVAILABLE_KEY_EMPTY = "Signature provider return no available key"; + + /** + * Error message get thrown if {@link IRPCProvider#getRequiredKeys(GetRequiredKeysRequest)} get error. + */ + public static final String TRANSACTION_PROCESSOR_RPC_GET_REQUIRED_KEYS = "Error happened on calling getRequiredKeys RPC call."; + + /** + * Error message get thrown if {@link IRPCProvider#getRequiredKeys(GetRequiredKeysRequest)} returns no key. + */ + public static final String GET_REQUIRED_KEY_RPC_EMPTY_RESULT = "GetRequiredKeys RPC returned no required keys"; + + /** + * Error message get thrown if {@link ISignatureProvider#signTransaction(EosioTransactionSignatureRequest)} returns error + */ + public static final String TRANSACTION_PROCESSOR_SIGN_TRANSACTION_ERROR = "Error happened on calling sign transaction of Signature provider"; + + /** + * Error message get thrown if {@link ISignatureProvider#signTransaction(EosioTransactionSignatureRequest)} return empty serialized transaction. + */ + public static final String TRANSACTION_PROCESSOR_SIGN_TRANSACTION_TRANS_EMPTY_ERROR = "Serialized transaction come back empty from Signature Provider"; + + /** + * Error message get thrown if {@link ISignatureProvider#signTransaction(EosioTransactionSignatureRequest)} return no signature. + */ + public static final String TRANSACTION_PROCESSOR_SIGN_TRANSACTION_SIGN_EMPTY_ERROR = "Signatures come back empty from Signature Provider"; + + /** + * Error message get thrown if {@link EosioTransactionSignatureResponse} which return from {@link ISignatureProvider#signTransaction(EosioTransactionSignatureRequest)} has modified serialized transaction but {@link TransactionProcessor#isTransactionModificationAllowed()} is false + */ + public static final String TRANSACTION_IS_NOT_ALLOWED_TOBE_MODIFIED = "The transaction is not allowed to be modified but was modified by signature provider!"; + + /** + * Error message get thrown if {@link ISerializationProvider#deserializeTransaction} returns error during deserialize modified serialized transaction inside {@link EosioTransactionSignatureResponse} which return from {@link ISignatureProvider#signTransaction(EosioTransactionSignatureRequest)} + */ + public static final String TRANSACTION_PROCESSOR_GET_SIGN_DESERIALIZE_TRANS_ERROR = "Error happened on calling deserializeTransaction to refresh transaction object with new values"; + + /** + * Error message get thrown if {@link IRPCProvider#pushTransaction(PushTransactionRequest)} returns error. + */ + public static final String TRANSACTION_PROCESSOR_RPC_PUSH_TRANSACTION = "Error happened on calling pushTransaction RPC call"; + + /** + * Error message get thrown if {@link TransactionProcessor#serialize()} + */ + public static final String TRANSACTION_PROCESSOR_SERIALIZE_ERROR = "Error happened on calling serializeTransaction"; + + /** + * Error message get thrown if error happens during creating signature process of {@link TransactionProcessor#sign()} + */ + public static final String TRANSACTION_PROCESSOR_SIGN_CREATE_SIGN_REQUEST_ERROR = "Error happened on creating signature request for Signature Provider to sign!"; + + /** + * Error message get thrown if error happens during pushing transaction to backend + */ + public static final String TRANSACTION_PROCESSOR_BROADCAST_TRANS_ERROR = "Error happened on pushing transaction to chain!"; + + /** + * Error message get thrown if required keys from {@link GetRequiredKeysResponse} is not subset of keys from {@link ISignatureProvider#getAvailableKeys()} + */ + public static final String TRANSACTION_PROCESSOR_REQUIRED_KEY_NOT_SUBSET = "Required keys from back end are not available in available keys from Signature Provider."; + + /** + * Error message get thrown if serialized transaction is empty or has not been populated during process of {@link TransactionProcessor#broadcast()} + */ + public static final String TRANSACTION_PROCESSOR_BROADCAST_SERIALIZED_TRANSACTION_EMPTY = "Serialized Transaction is empty or has not been populated. Make sure to call prepare then sign before calling broadcast"; + + /** + * Error message get thrown if serialized transaction is empty or has not been populated during process of {@link TransactionProcessor#signAndBroadcast()} ()} + */ + public static final String TRANSACTION_PROCESSOR_SIGN_BROADCAST_SERIALIZED_TRANSACTION_EMPTY = "Serialized Transaction is empty or has not been populated. Make sure to call prepare then sign before calling sign and broadcast"; + + /** + * Error message get thrown if {@link ISignatureProvider#signTransaction(EosioTransactionSignatureRequest)} return error during process of {@link TransactionProcessor#sign()} + */ + public static final String TRANSACTION_PROCESSOR_SIGN_SIGNATURE_RESPONSE_ERROR = "Error happened on the response of getSignature."; + + /** + * Error message get thrown if {@link ISerializationProvider#deserializeTransaction} returns empty result during deserialize modified serialized transaction inside {@link EosioTransactionSignatureResponse} which return from {@link ISignatureProvider#signTransaction(EosioTransactionSignatureRequest)} + */ + public static final String TRANSACTION_PROCESSOR_GET_SIGN_DESERIALIZE_TRANS_EMPTY_ERROR = "Deserialized transaction is null or empty"; + + /** + * Error message get thrown if {@link TransactionProcessor#getSignatures()} is empty during process of {@link TransactionProcessor#broadcast()} + */ + public static final String TRANSACTION_PROCESSOR_BROADCAST_SIGN_EMPTY = "Can't call broadcast because Signature is empty. Make sure of calling sign before calling broadcast."; + + /** + * Error message get thrown if {@link TransactionProcessor#getSignatures()} is empty during process of {@link TransactionProcessor#signAndBroadcast()} ()} + */ + public static final String TRANSACTION_PROCESSOR_SIGN_BROADCAST_SIGN_EMPTY = "Can't call sign and broadcast because Signature is empty. Make sure of calling sign before calling sign and broadcast."; + +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/abiProvider/AbiProviderError.java b/app/src/main/java/com/tangem/wallet/eos/error/abiProvider/AbiProviderError.java new file mode 100644 index 0000000000..2741d8ec18 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/abiProvider/AbiProviderError.java @@ -0,0 +1,28 @@ +package com.tangem.wallet.eos.error.abiProvider; + +import com.tangem.wallet.eos.error.EosioError; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call any method in an + * AbiProvider implementation. + */ +public class AbiProviderError extends EosioError { + + public AbiProviderError() { + } + + public AbiProviderError(@NotNull String message) { + super(message); + } + + public AbiProviderError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public AbiProviderError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/abiProvider/GetAbiError.java b/app/src/main/java/com/tangem/wallet/eos/error/abiProvider/GetAbiError.java new file mode 100644 index 0000000000..9e57386d1a --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/abiProvider/GetAbiError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.abiProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call the GetAbi or GetAbis methods + * of IABIProvider {@link one.block.eosiojava.interfaces.IABIProvider}. + */ +public class GetAbiError extends AbiProviderError { + + public GetAbiError() { + } + + public GetAbiError(@NotNull String message) { + super(message); + } + + public GetAbiError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public GetAbiError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/abiProvider/package-info.java b/app/src/main/java/com/tangem/wallet/eos/error/abiProvider/package-info.java new file mode 100644 index 0000000000..c7d5a34767 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/abiProvider/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides the classes necessary for describe meaningful exceptions that occur during an ABI Provider implementation like: + * {@link one.block.eosiojava.error.abiProvider.GetAbiError} + */ + +package com.tangem.wallet.eos.error.abiProvider; \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/eos/error/package-info.java b/app/src/main/java/com/tangem/wallet/eos/error/package-info.java new file mode 100644 index 0000000000..71a5584dc5 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/package-info.java @@ -0,0 +1,8 @@ +/** + * Provides the classes/constants necessary to describe meaningful exceptions that occur in all processes + * of eosio-java like: + * {@link one.block.eosiojava.session.TransactionProcessor} transaction processing flow, + * {@link one.block.eosiojava.utilities.EOSFormatter} utilities and other processes. + */ + +package com.tangem.wallet.eos.error; \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/GetBlockRpcError.java b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/GetBlockRpcError.java new file mode 100644 index 0000000000..a023fa7ee1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/GetBlockRpcError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.rpcProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to use the RPC call, getBlock(). + */ +public class GetBlockRpcError extends RpcProviderError { + + public GetBlockRpcError() { + } + + public GetBlockRpcError(@NotNull String message) { + super(message); + } + + public GetBlockRpcError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public GetBlockRpcError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/GetInfoRpcError.java b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/GetInfoRpcError.java new file mode 100644 index 0000000000..a9a4dbb649 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/GetInfoRpcError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.rpcProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to use the RPC call, getInfo(). + */ +public class GetInfoRpcError extends RpcProviderError { + + public GetInfoRpcError() { + } + + public GetInfoRpcError(@NotNull String message) { + super(message); + } + + public GetInfoRpcError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public GetInfoRpcError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/GetRawAbiRpcError.java b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/GetRawAbiRpcError.java new file mode 100644 index 0000000000..40968bea68 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/GetRawAbiRpcError.java @@ -0,0 +1,27 @@ +package com.tangem.wallet.eos.error.rpcProvider; + +import com.tangem.wallet.eos.error.EosioError; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to use the RPC call, getRawAbi(). + */ +public class GetRawAbiRpcError extends EosioError { + + public GetRawAbiRpcError() { + } + + public GetRawAbiRpcError(@NotNull String message) { + super(message); + } + + public GetRawAbiRpcError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public GetRawAbiRpcError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/GetRequiredKeysRpcError.java b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/GetRequiredKeysRpcError.java new file mode 100644 index 0000000000..9312111531 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/GetRequiredKeysRpcError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.rpcProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to use the RPC call, + * getRequiredKeys(). + */ +public class GetRequiredKeysRpcError extends RpcProviderError { + + public GetRequiredKeysRpcError() { + } + + public GetRequiredKeysRpcError(@NotNull String message) { + super(message); + } + + public GetRequiredKeysRpcError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public GetRequiredKeysRpcError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/PushTransactionRpcError.java b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/PushTransactionRpcError.java new file mode 100644 index 0000000000..89e28da069 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/PushTransactionRpcError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.rpcProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to use the RPC call, + * pushTransaction(). + */ +public class PushTransactionRpcError extends RpcProviderError { + + public PushTransactionRpcError() { + } + + public PushTransactionRpcError(@NotNull String message) { + super(message); + } + + public PushTransactionRpcError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public PushTransactionRpcError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/RpcProviderError.java b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/RpcProviderError.java new file mode 100644 index 0000000000..8161dce389 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/RpcProviderError.java @@ -0,0 +1,29 @@ +package com.tangem.wallet.eos.error.rpcProvider; + +import com.tangem.wallet.eos.error.EosioError; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to use any RPC call. + *
+ * Any exception class which is used in an RPC Provider should extend this Error class. + */ +public class RpcProviderError extends EosioError { + + public RpcProviderError() { + } + + public RpcProviderError(@NotNull String message) { + super(message); + } + + public RpcProviderError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public RpcProviderError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/package-info.java b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/package-info.java new file mode 100644 index 0000000000..060425089f --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/rpcProvider/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides the classes necessary for describe meaningful exceptions that occur during an PRC Provider implementation like: + * {@link one.block.eosiojava.error.rpcProvider.GetInfoRpcError} + */ + +package com.tangem.wallet.eos.error.rpcProvider; \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/DeserializeAbiError.java b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/DeserializeAbiError.java new file mode 100644 index 0000000000..16da4ca3cb --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/DeserializeAbiError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.serializationProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call deserializeAbi() of + * Serialization Provider + */ +public class DeserializeAbiError extends SerializationProviderError { + + public DeserializeAbiError() { + } + + public DeserializeAbiError(@NotNull String message) { + super(message); + } + + public DeserializeAbiError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public DeserializeAbiError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/DeserializeError.java b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/DeserializeError.java new file mode 100644 index 0000000000..7ac79b8fcd --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/DeserializeError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.serializationProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call deserialize() of + * Serialization Provider + */ +public class DeserializeError extends SerializationProviderError { + + public DeserializeError() { + } + + public DeserializeError(@NotNull String message) { + super(message); + } + + public DeserializeError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public DeserializeError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/DeserializeTransactionError.java b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/DeserializeTransactionError.java new file mode 100644 index 0000000000..f91b25e9bf --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/DeserializeTransactionError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.serializationProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call deserializeTransaction() + * of Serialization Provider + */ +public class DeserializeTransactionError extends SerializationProviderError { + + public DeserializeTransactionError() { + } + + public DeserializeTransactionError(@NotNull String message) { + super(message); + } + + public DeserializeTransactionError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public DeserializeTransactionError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/SerializationProviderError.java b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/SerializationProviderError.java new file mode 100644 index 0000000000..0aea723946 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/SerializationProviderError.java @@ -0,0 +1,29 @@ +package com.tangem.wallet.eos.error.serializationProvider; + +import com.tangem.wallet.eos.error.EosioError; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call any method of Serialization Provider. + *
+ * Any exception class which is used for Serialization Provider should extend this Error class. + */ +public class SerializationProviderError extends EosioError { + + public SerializationProviderError() { + } + + public SerializationProviderError(@NotNull String message) { + super(message); + } + + public SerializationProviderError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public SerializationProviderError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/SerializeAbiError.java b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/SerializeAbiError.java new file mode 100644 index 0000000000..0e970e03f1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/SerializeAbiError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.serializationProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call serializeAbi() + * of Serialization Provider + */ +public class SerializeAbiError extends SerializationProviderError { + + public SerializeAbiError() { + } + + public SerializeAbiError(@NotNull String message) { + super(message); + } + + public SerializeAbiError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public SerializeAbiError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/SerializeError.java b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/SerializeError.java new file mode 100644 index 0000000000..db449bd11e --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/SerializeError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.serializationProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call serialize() + * of Serialization Provider + */ +public class SerializeError extends SerializationProviderError { + + public SerializeError() { + } + + public SerializeError(@NotNull String message) { + super(message); + } + + public SerializeError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public SerializeError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/SerializeTransactionError.java b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/SerializeTransactionError.java new file mode 100644 index 0000000000..309562d28a --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/SerializeTransactionError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.serializationProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call serializeTransaction() of + * Serialization Provider + */ +public class SerializeTransactionError extends SerializationProviderError { + + public SerializeTransactionError() { + } + + public SerializeTransactionError(@NotNull String message) { + super(message); + } + + public SerializeTransactionError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public SerializeTransactionError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/package-info.java b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/package-info.java new file mode 100644 index 0000000000..9e5b8ae3d2 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/serializationProvider/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides the classes necessary to describe meaningful exceptions that occur during a Serialization Provider implementation like: + * {@link one.block.eosiojava.error.serializationProvider.SerializeTransactionError} + */ + +package com.tangem.wallet.eos.error.serializationProvider; \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionBroadCastEmptySignatureError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionBroadCastEmptySignatureError.java new file mode 100644 index 0000000000..e498137192 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionBroadCastEmptySignatureError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error would be thrown from TransactionProcessor#BroadCast if signatures is empty + */ +public class TransactionBroadCastEmptySignatureError extends TransactionBroadCastError { + + public TransactionBroadCastEmptySignatureError() { + } + + public TransactionBroadCastEmptySignatureError(@NotNull String message) { + super(message); + } + + public TransactionBroadCastEmptySignatureError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionBroadCastEmptySignatureError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionBroadCastError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionBroadCastError.java new file mode 100644 index 0000000000..710741e475 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionBroadCastError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call broadCast() of TransactionProcessor + */ +public class TransactionBroadCastError extends TransactionProcessorError { + + public TransactionBroadCastError() { + } + + public TransactionBroadCastError(@NotNull String message) { + super(message); + } + + public TransactionBroadCastError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionBroadCastError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestAbiError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestAbiError.java new file mode 100644 index 0000000000..2b390a5c70 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestAbiError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call getAbi() inside + * createSignature() of TransactionProcessor + */ +public class TransactionCreateSignatureRequestAbiError extends TransactionCreateSignatureRequestError { + + public TransactionCreateSignatureRequestAbiError() { + } + + public TransactionCreateSignatureRequestAbiError(@NotNull String message) { + super(message); + } + + public TransactionCreateSignatureRequestAbiError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionCreateSignatureRequestAbiError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestEmptyAvailableKeyError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestEmptyAvailableKeyError.java new file mode 100644 index 0000000000..dd1dea4382 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestEmptyAvailableKeyError.java @@ -0,0 +1,28 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call getAvailableKeys() + * inside createSignatureRequest() of TransactionProcessor. + *
+ * Gets thrown when the result of GetAvailableKeys() is empty. + */ +public class TransactionCreateSignatureRequestEmptyAvailableKeyError extends TransactionCreateSignatureRequestError { + + public TransactionCreateSignatureRequestEmptyAvailableKeyError() { + } + + public TransactionCreateSignatureRequestEmptyAvailableKeyError(@NotNull String message) { + super(message); + } + + public TransactionCreateSignatureRequestEmptyAvailableKeyError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionCreateSignatureRequestEmptyAvailableKeyError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestError.java new file mode 100644 index 0000000000..1d658dafc3 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call any method related to the + * signing process inside getSignature() of TransactionProcessor. + */ +public class TransactionCreateSignatureRequestError extends TransactionProcessorError { + + public TransactionCreateSignatureRequestError() { + } + + public TransactionCreateSignatureRequestError(@NotNull String message) { + super(message); + } + + public TransactionCreateSignatureRequestError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionCreateSignatureRequestError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestKeyError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestKeyError.java new file mode 100644 index 0000000000..3a8871ad19 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestKeyError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call getAvailableKeys() + * inside createSignatureRequest() of TransactionProcessor + */ +public class TransactionCreateSignatureRequestKeyError extends TransactionCreateSignatureRequestError { + + public TransactionCreateSignatureRequestKeyError() { + } + + public TransactionCreateSignatureRequestKeyError(@NotNull String message) { + super(message); + } + + public TransactionCreateSignatureRequestKeyError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionCreateSignatureRequestKeyError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestRequiredKeysEmptyError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestRequiredKeysEmptyError.java new file mode 100644 index 0000000000..886a7b75d1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestRequiredKeysEmptyError.java @@ -0,0 +1,28 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call getRequiredKeys() + * inside createSignatureRequest() of TransactionProcessor. + *
+ * Gets thrown if GetRequiredKeys() returns an empty list. + */ +public class TransactionCreateSignatureRequestRequiredKeysEmptyError extends TransactionCreateSignatureRequestError { + + public TransactionCreateSignatureRequestRequiredKeysEmptyError() { + } + + public TransactionCreateSignatureRequestRequiredKeysEmptyError(@NotNull String message) { + super(message); + } + + public TransactionCreateSignatureRequestRequiredKeysEmptyError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionCreateSignatureRequestRequiredKeysEmptyError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestRequiredKeysError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestRequiredKeysError.java new file mode 100644 index 0000000000..43efea0865 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestRequiredKeysError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call getRequiredKeys() inside + * createSignatureRequest() of TransactionProcessor + */ +public class TransactionCreateSignatureRequestRequiredKeysError extends TransactionCreateSignatureRequestError { + + public TransactionCreateSignatureRequestRequiredKeysError() { + } + + public TransactionCreateSignatureRequestRequiredKeysError(@NotNull String message) { + super(message); + } + + public TransactionCreateSignatureRequestRequiredKeysError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionCreateSignatureRequestRequiredKeysError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestRpcError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestRpcError.java new file mode 100644 index 0000000000..b8a98c204d --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestRpcError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call any RPC call inside + * createSignatureRequest() of TransactionProcessor + */ +public class TransactionCreateSignatureRequestRpcError extends TransactionCreateSignatureRequestError { + + public TransactionCreateSignatureRequestRpcError() { + } + + public TransactionCreateSignatureRequestRpcError(@NotNull String message) { + super(message); + } + + public TransactionCreateSignatureRequestRpcError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionCreateSignatureRequestRpcError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestSerializationError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestSerializationError.java new file mode 100644 index 0000000000..72da3f6e57 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionCreateSignatureRequestSerializationError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call Serialization method + * inside createSignatureRequest() of TransactionProcessor + */ +public class TransactionCreateSignatureRequestSerializationError extends TransactionCreateSignatureRequestError { + + public TransactionCreateSignatureRequestSerializationError() { + } + + public TransactionCreateSignatureRequestSerializationError(@NotNull String message) { + super(message); + } + + public TransactionCreateSignatureRequestSerializationError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionCreateSignatureRequestSerializationError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionGetSignatureDeserializationError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionGetSignatureDeserializationError.java new file mode 100644 index 0000000000..496852c9c1 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionGetSignatureDeserializationError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call any deserialization + * method inside createSignatureRequest() of TransactionProcessor + */ +public class TransactionGetSignatureDeserializationError extends TransactionGetSignatureError { + + public TransactionGetSignatureDeserializationError() { + } + + public TransactionGetSignatureDeserializationError(@NotNull String message) { + super(message); + } + + public TransactionGetSignatureDeserializationError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionGetSignatureDeserializationError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionGetSignatureError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionGetSignatureError.java new file mode 100644 index 0000000000..8ff030b6cd --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionGetSignatureError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call getSignature() of TransactionProcessor + */ +public class TransactionGetSignatureError extends TransactionProcessorError { + + public TransactionGetSignatureError() { + } + + public TransactionGetSignatureError(@NotNull String message) { + super(message); + } + + public TransactionGetSignatureError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionGetSignatureError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionGetSignatureNotAllowModifyTransactionError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionGetSignatureNotAllowModifyTransactionError.java new file mode 100644 index 0000000000..401c0fee45 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionGetSignatureNotAllowModifyTransactionError.java @@ -0,0 +1,27 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call getSignature() inside TransactionProcessor. + *
+ * Gets thrown when Signature provider modifies a transaction but TransactionProcessor is not set to allow that. + */ +public class TransactionGetSignatureNotAllowModifyTransactionError extends TransactionGetSignatureError { + + public TransactionGetSignatureNotAllowModifyTransactionError() { + } + + public TransactionGetSignatureNotAllowModifyTransactionError(@NotNull String message) { + super(message); + } + + public TransactionGetSignatureNotAllowModifyTransactionError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionGetSignatureNotAllowModifyTransactionError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionGetSignatureSigningError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionGetSignatureSigningError.java new file mode 100644 index 0000000000..c077ce36ec --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionGetSignatureSigningError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call getSignature() of TransactionProcessor + */ +public class TransactionGetSignatureSigningError extends TransactionGetSignatureError { + + public TransactionGetSignatureSigningError() { + } + + public TransactionGetSignatureSigningError(@NotNull String message) { + super(message); + } + + public TransactionGetSignatureSigningError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionGetSignatureSigningError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionPrepareError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionPrepareError.java new file mode 100644 index 0000000000..7e9f0de9dd --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionPrepareError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call prepare() of TransactionProcessor + */ +public class TransactionPrepareError extends TransactionProcessorError { + + public TransactionPrepareError() { + } + + public TransactionPrepareError(@NotNull String message) { + super(message); + } + + public TransactionPrepareError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionPrepareError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionPrepareInputError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionPrepareInputError.java new file mode 100644 index 0000000000..e97f451a24 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionPrepareInputError.java @@ -0,0 +1,27 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call prepare() inside TransactionProcessor. + *
+ * Gets thrown if input for Prepare() is invalid. + */ +public class TransactionPrepareInputError extends TransactionPrepareError { + + public TransactionPrepareInputError() { + } + + public TransactionPrepareInputError(@NotNull String message) { + super(message); + } + + public TransactionPrepareInputError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionPrepareInputError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionPrepareRpcError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionPrepareRpcError.java new file mode 100644 index 0000000000..9c3dd1a508 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionPrepareRpcError.java @@ -0,0 +1,26 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting make any RPC calls inside + * prepare() of TransactionProcessor + */ +public class TransactionPrepareRpcError extends TransactionPrepareError { + + public TransactionPrepareRpcError() { + } + + public TransactionPrepareRpcError(@NotNull String message) { + super(message); + } + + public TransactionPrepareRpcError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionPrepareRpcError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionProcessorConstructorInputError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionProcessorConstructorInputError.java new file mode 100644 index 0000000000..2915bb7437 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionProcessorConstructorInputError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to initialize TransactionProcessor + */ +public class TransactionProcessorConstructorInputError extends TransactionProcessorError { + + public TransactionProcessorConstructorInputError() { + } + + public TransactionProcessorConstructorInputError(@NotNull String message) { + super(message); + } + + public TransactionProcessorConstructorInputError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionProcessorConstructorInputError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionProcessorError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionProcessorError.java new file mode 100644 index 0000000000..d96900a745 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionProcessorError.java @@ -0,0 +1,27 @@ +package com.tangem.wallet.eos.error.session; + +import com.tangem.wallet.eos.error.EosioError; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call any method of TransactionProcessor + */ +public class TransactionProcessorError extends EosioError { + + public TransactionProcessorError() { + } + + public TransactionProcessorError(@NotNull String message) { + super(message); + } + + public TransactionProcessorError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionProcessorError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionPushTransactionError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionPushTransactionError.java new file mode 100644 index 0000000000..e6d0b85a51 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionPushTransactionError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call pushTransaction() of TransactionProcessor + */ +public class TransactionPushTransactionError extends TransactionProcessorError { + + public TransactionPushTransactionError() { + } + + public TransactionPushTransactionError(@NotNull String message) { + super(message); + } + + public TransactionPushTransactionError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionPushTransactionError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionSerializeError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionSerializeError.java new file mode 100644 index 0000000000..5f6800d6ee --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionSerializeError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call serialize() of TransactionProcessor + */ +public class TransactionSerializeError extends TransactionProcessorError { + + public TransactionSerializeError() { + } + + public TransactionSerializeError(@NotNull String message) { + super(message); + } + + public TransactionSerializeError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionSerializeError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionSignAndBroadCastError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionSignAndBroadCastError.java new file mode 100644 index 0000000000..4106df0e5b --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionSignAndBroadCastError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call signAndBroadCast() of TransactionProcessor + */ +public class TransactionSignAndBroadCastError extends TransactionProcessorError { + + public TransactionSignAndBroadCastError() { + } + + public TransactionSignAndBroadCastError(@NotNull String message) { + super(message); + } + + public TransactionSignAndBroadCastError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionSignAndBroadCastError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionSignError.java b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionSignError.java new file mode 100644 index 0000000000..c716d54253 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/TransactionSignError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.session; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call sign() of TransactionProcessor + */ +public class TransactionSignError extends TransactionProcessorError { + + public TransactionSignError() { + } + + public TransactionSignError(@NotNull String message) { + super(message); + } + + public TransactionSignError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public TransactionSignError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/session/package-info.java b/app/src/main/java/com/tangem/wallet/eos/error/session/package-info.java new file mode 100644 index 0000000000..07368c667b --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/session/package-info.java @@ -0,0 +1,7 @@ +/** + * Provides the classes necessary to describe meaningful exceptions that occur during {@link + * one.block.eosiojava.session.TransactionProcessor} and {@link one.block.eosiojava.session.TransactionSession} + * implementations like: {@link one.block.eosiojava.error.session.TransactionGetSignatureError} + */ + +package com.tangem.wallet.eos.error.session; \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/eos/error/signatureProvider/GetAvailableKeysError.java b/app/src/main/java/com/tangem/wallet/eos/error/signatureProvider/GetAvailableKeysError.java new file mode 100644 index 0000000000..e1eb17dad4 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/signatureProvider/GetAvailableKeysError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.signatureProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call getAvailableKeys() of SignatureProvider + */ +public class GetAvailableKeysError extends SignatureProviderError { + + public GetAvailableKeysError() { + } + + public GetAvailableKeysError(@NotNull String message) { + super(message); + } + + public GetAvailableKeysError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public GetAvailableKeysError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/signatureProvider/SignTransactionError.java b/app/src/main/java/com/tangem/wallet/eos/error/signatureProvider/SignTransactionError.java new file mode 100644 index 0000000000..fc3b0d6a78 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/signatureProvider/SignTransactionError.java @@ -0,0 +1,25 @@ +package com.tangem.wallet.eos.error.signatureProvider; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call signTransaction() of SignatureProvider + */ +public class SignTransactionError extends SignatureProviderError { + + public SignTransactionError() { + } + + public SignTransactionError(@NotNull String message) { + super(message); + } + + public SignTransactionError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public SignTransactionError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/signatureProvider/SignatureProviderError.java b/app/src/main/java/com/tangem/wallet/eos/error/signatureProvider/SignatureProviderError.java new file mode 100644 index 0000000000..a366060331 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/signatureProvider/SignatureProviderError.java @@ -0,0 +1,27 @@ +package com.tangem.wallet.eos.error.signatureProvider; + +import com.tangem.wallet.eos.error.EosioError; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call any method of SignatureProvider + */ +public class SignatureProviderError extends EosioError { + + public SignatureProviderError() { + } + + public SignatureProviderError(@NotNull String message) { + super(message); + } + + public SignatureProviderError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public SignatureProviderError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/signatureProvider/package-info.java b/app/src/main/java/com/tangem/wallet/eos/error/signatureProvider/package-info.java new file mode 100644 index 0000000000..7502dddd4c --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/signatureProvider/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides the classes necessary to describe meaningful exceptions that occur during a signature + * provider implementation like {@link one.block.eosiojava.error.signatureProvider.SignTransactionError} + */ + +package com.tangem.wallet.eos.error.signatureProvider; \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/eos/error/utilities/Base58ManipulationError.java b/app/src/main/java/com/tangem/wallet/eos/error/utilities/Base58ManipulationError.java new file mode 100644 index 0000000000..3f77b079a4 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/utilities/Base58ManipulationError.java @@ -0,0 +1,27 @@ +package com.tangem.wallet.eos.error.utilities; + +import com.tangem.wallet.eos.error.EosioError; + +import org.jetbrains.annotations.NotNull; + +/** + * Error is thrown for exceptions that occur during Base58 + * encoding or decoding operations. + */ +public class Base58ManipulationError extends EosioError { + public Base58ManipulationError() { + } + + public Base58ManipulationError(@NotNull String message) { + super(message); + } + + public Base58ManipulationError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public Base58ManipulationError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/utilities/DerToPemConversionError.java b/app/src/main/java/com/tangem/wallet/eos/error/utilities/DerToPemConversionError.java new file mode 100644 index 0000000000..e5f3cf2c58 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/utilities/DerToPemConversionError.java @@ -0,0 +1,28 @@ +package com.tangem.wallet.eos.error.utilities; + +import com.tangem.wallet.eos.error.EosioError; + +import org.jetbrains.annotations.NotNull; + +/** + * Error is thrown for exceptions involving conversions of keys + * or signatures from DER encoded format to PEM. + */ +public class DerToPemConversionError extends EosioError { + public DerToPemConversionError() { + } + + public DerToPemConversionError(@NotNull String message) { + super(message); + } + + public DerToPemConversionError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public DerToPemConversionError(@NotNull Exception exception) { + super(exception); + } + +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/utilities/EOSFormatterError.java b/app/src/main/java/com/tangem/wallet/eos/error/utilities/EOSFormatterError.java new file mode 100644 index 0000000000..25a4ba660a --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/utilities/EOSFormatterError.java @@ -0,0 +1,29 @@ + + +package com.tangem.wallet.eos.error.utilities; + +import com.tangem.wallet.eos.error.EosioError; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to call any method of EOSFormatter + */ +public class EOSFormatterError extends EosioError { + + public EOSFormatterError() { + } + + public EOSFormatterError(@NotNull String message) { + super(message); + } + + public EOSFormatterError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public EOSFormatterError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/utilities/EosFormatterSignatureIsNotCanonicalError.java b/app/src/main/java/com/tangem/wallet/eos/error/utilities/EosFormatterSignatureIsNotCanonicalError.java new file mode 100644 index 0000000000..3db703684d --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/utilities/EosFormatterSignatureIsNotCanonicalError.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017-2019 block.one all rights reserved. + */ + +package com.tangem.wallet.eos.error.utilities; + +import org.jetbrains.annotations.NotNull; + +/** + * Error class is used when there is an exception while attempting to convert a + * signature to EOS format and the signature is not canonical. + *
+ * * This exception only happens with signatures signed by a key generated by the SECP256K1 + * algorithm. + *
+ * * The signature must be recreated and tested to pass this exception. + */ +public class EosFormatterSignatureIsNotCanonicalError extends EOSFormatterError { + public EosFormatterSignatureIsNotCanonicalError() { + } + + public EosFormatterSignatureIsNotCanonicalError(@NotNull String message) { + super(message); + } + + public EosFormatterSignatureIsNotCanonicalError(@NotNull String message, @NotNull Exception exception) { + super(message, exception); + } + + public EosFormatterSignatureIsNotCanonicalError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/utilities/LowSVerificationError.java b/app/src/main/java/com/tangem/wallet/eos/error/utilities/LowSVerificationError.java new file mode 100644 index 0000000000..9da11c249d --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/utilities/LowSVerificationError.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017-2019 block.one all rights reserved. + */ + +package com.tangem.wallet.eos.error.utilities; + +import org.jetbrains.annotations.NotNull; + +/** + * Error thrown when exception occurs during signature manipulations. Specifically, this + * error indicates that a failure occurred while verifying whether the value of S was low. + */ +public class LowSVerificationError extends EOSFormatterError { + + public LowSVerificationError() { + } + + public LowSVerificationError(@NotNull String message) { + super(message); + } + + public LowSVerificationError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public LowSVerificationError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/utilities/PEMProcessorError.java b/app/src/main/java/com/tangem/wallet/eos/error/utilities/PEMProcessorError.java new file mode 100644 index 0000000000..d895837089 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/utilities/PEMProcessorError.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2017-2019 block.one all rights reserved. + */ + +package com.tangem.wallet.eos.error.utilities; + +import com.tangem.wallet.eos.error.EosioError; + +import org.jetbrains.annotations.NotNull; + +/** + * Error that originates from the {@link one.block.eosiojava.utilities.PEMProcessor} class. + */ +public class PEMProcessorError extends EosioError { + + public PEMProcessorError() { + } + + public PEMProcessorError(@NotNull String message) { + super(message); + } + + public PEMProcessorError(@NotNull String message, + @NotNull Exception exception) { + super(message, exception); + } + + public PEMProcessorError(@NotNull Exception exception) { + super(exception); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/error/utilities/package-info.java b/app/src/main/java/com/tangem/wallet/eos/error/utilities/package-info.java new file mode 100644 index 0000000000..1fb7512a69 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/error/utilities/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides the classes necessary to describe meaningful exceptions that occur while using + * eosio-java utilities. like {@link one.block.eosiojava.error.utilities.PEMProcessorError} + */ + +package com.tangem.wallet.eos.error.utilities; \ No newline at end of file diff --git a/app/src/main/java/com/tangem/wallet/eos/utilities/ByteFormatter.java b/app/src/main/java/com/tangem/wallet/eos/utilities/ByteFormatter.java new file mode 100644 index 0000000000..345b8b45ea --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/utilities/ByteFormatter.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2017-2019 block.one all rights reserved. + */ + +package com.tangem.wallet.eos.utilities; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Strings; +import org.bitcoinj.core.Sha256Hash; +import org.bouncycastle.util.encoders.Base64; +import org.bouncycastle.util.encoders.Hex; +import org.jetbrains.annotations.NotNull; + +/** + * This class provides methods for transforming and formatting byte data to and from different + * formats in use on the blockchain. + */ +public class ByteFormatter { + + private static final int BASE64_PADDING = 4; + private static final char BASE64_PADDING_CHAR = '='; + + @NotNull + private byte[] context; + + public ByteFormatter(@NotNull byte[] context) { + this.context = context; + } + + /** + * Create and initialize a ByteFormatter from a Base64 encoded string. The Base64 string + * will have its padding checked and adjusted if necessary. + * + * @param base64String - Base64 encoded string. + * @return - Initialized ByteFormatter + */ + public static ByteFormatter createFromBase64(@NotNull String base64String) { + // Base64 encoded strings must be an even multiple of 4 if they are handled with padding. + // The strings that we get back from the blockchain in the JSON do not follow this + // strictly so we have to adjust the string if necessary before decoding. The padding + // character is '='. So we remove all existing padding characters and then pad the + // string to the nearest multiple of 4. + String trimmed = CharMatcher.is(BASE64_PADDING_CHAR).removeFrom(base64String); + String padded = Strings.padEnd(trimmed, + (trimmed.length() + BASE64_PADDING - 1) / BASE64_PADDING * BASE64_PADDING, + BASE64_PADDING_CHAR); + return new ByteFormatter(Base64.decode(padded)); + } + + /** + * Create and initialize a ByteFormatter from a hex encoded string. + * + * @param hexString - Hex encoded string. + * @return - Initialized ByteFormatter + */ + public static ByteFormatter createFromHex(@NotNull String hexString) { + byte[] data = Hex.decode(hexString); + return new ByteFormatter(data); + } + + /** + * Convert the current ByteFormatter contents to a Hex encoded string and return it. + * @return - Hex encoded string representation of the current formatter context. + */ + public String toHex() { + return Hex.toHexString(this.context); + } + + /** + * Calculate the sha256 hash of the current ByteFormatter context and return it as a new + * ByteFormatter. + * + * @return - New ByteFormatter containing the sha256 hash of the current one. + */ + public ByteFormatter sha256() { + return new ByteFormatter(Sha256Hash.hash(this.context)); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/utilities/DateFormatter.java b/app/src/main/java/com/tangem/wallet/eos/utilities/DateFormatter.java new file mode 100644 index 0000000000..8ecb4f6ad6 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/utilities/DateFormatter.java @@ -0,0 +1,76 @@ +package com.tangem.wallet.eos.utilities; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * This class provides utility methods to handle the formatting of dates and times to supported patterns. + */ +public class DateFormatter { + + /** + * Blockchain pattern for SimpleDateFormat + */ + public static final String BACKEND_DATE_PATTERN = "yyyy-MM-dd'T'kk:mm:ss.SSS"; + + /** + * Blockchain pattern for SimpleDateFormat. It includes timezone. + */ + public static final String BACKEND_DATE_PATTERN_WITH_TIMEZONE = "yyyy-MM-dd'T'kk:mm:ss.SSS zzz"; + + /** + * Blockchain timezone/time standard for SimpleDateFormat + */ + public static final String BACKEND_DATE_TIME_ZONE = "UTC"; + + private DateFormatter() {} + + /** + * Converting backend time to millisecond. + *

+ * Backend time pattern "yyyy-MM-dd'T'HH:mm:ss.sss" in GMT. + * @param backendTime input backend time. + * @return Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by parsed input backend time. + * @throws ParseException thrown if the input does not match with any supported datetime pattern. + */ + public static long convertBackendTimeToMilli(String backendTime) throws ParseException { + String[] datePatterns = new String[]{ + BACKEND_DATE_PATTERN, BACKEND_DATE_PATTERN_WITH_TIMEZONE + }; + + for (String datePattern : datePatterns) { + try { + SimpleDateFormat sdf = new SimpleDateFormat(datePattern); + sdf.setTimeZone(TimeZone.getTimeZone(BACKEND_DATE_TIME_ZONE)); + Date parsedDate = sdf.parse(backendTime); + return parsedDate.getTime(); + } catch (ParseException ex) { + // Keep going even if exception is thrown for trying different date pattern + } catch (IllegalArgumentException ex) { + // Keep going even if exception is thrown for trying different date pattern + } + } + + throw new ParseException("Unable to parse input backend time with supported date patterns!", 0); + } + + /** + * Convert milliseconds to time string format used on blockchain. + *

+ * Backend time pattern "yyyy-MM-dd'T'HH:mm:ss.sss" in GMT + * @param timeInMilliSeconds input number of milliseconds + * @return String format of input number of milliseconds + */ + public static String convertMilliSecondToBackendTimeString(long timeInMilliSeconds) { + SimpleDateFormat sdf = new SimpleDateFormat(BACKEND_DATE_PATTERN); + sdf.setTimeZone(TimeZone.getTimeZone(BACKEND_DATE_TIME_ZONE)); + + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(timeInMilliSeconds); + + return sdf.format(calendar.getTime()); + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/utilities/EOSFormatter.java b/app/src/main/java/com/tangem/wallet/eos/utilities/EOSFormatter.java new file mode 100644 index 0000000000..cc1d584145 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/utilities/EOSFormatter.java @@ -0,0 +1,1536 @@ + + +package com.tangem.wallet.eos.utilities; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.primitives.Bytes; +import java.io.CharArrayReader; +import java.io.Reader; +import java.math.BigInteger; +import java.util.Arrays; +import com.tangem.wallet.eos.enums.AlgorithmEmployed; +import com.tangem.wallet.eos.error.ErrorConstants; +import com.tangem.wallet.eos.error.utilities.*; +import org.bitcoinj.core.Base58; +import org.bitcoinj.core.Sha256Hash; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.sec.SECNamedCurves; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.asn1.x9.X9IntegerConverter; +import org.bouncycastle.crypto.digests.RIPEMD160Digest; +import org.bouncycastle.crypto.ec.CustomNamedCurves; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.math.ec.ECAlgorithms; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.math.ec.FixedPointUtil; +import org.bouncycastle.util.encoders.Base64; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import org.jetbrains.annotations.NotNull; + +/** + * This class provides a number of helper methods that can be used to convert certain objects to and + * from formats that are germane to EOS blockchain transactions and the PEM (Privacy Enhanced Mail) + * format for those objects. It also provides methods to format a serialized transaction into a + * format that can be submitted to an EOS blockchain. + */ +public class EOSFormatter { + + /* + EOS Format Prefixes - The prefixes below are all used to preface the EOS format of certain types + of keys and signatures. For instance, 'EOS' is used to preface a legacy form of a public key + that was generated using the secp256k1 algorithm. The prefixes and there associated objects are + as follows: + EOS - Public Key generated with secp256k1 algorithm formatted for use on EOS blockchain. + PUB_R1_ - Public Key generated with secp256r1 or prime256v1 algorithm formatted for use on EOS blockchain. + PUB_K1_ - Public Key generated with secp256k1 algorithm formatted for use on EOS blockchain. + PVT_R1_ - Private Key generated with secp256r1 algorithm formatted for use on EOS blockchain. + SIG_R1_ - Signature signed with key generated with secp256r1 algorithm. + SIG_K1_ - Signature signed with key generated with secp256k1 algorithm. + */ + private static final String PATTERN_STRING_EOS_PREFIX_EOS = "EOS"; + private static final String PATTERN_STRING_EOS_PREFIX_PUB_R1 = "PUB_R1_"; + private static final String PATTERN_STRING_EOS_PREFIX_PUB_K1 = "PUB_K1_"; + private static final String PATTERN_STRING_EOS_PREFIX_PVT_R1 = "PVT_R1_"; + private static final String PATTERN_STRING_EOS_PREFIX_SIG_R1 = "SIG_R1_"; + private static final String PATTERN_STRING_EOS_PREFIX_SIG_K1 = "SIG_K1_"; + + //PEM FORMAT PREFIXES + private static final String PATTERN_STRING_PEM_PREFIX_PRIVATE_KEY_SECP256R1 = "30770201010420"; + private static final String PATTERN_STRING_PEM_PREFIX_PRIVATE_KEY_SECP256K1 = "302E0201010420"; + private static final String PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256R1_UNCOMPRESSED = "3059301306072a8648ce3d020106082a8648ce3d030107034200"; + private static final String PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256K1_UNCOMPRESSED = "3056301006072a8648ce3d020106052b8104000a034200"; + private static final String PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256R1_COMPRESSED = "3039301306072a8648ce3d020106082a8648ce3d030107032200"; + private static final String PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256K1_COMPRESSED = "3036301006072a8648ce3d020106052b8104000a032200"; + + //PEM FORMAT SUFFIXES + private static final String PATTERN_STRING_PEM_SUFFIX_PRIVATE_KEY_SECP256K1 = "A00706052B8104000A"; + private static final String PATTERN_STRING_PEM_SUFFIX_PRIVATE_KEY_SECP256R1 = "A00A06082A8648CE3D030107"; + + //PEM HEADERS & FOOTERS + private static final String PEM_HEADER_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----"; + private static final String PEM_FOOTER_PUBLIC_KEY = "-----END PUBLIC KEY-----"; + private static final String PEM_HEADER_PRIVATE_KEY = "-----BEGIN EC PRIVATE KEY-----"; + private static final String PEM_FOOTER_PRIVATE_KEY = "-----END EC PRIVATE KEY-----"; + private static final String PEM_HEADER_EC_PRIVATE_KEY = "EC PRIVATE KEY"; + private static final String PEM_HEADER_EC_PUBLIC_KEY = "PUBLIC KEY"; + + //CHECKSUM RELATED + private static final String SECP256R1_AND_PRIME256V1_CHECKSUM_VALIDATION_SUFFIX = "R1"; + private static final String SECP256K1_CHECKSUM_VALIDATION_SUFFIX = "K1"; + private static final String LEGACY_CHECKSUM_VALIDATION_SUFFIX = ""; + + //CONSTANTS USED DURING DECODING AND CHECKSUM VALIDATION + private static final int STANDARD_KEY_LENGTH = 32; + private static final int CHECKSUM_BYTES = 4; + private static final int FIRST_TWO_BYTES_OF_KEY = 4; + private static final int DATA_SEQUENCE_LENGTH_BYTE_POSITION = 2; + + //CONSTANTS USED DURING EOS ENCODING + private static final int EOS_SECP256K1_HEADER_BYTE = 0x80; + + //CONSTANTS USED DURING EOS DECODING + private static final byte UNCOMPRESSED_PUBLIC_KEY_BYTE_INDICATOR = 0x04; + private static final byte COMPRESSED_PUBLIC_KEY_BYTE_INDICATOR_POSITIVE_Y = 0x02; + private static final byte COMPRESSED_PUBLIC_KEY_BYTE_INDICATOR_NEGATIVE_Y = 0x03; + + private static final int CHAIN_ID_LENGTH = 64; + /** + * Minimum length of signable transaction: Chain id length + 32 bytes of 0's length + 1 (minimum length for serialized transaction) + */ + private static final int MINIMUM_SIGNABLE_TRANSACTION_LENGTH = CHAIN_ID_LENGTH + 64 + 1; + + //SIGNATURE RELATED CONSTANTS + private static final int VALUE_TO_ADD_TO_SIGNATURE_HEADER = 31; + private static final int EXPECTED_R_OR_S_LENGTH = 32; + private static final int NUMBER_OF_POSSIBLE_PUBLIC_KEYS = 4; + + /* + Covers the PEM objects currently supported by this class (i.e. The class allows for PEM + formatting of public keys, private keys, and signatures). + */ + private enum PEMObjectType { + PUBLICKEY("PUBLIC KEY"), + PRIVATEKEY("PRIVATE KEY"), + SIGNATURE("SIGNATURE"); + + private String value; + + PEMObjectType(String value) { + this.value = value; + } + + public String getString() { + return value; + } + } + + /** + * Const name of secp256r1 curves + */ + private static final String SECP256_R1 = "secp256r1"; + + /** + * Const name of secp256k1 curves + */ + private static final String SECP256_K1 = "secp256k1"; + + /** + * EC domain parameters of R1 key + */ + private static final ECDomainParameters ecParamsR1; + + /** + * EC domain parameters of K1 key + */ + private static final ECDomainParameters ecParamsK1; + + /** + * EC parameters holder of R1 key type + */ + private static final X9ECParameters CURVE_PARAMS_R1 = CustomNamedCurves.getByName(SECP256_R1); + + /** + * EC parameters holder of K1 key type + */ + private static final X9ECParameters CURVE_PARAMS_K1 = CustomNamedCurves.getByName(SECP256_K1); + + /** + * EC holder of R1 key type + */ + private static final ECDomainParameters CURVE_R1; + + /** + * Half curve value of R1 key type (to calculate low S) + */ + private static final BigInteger HALF_CURVE_ORDER_R1; + + /** + * EC holder of K1 key type + */ + private static final ECDomainParameters CURVE_K1; + + /** + * Half curve value of K1 key type (to calculate low S) + */ + private static final BigInteger HALF_CURVE_ORDER_K1; + + + static { + X9ECParameters paramsR1 = SECNamedCurves.getByName(SECP256_R1); + ecParamsR1 = new ECDomainParameters(paramsR1.getCurve(), paramsR1.getG(), paramsR1.getN(), + paramsR1.getH()); + + X9ECParameters paramsK1 = SECNamedCurves.getByName(SECP256_K1); + ecParamsK1 = new ECDomainParameters(paramsK1.getCurve(), paramsK1.getG(), paramsK1.getN(), + paramsK1.getH()); + + // secp256r1 + FixedPointUtil.precompute(CURVE_PARAMS_R1.getG()); + CURVE_R1 = new ECDomainParameters( + CURVE_PARAMS_R1.getCurve(), + CURVE_PARAMS_R1.getG(), + CURVE_PARAMS_R1.getN(), + CURVE_PARAMS_R1.getH()); + HALF_CURVE_ORDER_R1 = CURVE_PARAMS_R1.getN().shiftRight(1); + + // secp256k1 + CURVE_K1 = new ECDomainParameters( + CURVE_PARAMS_K1.getCurve(), + CURVE_PARAMS_K1.getG(), + CURVE_PARAMS_K1.getN(), + CURVE_PARAMS_K1.getH()); + HALF_CURVE_ORDER_K1 = CURVE_PARAMS_K1.getN().shiftRight(1); + } + + /** + * This method converts a PEM formatted public key to the EOS format. + * + * @param publicKeyPEM Public key in the PEM format + * @param requireLegacyFormOfSecp256k1Key - If the developer prefers a legacy version of a + * secp256k1 key that uses a "EOS" prefix. + * @return EOS formatted public key as string + * @throws EOSFormatterError if PEM conversion to EOS format fails. + */ + @NotNull + public static String convertPEMFormattedPublicKeyToEOSFormat(@NotNull String publicKeyPEM, + boolean requireLegacyFormOfSecp256k1Key) + throws EOSFormatterError { + String eosFormattedPublicKey = publicKeyPEM; + AlgorithmEmployed algorithmEmployed; + PemObject pemObject; + + /* + Validate that key type in PEM object is 'EC PUBLIC KEY'. + */ + String type; + try (Reader reader = new CharArrayReader(eosFormattedPublicKey.toCharArray()); + PemReader pemReader = new PemReader(reader);) { + pemObject = pemReader.readPemObject(); + type = pemObject.getType(); + } catch (Exception e) { + throw new EOSFormatterError(ErrorConstants.INVALID_PEM_PRIVATE_KEY, e); + } + + //Perform a case-insensitive search for the 'EC PRIVATE KEY' string + if (type.matches("(?i:.*" + PEM_HEADER_EC_PUBLIC_KEY + ".*)")) { + + //Get Base64 encoded public key from PEM object + eosFormattedPublicKey = Hex.toHexString(pemObject.getContent()); + + //Determine algorithm used to generate key and remove DER header + if (eosFormattedPublicKey + .toUpperCase().contains( + PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256R1_UNCOMPRESSED + .toUpperCase())) { + eosFormattedPublicKey = eosFormattedPublicKey.toUpperCase() + .replace( + PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256R1_UNCOMPRESSED + .toUpperCase(), + ""); + algorithmEmployed = AlgorithmEmployed.SECP256R1; + } else if (eosFormattedPublicKey + .toUpperCase().contains( + PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256R1_COMPRESSED + .toUpperCase())) { + eosFormattedPublicKey = eosFormattedPublicKey.toUpperCase() + .replace(PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256R1_COMPRESSED + .toUpperCase(), + ""); + algorithmEmployed = AlgorithmEmployed.SECP256R1; + } else if (eosFormattedPublicKey + .toUpperCase().contains( + PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256K1_UNCOMPRESSED + .toUpperCase())) { + eosFormattedPublicKey = eosFormattedPublicKey.toUpperCase() + .replace( + PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256K1_UNCOMPRESSED + .toUpperCase(), + ""); + algorithmEmployed = AlgorithmEmployed.SECP256K1; + } else if (eosFormattedPublicKey + .toUpperCase().contains( + PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256K1_COMPRESSED + .toUpperCase())) { + eosFormattedPublicKey = eosFormattedPublicKey.toUpperCase() + .replace(PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256K1_COMPRESSED + .toUpperCase(), + ""); + algorithmEmployed = AlgorithmEmployed.SECP256K1; + } else { + throw new EOSFormatterError(ErrorConstants.INVALID_DER_PRIVATE_KEY); + } + + /* + Compress the public key if necessary. + Compression is only necessary if the key has a value of 0x04 for the first byte, which + indicates it is uncompressed. + */ + byte[] eosFormattedPublicKeyBytes = Hex.decode(eosFormattedPublicKey); + if (eosFormattedPublicKeyBytes[0] == UNCOMPRESSED_PUBLIC_KEY_BYTE_INDICATOR) { + try { + eosFormattedPublicKey = Hex.toHexString( + compressPublickey(Hex.decode(eosFormattedPublicKey), + algorithmEmployed)); + } catch (Exception e) { + throw new EOSFormatterError(e); + } + } + + try { + //Add checksum,Base58 encode key, and add prefix + eosFormattedPublicKey = encodePublicKey(Hex.decode(eosFormattedPublicKey), + algorithmEmployed, requireLegacyFormOfSecp256k1Key); + } catch (Base58ManipulationError e) { + throw new EOSFormatterError(e); + } + + + } else { + throw new EOSFormatterError(ErrorConstants.INVALID_PEM_PRIVATE_KEY); + } + + return eosFormattedPublicKey; + } + + /** + * This method converts an EOS formatted public key to the PEM format. + * + * @param publicKeyEOS Public key in the EOS format + * @return PEM formatted public key as string + * @throws EOSFormatterError if EOS conversion to PEM format fails. + */ + @NotNull + public static String convertEOSPublicKeyToPEMFormat(@NotNull String publicKeyEOS) + throws EOSFormatterError { + String pemFormattedPublickKey = publicKeyEOS; + AlgorithmEmployed algorithmEmployed; + String keyPrefix; + + /* + The public key will contain an EOS prefix indicating which algorithm was used to generate it. + Below we split the prefix from the key. + */ + if (pemFormattedPublickKey.toUpperCase() + .contains(PATTERN_STRING_EOS_PREFIX_PUB_R1.toUpperCase())) { + algorithmEmployed = AlgorithmEmployed.SECP256R1; + keyPrefix = PATTERN_STRING_EOS_PREFIX_PUB_R1; + pemFormattedPublickKey = pemFormattedPublickKey + .replace(PATTERN_STRING_EOS_PREFIX_PUB_R1, ""); + } else if (pemFormattedPublickKey.toUpperCase() + .contains(PATTERN_STRING_EOS_PREFIX_PUB_K1.toUpperCase())) { + algorithmEmployed = AlgorithmEmployed.SECP256K1; + keyPrefix = PATTERN_STRING_EOS_PREFIX_PUB_K1; + pemFormattedPublickKey = pemFormattedPublickKey + .replace(PATTERN_STRING_EOS_PREFIX_PUB_K1, ""); + } else if (pemFormattedPublickKey.toUpperCase() + .contains(PATTERN_STRING_EOS_PREFIX_EOS.toUpperCase())) { + algorithmEmployed = AlgorithmEmployed.SECP256K1; + keyPrefix = PATTERN_STRING_EOS_PREFIX_EOS; + pemFormattedPublickKey = pemFormattedPublickKey + .replace(PATTERN_STRING_EOS_PREFIX_EOS, ""); + } else { + throw new EOSFormatterError(ErrorConstants.INVALID_EOS_PUBLIC_KEY); + } + + //Base58 decode the key + byte[] base58DecodedPublicKey; + try { + base58DecodedPublicKey = decodePublicKey(pemFormattedPublickKey, + keyPrefix); + } catch (Exception e) { + throw new EOSFormatterError(ErrorConstants.BASE58_DECODING_ERROR, e); + } + + //Convert decoded array to string + pemFormattedPublickKey = Hex.toHexString(base58DecodedPublicKey); + + /* + Compress the public key if necessary. + Compression is only necessary if the key has a value of 0x04 for the first byte, which + indicates it is uncompressed. + */ + if (base58DecodedPublicKey[0] == UNCOMPRESSED_PUBLIC_KEY_BYTE_INDICATOR) { + try { + pemFormattedPublickKey = Hex.toHexString( + compressPublickey(Hex.decode(pemFormattedPublickKey), algorithmEmployed)); + } catch (Exception e) { + throw new EOSFormatterError(e); + } + } + + //Add DER header + switch (algorithmEmployed) { + case SECP256R1: + pemFormattedPublickKey = + PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256R1_COMPRESSED + + pemFormattedPublickKey; + break; + case SECP256K1: + pemFormattedPublickKey = + PATTERN_STRING_PEM_PREFIX_PUBLIC_KEY_SECP256K1_COMPRESSED + + pemFormattedPublickKey; + break; + default: + throw new EOSFormatterError(ErrorConstants.UNSUPPORTED_ALGORITHM); + } + + /* + Correct the sequence length value. According to the ASN.1 specification. For a DER encoded public key the second byte reflects the number of bytes following + the second byte. Here we take the length of the entire string, subtract 4 to remove the first two bytes, divide by 2 (i.e. two characters per byte) and replace + the second byte in the string with the corrected length. + */ + if (pemFormattedPublickKey.length() > FIRST_TWO_BYTES_OF_KEY) { + int i = (pemFormattedPublickKey.length() - FIRST_TWO_BYTES_OF_KEY) / 2; + String correctedLength = Integer.toHexString(i); + pemFormattedPublickKey = + pemFormattedPublickKey.substring(0, DATA_SEQUENCE_LENGTH_BYTE_POSITION) + + correctedLength + + pemFormattedPublickKey.substring(FIRST_TWO_BYTES_OF_KEY); + } else { + throw new EOSFormatterError(ErrorConstants.INVALID_EOS_PUBLIC_KEY); + } + + try { + pemFormattedPublickKey = derToPEM(Hex.decode(pemFormattedPublickKey), + PEMObjectType.PUBLICKEY); + } catch (Exception e) { + throw new EOSFormatterError(e); + } + + return pemFormattedPublickKey; + } + + /** + * This method converts a signature to a EOS compliant form. The signature to be converted must + * be an The ECDSA signature that is a DER encoded ASN.1 sequence of two integer fields (see + * ECDSA-Sig-Value in rfc3279 section 2.2.3). + * + * The DER encoded ECDSA signature follows the following format: Byte 1 - Sequence (Should be + * 30) Byte 2 - Signature length Byte 3 - R Marker (0x02) Byte 4 - R length Bytes 5 to 37 or 38- + * R Byte After R - S Marker (0x02) Byte After S Marker - S Length Bytes After S Length - S + * (always 32-33 bytes) Byte Final - Hash Type + * + * @param signatureDER ECDSA DER encoded signature as byte array + * @param signableTransaction Transaction in signable format + * @param publicKeyPEM public key in PEM format + * @return EOS format of signature + * @throws EOSFormatterError if DER conversion to EOS format fails. + */ + @NotNull + public static String convertDERSignatureToEOSFormat(@NotNull byte[] signatureDER, + @NotNull byte[] signableTransaction, @NotNull String publicKeyPEM) + throws EOSFormatterError { + String eosFormattedSignature = ""; + + try (ASN1InputStream asn1InputStream = new ASN1InputStream(signatureDER)) { + + PEMProcessor publicKey = new PEMProcessor(publicKeyPEM); + AlgorithmEmployed algorithmEmployed = publicKey.getAlgorithm(); + byte[] keyData = publicKey.getKeyData(); + DLSequence sequence = (DLSequence) asn1InputStream.readObject(); + BigInteger r = ((ASN1Integer) sequence.getObjectAt(0)).getPositiveValue(); + BigInteger s = ((ASN1Integer) sequence.getObjectAt(1)).getPositiveValue(); + + s = checkAndHandleLowS(s, algorithmEmployed); + + /* + Get recovery ID. This is the index of the public key (0-3) that represents the + expected public key used to sign the transaction. + */ + int recoverId = getRecoveryId(r, s, Sha256Hash.of(signableTransaction), keyData, + algorithmEmployed); + + if (recoverId < 0) { + throw new IllegalStateException( + ErrorConstants.COULD_NOT_RECOVER_PUBLIC_KEY_FROM_SIG); + } + + //Add RecoveryID + 27 + 4 to create the header byte + recoverId += VALUE_TO_ADD_TO_SIGNATURE_HEADER; + byte headerByte = ((Integer) recoverId).byteValue(); + + + + byte[] decodedSignature = Bytes + .concat(new byte[]{headerByte}, org.bitcoinj.core.Utils.bigIntegerToBytes(r,EXPECTED_R_OR_S_LENGTH), org.bitcoinj.core.Utils.bigIntegerToBytes(s,EXPECTED_R_OR_S_LENGTH)); + if (algorithmEmployed.equals(AlgorithmEmployed.SECP256K1) && + !isCanonical(decodedSignature)) { + throw new IllegalArgumentException(ErrorConstants.NON_CANONICAL_SIGNATURE); + } + + //Add checksum to signature + byte[] signatureWithCheckSum; + String signaturePrefix; + switch (algorithmEmployed) { + case SECP256R1: + signatureWithCheckSum = addCheckSumToSignature(decodedSignature, + SECP256R1_AND_PRIME256V1_CHECKSUM_VALIDATION_SUFFIX.getBytes()); + signaturePrefix = PATTERN_STRING_EOS_PREFIX_SIG_R1; + break; + case SECP256K1: + signatureWithCheckSum = addCheckSumToSignature(decodedSignature, + SECP256K1_CHECKSUM_VALIDATION_SUFFIX.getBytes()); + signaturePrefix = PATTERN_STRING_EOS_PREFIX_SIG_K1; + break; + default: + throw new EOSFormatterError(ErrorConstants.UNSUPPORTED_ALGORITHM); + + } + + //Base58 encode signature and add pertinent EOS prefix + eosFormattedSignature = signaturePrefix.concat(Base58.encode(signatureWithCheckSum)); + + } catch (Exception e) { + throw new EOSFormatterError(ErrorConstants.SIGNATURE_FORMATTING_ERROR, e); + } + + return eosFormattedSignature; + } + + /** + * This method converts a signature to a EOS compliant form. The signature to be converted must + * be an The ECDSA signature that is a DER encoded ASN.1 sequence of two integer fields (see + * ECDSA-Sig-Value in rfc3279 section 2.2.3). This method should be used when only the R and S + * values of the signature are available. + * + * The DER encoded ECDSA signature follows the following format: Byte 1 - Sequence (Should be + * 30) Byte 2 - Signature length Byte 3 - R Marker (0x02) Byte 4 - R length Bytes 5 to 37 or 38- + * R Byte After R - S Marker (0x02) Byte After S Marker - S Length Bytes After S Length - S + * (always 32-33 bytes) Byte Final - Hash Type + * + * @param signatureR R value as BigInteger in string format + * @param signatureS S value as BigInteger in string format + * @param signableTransaction Transaction in signable format + * @param publicKeyPEM Public Key used to sign in PEM format + * @return EOS format of signature + * @throws EOSFormatterError if conversion to EOS format fails. + */ + @NotNull + public static String convertRawRandSofSignatureToEOSFormat(@NotNull String signatureR, + String signatureS, + @NotNull byte[] signableTransaction, @NotNull String publicKeyPEM) + throws EOSFormatterError { + String eosFormattedSignature = ""; + + try { + PEMProcessor publicKey = new PEMProcessor(publicKeyPEM); + AlgorithmEmployed algorithmEmployed = publicKey.getAlgorithm(); + byte[] keyData = publicKey.getKeyData(); + + BigInteger r = new BigInteger(signatureR); + BigInteger s = new BigInteger(signatureS); + + s = checkAndHandleLowS(s, algorithmEmployed); + + /* + Get recovery ID. This is the index of the public key (0-3) that represents the + expected public key used to sign the transaction. + */ + int recoverId = getRecoveryId(r, s, Sha256Hash.of(signableTransaction), keyData, + algorithmEmployed); + + if (recoverId < 0) { + throw new IllegalStateException( + ErrorConstants.COULD_NOT_RECOVER_PUBLIC_KEY_FROM_SIG); + } + + //Add RecoveryID + 27 + 4 to create the header byte + recoverId += VALUE_TO_ADD_TO_SIGNATURE_HEADER; + byte headerByte = ((Integer) recoverId).byteValue(); + + byte[] decodedSignature = Bytes + .concat(new byte[]{headerByte}, org.bitcoinj.core.Utils.bigIntegerToBytes(r,EXPECTED_R_OR_S_LENGTH), org.bitcoinj.core.Utils.bigIntegerToBytes(s,EXPECTED_R_OR_S_LENGTH)); + if (algorithmEmployed.equals(AlgorithmEmployed.SECP256K1) && + !isCanonical(decodedSignature)) { + throw new EosFormatterSignatureIsNotCanonicalError(ErrorConstants.NON_CANONICAL_SIGNATURE); + } + + //Add checksum to signature + byte[] signatureWithCheckSum; + String signaturePrefix; + switch (algorithmEmployed) { + case SECP256R1: + signatureWithCheckSum = addCheckSumToSignature(decodedSignature, + SECP256R1_AND_PRIME256V1_CHECKSUM_VALIDATION_SUFFIX.getBytes()); + signaturePrefix = PATTERN_STRING_EOS_PREFIX_SIG_R1; + break; + case SECP256K1: + signatureWithCheckSum = addCheckSumToSignature(decodedSignature, + SECP256K1_CHECKSUM_VALIDATION_SUFFIX.getBytes()); + signaturePrefix = PATTERN_STRING_EOS_PREFIX_SIG_K1; + break; + default: + throw new EOSFormatterError(ErrorConstants.UNSUPPORTED_ALGORITHM); + + } + + //Base58 encode signature and add pertinent EOS prefix + eosFormattedSignature = signaturePrefix.concat(Base58.encode(signatureWithCheckSum)); + + } catch (Exception e) { + throw new EOSFormatterError(ErrorConstants.SIGNATURE_FORMATTING_ERROR, e); + } + + return eosFormattedSignature; + } + + /** + * This method converts a PEM formatted private key to the EOS format. + * + * @param privateKeyPEM Private key in PEM format + * @return EOS formatted private key as string + * @throws EOSFormatterError if PEM conversion to EOS format fails. + */ + @NotNull + public static String convertPEMFormattedPrivateKeyToEOSFormat(@NotNull String privateKeyPEM) + throws EOSFormatterError { + String eosFormattedPrivateKey = privateKeyPEM; + AlgorithmEmployed algorithmEmployed; + PemObject pemObject; + + /* + Validate that key type in PEM object is 'EC PRIVATE KEY'. + */ + String type; + try (Reader reader = new CharArrayReader(eosFormattedPrivateKey.toCharArray()); + PemReader pemReader = new PemReader(reader);) { + pemObject = pemReader.readPemObject(); + type = pemObject.getType(); + } catch (Exception e) { + throw new EOSFormatterError(ErrorConstants.INVALID_PEM_PRIVATE_KEY, e); + } + + //Perform a case-insensitive search for the 'EC PRIVATE KEY' string + if (type.matches("(?i:.*" + PEM_HEADER_EC_PRIVATE_KEY + ".*)")) { + + //Get Base64 encoded private key from PEM object + eosFormattedPrivateKey = Hex.toHexString(pemObject.getContent()); + + //Determine algorithm used to generate key + if (eosFormattedPrivateKey + .matches("(?i:.*" + PATTERN_STRING_PEM_SUFFIX_PRIVATE_KEY_SECP256R1 + ".*)")) { + algorithmEmployed = AlgorithmEmployed.SECP256R1; + } else if (eosFormattedPrivateKey + .matches("(?i:.*" + PATTERN_STRING_PEM_SUFFIX_PRIVATE_KEY_SECP256K1 + ".*)")) { + algorithmEmployed = AlgorithmEmployed.SECP256K1; + } else { + throw new EOSFormatterError(ErrorConstants.INVALID_DER_PRIVATE_KEY); + } + + //Strip away the DER header and footer + switch (algorithmEmployed) { + case SECP256R1: + eosFormattedPrivateKey = eosFormattedPrivateKey + .substring(PATTERN_STRING_PEM_PREFIX_PRIVATE_KEY_SECP256R1.length(), + eosFormattedPrivateKey.length() + - PATTERN_STRING_PEM_SUFFIX_PRIVATE_KEY_SECP256R1 + .length()); + break; + case SECP256K1: + eosFormattedPrivateKey = eosFormattedPrivateKey + .substring(PATTERN_STRING_PEM_PREFIX_PRIVATE_KEY_SECP256K1.length(), + eosFormattedPrivateKey.length() + - PATTERN_STRING_PEM_SUFFIX_PRIVATE_KEY_SECP256K1 + .length()); + break; + default: + throw new EOSFormatterError(ErrorConstants.UNSUPPORTED_ALGORITHM); + } + + try { + //Add checksum and Base58 encode key + eosFormattedPrivateKey = encodePrivateKey(Hex.decode(eosFormattedPrivateKey), + algorithmEmployed); + } catch (Base58ManipulationError e) { + throw new EOSFormatterError(e); + } + + //Add prefix + StringBuilder builder = new StringBuilder(eosFormattedPrivateKey); + switch (algorithmEmployed) { + case SECP256K1: + //K1 keys do not currently use prefixes + break; + case SECP256R1: + builder.insert(0, PATTERN_STRING_EOS_PREFIX_PVT_R1); + break; + default: + break; + } + eosFormattedPrivateKey = builder.toString(); + + } else { + throw new EOSFormatterError(ErrorConstants.INVALID_PEM_PRIVATE_KEY); + } + + return eosFormattedPrivateKey; + } + + /** + * This method converts an EOS formatted private key to the PEM format. + * + * @param privateKeyEOS Private key in EOS format + * @return PEM formatted private key as a string + * @throws EOSFormatterError if EOS conversion to PEM format fails. + */ + @NotNull + public static String convertEOSPrivateKeyToPEMFormat(@NotNull String privateKeyEOS) + throws EOSFormatterError { + String pemFormattedPrivateKey = privateKeyEOS; + AlgorithmEmployed algorithmEmployed; + + /* + If the private key was encrypted using the secp256R1 algorithm it will have a 'PVT_R1_' prefix + that needs to be removed. + */ + if (pemFormattedPrivateKey.toUpperCase() + .contains(PATTERN_STRING_EOS_PREFIX_PVT_R1.toUpperCase())) { + algorithmEmployed = AlgorithmEmployed.SECP256R1; + /* + Split the prefix from the key and take the second half of string. The second half contains + the key minus the prefix. + */ + pemFormattedPrivateKey = pemFormattedPrivateKey + .split(PATTERN_STRING_EOS_PREFIX_PVT_R1)[1]; + } else { + algorithmEmployed = AlgorithmEmployed.SECP256K1; + } + + //Base58 decode the key + byte[] base58DecodedPrivateKey; + try { + base58DecodedPrivateKey = decodePrivateKey(pemFormattedPrivateKey, + algorithmEmployed); + } catch (Exception e) { + throw new EOSFormatterError(ErrorConstants.BASE58_DECODING_ERROR, e); + } + + //Convert decoded array to string + pemFormattedPrivateKey = Hex.toHexString(base58DecodedPrivateKey); + + //Add header and footer + switch (algorithmEmployed) { + case SECP256R1: + pemFormattedPrivateKey = + PATTERN_STRING_PEM_PREFIX_PRIVATE_KEY_SECP256R1 + pemFormattedPrivateKey + + PATTERN_STRING_PEM_SUFFIX_PRIVATE_KEY_SECP256R1; + break; + case SECP256K1: + pemFormattedPrivateKey = + PATTERN_STRING_PEM_PREFIX_PRIVATE_KEY_SECP256K1 + pemFormattedPrivateKey + + PATTERN_STRING_PEM_SUFFIX_PRIVATE_KEY_SECP256K1; + break; + default: + throw new EOSFormatterError(ErrorConstants.UNSUPPORTED_ALGORITHM); + } + + /* + Correct the sequence length value. According to the ASN.1 specification. For a DER + encoded private key the second byte reflects the number of bytes following + the second byte. Here we take the length of the entire string, subtract 4 to remove + the first two bytes, divide by 2 (i.e. two characters per byte) and replace + the second byte in the string with the corrected length. + */ + if (pemFormattedPrivateKey.length() > FIRST_TWO_BYTES_OF_KEY) { + int i = (pemFormattedPrivateKey.length() - FIRST_TWO_BYTES_OF_KEY) / 2; + String correctedLength = Integer.toHexString(i); + pemFormattedPrivateKey = + pemFormattedPrivateKey.substring(0, DATA_SEQUENCE_LENGTH_BYTE_POSITION) + + correctedLength + + pemFormattedPrivateKey.substring(FIRST_TWO_BYTES_OF_KEY); + } else { + throw new EOSFormatterError(ErrorConstants.INVALID_EOS_PRIVATE_KEY); + } + + try { + pemFormattedPrivateKey = derToPEM(Hex.decode(pemFormattedPrivateKey), + PEMObjectType.PRIVATEKEY); + } catch (DerToPemConversionError e) { + throw new EOSFormatterError(e); + } + + return pemFormattedPrivateKey; + } + + /** + * Extract serialized transaction from a signable transaction + *

+ * Signable signature structure: + *

+ * chainId (64 characters) + serialized transaction + 32 bytes of 0 + * + * @param eosTransaction - the input signable transaction + * @return - extracted serialized transaction from the input signable transaction + * @throws EOSFormatterError if input is invalid + */ + public static String extractSerializedTransactionFromSignable(@NotNull String eosTransaction) + throws EOSFormatterError { + if (eosTransaction.isEmpty()) { + throw new EOSFormatterError(ErrorConstants.EMPTY_INPUT_EXTRACT_SERIALIZIED_TRANS_FROM_SIGNABLE); + } + + if (eosTransaction.length() <= MINIMUM_SIGNABLE_TRANSACTION_LENGTH) { + throw new EOSFormatterError(String.format(ErrorConstants.INVALID_INPUT_SIGNABLE_TRANS_LENGTH_EXTRACT_SERIALIZIED_TRANS_FROM_SIGNABLE, MINIMUM_SIGNABLE_TRANSACTION_LENGTH)); + } + + if (!eosTransaction.endsWith(Hex.toHexString(new byte[32]))) { + throw new EOSFormatterError(ErrorConstants.INVALID_INPUT_SIGNABLE_TRANS_EXTRACT_SERIALIZIED_TRANS_FROM_SIGNABLE); + } + + try { + String cutChainId = eosTransaction.substring(CHAIN_ID_LENGTH); + return cutChainId.substring(0, cutChainId.length() - Hex.toHexString(new byte[32]).length()); + } catch (Exception ex) { + throw new EOSFormatterError(ErrorConstants.EXTRACT_SERIALIZIED_TRANS_FROM_SIGNABLE_ERROR, ex); + } + } + + /** + * Preparing signable transaction for signing. + *

+ * Signable signature structure: + *

+ * chainId + serialized transaction + 32 bytes of 0 + * + * @param serializedTransaction - the serialized transaction to be converted to signable transaction + * @param chainId - the chain id will be used inside the signature transaction structure. + * @return - Signable transaction + * @throws EOSFormatterError if inputs are invalid + */ + public static String prepareSerializedTransactionForSigning(@NotNull String serializedTransaction, + @NotNull String chainId) throws EOSFormatterError { + if (serializedTransaction.isEmpty() || chainId.isEmpty()) { + throw new EOSFormatterError(ErrorConstants.EMPTY_INPUT_PREPARE_SERIALIZIED_TRANS_FOR_SIGNING); + } + + String signableTransaction = chainId + serializedTransaction + Hex.toHexString(new byte[32]); + if (signableTransaction.length() <= MINIMUM_SIGNABLE_TRANSACTION_LENGTH) { + throw new EOSFormatterError(String.format(ErrorConstants.INVALID_INPUT_SIGNABLE_TRANS_LENGTH_EXTRACT_SERIALIZIED_TRANS_FROM_SIGNABLE, MINIMUM_SIGNABLE_TRANSACTION_LENGTH)); + } + + return signableTransaction; + } + + /** + * This method converts a DER encoded private key, public key, or signature into the PEM + * format. + * + * Example of a PEM formatted private key: -----BEGIN EC PRIVATE KEY----- + * MDECAQEEIEJSCKmyR0kmxy2pgkEwkqrodn2jG9mhXRhhxgsneuBsoAoGCCqGSM49AwEH -----END EC PRIVATE + * KEY----- + * + * The key data between the header and footer is Base64 encoded. + * + * @param derEncodedByteArray DER encoded byte array to convert to PEM format + * @param pemObjectType The type of PEM object being created (i.e. Private Key, Public Key, + * Signature). + * @return PEM format as string. + * @throws DerToPemConversionError if DER to PEM conversion fails. + */ + @NotNull + private static String derToPEM(@NotNull byte[] derEncodedByteArray, + @NotNull PEMObjectType pemObjectType) throws DerToPemConversionError { + StringBuilder pemForm = new StringBuilder(); + try { + //Build Header + if (pemObjectType.equals(PEMObjectType.PRIVATEKEY)) { + pemForm.append(PEM_HEADER_PRIVATE_KEY); + } else if (pemObjectType.equals(PEMObjectType.PUBLICKEY)) { + pemForm.append(PEM_HEADER_PUBLIC_KEY); + } else { + throw new DerToPemConversionError(ErrorConstants.DER_TO_PEM_CONVERSION); + } + pemForm.append("\n"); + + //Base64 Encode DER Encoded Byte Array And Add to PEM Object + String base64EncodedByteArray = new String(Base64.encode(derEncodedByteArray)); + pemForm.append(base64EncodedByteArray); + pemForm.append("\n"); + + //Build Footer + if (pemObjectType.equals(PEMObjectType.PRIVATEKEY)) { + pemForm.append(PEM_FOOTER_PRIVATE_KEY); + } else if (pemObjectType.equals(PEMObjectType.PUBLICKEY)) { + pemForm.append(PEM_FOOTER_PUBLIC_KEY); + } else { + throw new DerToPemConversionError(ErrorConstants.DER_TO_PEM_CONVERSION); + } + + } catch (Exception e) { + throw new DerToPemConversionError(ErrorConstants.DER_TO_PEM_CONVERSION, e); + } + + return pemForm.toString(); + } + + /** + * This method Base58 decodes the private key and validates its checksum. + * + * @param strKey Base58 value of the key + * @param keyType key type + * @return Base58 decoded key minus checksum + * @throws Base58ManipulationError if private key decoding fails. + */ + @NotNull + private static byte[] decodePrivateKey(@NotNull String strKey, AlgorithmEmployed keyType) + throws Base58ManipulationError { + if (strKey.isEmpty()) { + throw new IllegalArgumentException(ErrorConstants.BASE58_EMPTY_KEY); + } + + byte[] decodedKey; + + try { + byte[] base58Decoded = Base58.decode(strKey); + byte[] firstCheckSum = Arrays + .copyOfRange(base58Decoded, base58Decoded.length - CHECKSUM_BYTES, + base58Decoded.length); + decodedKey = Arrays + .copyOfRange(base58Decoded, 0, base58Decoded.length - CHECKSUM_BYTES); + + switch (keyType) { + case SECP256R1: + byte[] secp256r1Suffix = SECP256R1_AND_PRIME256V1_CHECKSUM_VALIDATION_SUFFIX + .getBytes(); + if (invalidRipeMD160CheckSum(decodedKey, firstCheckSum, secp256r1Suffix)) { + throw new IllegalArgumentException(ErrorConstants.BASE58_INVALID_CHECKSUM); + } + break; + case PRIME256V1: + byte[] prime256v1Suffix = SECP256R1_AND_PRIME256V1_CHECKSUM_VALIDATION_SUFFIX + .getBytes(); + if (invalidRipeMD160CheckSum(decodedKey, firstCheckSum, prime256v1Suffix)) { + throw new IllegalArgumentException(ErrorConstants.BASE58_INVALID_CHECKSUM); + } + break; + case SECP256K1: + if (invalidSha256x2CheckSum(decodedKey, firstCheckSum)) { + throw new IllegalArgumentException(ErrorConstants.BASE58_INVALID_CHECKSUM); + } + break; + default: + throw new Base58ManipulationError(ErrorConstants.UNSUPPORTED_ALGORITHM); + + } + + // trim 0x80 out if the key size is more than 32 bytes + // this code apply for key has more than 32 byte and non R1 key + if (decodedKey.length > STANDARD_KEY_LENGTH && keyType != AlgorithmEmployed.SECP256R1) { + // Slice out the first byte + decodedKey = Arrays.copyOfRange(decodedKey, 1, decodedKey.length); + if (decodedKey.length > STANDARD_KEY_LENGTH + && decodedKey[STANDARD_KEY_LENGTH] == ((Integer) 1).byteValue()) { + // Slice out last byte + decodedKey = Arrays.copyOfRange(decodedKey, 0, decodedKey.length - 1); + } + } + } catch (Exception ex) { + throw new Base58ManipulationError(ErrorConstants.BASE58_DECODING_ERROR, ex); + } + + return decodedKey; + } + + /** + * Base58 encodes a private key after calculating and appending the checksum. + * + * @param pemKey - Private key as byte[] to encode + * @param keyType - input key type + * @return Base58 encoded private key as byte[] + * @throws Base58ManipulationError it private key encoding fails. + */ + @NotNull + public static String encodePrivateKey(@NotNull byte[] pemKey, + @NotNull AlgorithmEmployed keyType) throws Base58ManipulationError { + byte[] checkSum; + String base58Key = ""; + + switch (keyType) { + case SECP256R1: + checkSum = extractCheckSumRIPEMD160(pemKey, + SECP256R1_AND_PRIME256V1_CHECKSUM_VALIDATION_SUFFIX.getBytes()); + break; + case PRIME256V1: + checkSum = extractCheckSumRIPEMD160(pemKey, + SECP256R1_AND_PRIME256V1_CHECKSUM_VALIDATION_SUFFIX.getBytes()); + break; + case SECP256K1: + pemKey = Bytes.concat(new byte[]{((Integer) EOS_SECP256K1_HEADER_BYTE).byteValue()}, + pemKey); + checkSum = extractCheckSumSha256x2(pemKey); + break; + default: + throw new Base58ManipulationError(ErrorConstants.CHECKSUM_GENERATION_ERROR); + + } + + base58Key = Base58.encode(Bytes.concat(pemKey, checkSum)); + + if (base58Key.isEmpty()) { + throw new Base58ManipulationError(ErrorConstants.BASE58_ENCODING_ERROR); + } else { + return base58Key; + } + + } + + /** + * Encoding PEM public key to EOS format. + * + * @param pemKey - PEM key as byte[] to encode + * @param keyType - Algorithm type used to create key + * @param isLegacy - If the developer prefers a legacy version of a secp256k1 key that uses an + * "EOS" prefix. + * @return - EOS format of public key + * @throws Base58ManipulationError if public key encoding fails. + */ + @NotNull + public static String encodePublicKey(@NotNull byte[] pemKey, @NotNull AlgorithmEmployed keyType, + boolean isLegacy) + throws Base58ManipulationError { + String base58Key = ""; + if (pemKey.length == 0) { + throw new IllegalArgumentException(ErrorConstants.PUBLIC_KEY_IS_EMPTY); + } + + try { + byte[] checkSum; + switch (keyType) { + case SECP256K1: + if (isLegacy) { + checkSum = extractCheckSumRIPEMD160(pemKey, + LEGACY_CHECKSUM_VALIDATION_SUFFIX.getBytes()); + } else { + checkSum = extractCheckSumRIPEMD160(pemKey, + SECP256K1_CHECKSUM_VALIDATION_SUFFIX.getBytes()); + } + break; + case SECP256R1: + checkSum = extractCheckSumRIPEMD160(pemKey, + SECP256R1_AND_PRIME256V1_CHECKSUM_VALIDATION_SUFFIX.getBytes()); + break; + default: + throw new Base58ManipulationError(ErrorConstants.UNSUPPORTED_ALGORITHM); + + } + + base58Key = Base58.encode(Bytes.concat(pemKey, checkSum)); + + if (base58Key.equals("")) { + throw new Base58ManipulationError(ErrorConstants.BASE58_ENCODING_ERROR); + } + + } catch (Exception ex) { + throw new Base58ManipulationError(ErrorConstants.BASE58_ENCODING_ERROR, ex); + } + + //Add prefix + StringBuilder builder = new StringBuilder(base58Key); + switch (keyType) { + case SECP256K1: + if (isLegacy) { + builder.insert(0, PATTERN_STRING_EOS_PREFIX_EOS); + } else { + builder.insert(0, PATTERN_STRING_EOS_PREFIX_PUB_K1); + } + break; + case SECP256R1: + builder.insert(0, PATTERN_STRING_EOS_PREFIX_PUB_R1); + break; + default: + break; + } + base58Key = builder.toString(); + + return base58Key; + } + + /** + * Base58 decodes a public key and validates checksum. + * + * @param strKey Base58 encoded public key in string format. + * @param keyPrefix EOS specific key type prefix (i.e. PUB_R1_, PUB_K1_, or EOS). + * @return Base58 decoded public key as byte[] + * @throws Base58ManipulationError if public key decoding fails. + */ + @NotNull + public static byte[] decodePublicKey(@NotNull String strKey, String keyPrefix) + throws Base58ManipulationError { + if (strKey.isEmpty()) { + throw new IllegalArgumentException("Input key to decode can't be empty."); + } + + byte[] decodedKey = null; + + try { + byte[] base58Decoded = Base58.decode(strKey); + byte[] firstCheckSum = Arrays + .copyOfRange(base58Decoded, base58Decoded.length - CHECKSUM_BYTES, + base58Decoded.length); + decodedKey = Arrays + .copyOfRange(base58Decoded, 0, base58Decoded.length - CHECKSUM_BYTES); + + switch (keyPrefix) { + case PATTERN_STRING_EOS_PREFIX_PUB_R1: + if (invalidRipeMD160CheckSum(decodedKey, firstCheckSum, + SECP256R1_AND_PRIME256V1_CHECKSUM_VALIDATION_SUFFIX.getBytes())) { + throw new IllegalArgumentException( + ErrorConstants.BASE58_INVALID_CHECKSUM); + } + break; + + case PATTERN_STRING_EOS_PREFIX_PUB_K1: + if (invalidRipeMD160CheckSum(decodedKey, firstCheckSum, + SECP256K1_CHECKSUM_VALIDATION_SUFFIX.getBytes())) { + throw new IllegalArgumentException( + ErrorConstants.BASE58_INVALID_CHECKSUM); + } + break; + + case PATTERN_STRING_EOS_PREFIX_EOS: + if (invalidRipeMD160CheckSum(decodedKey, firstCheckSum, + LEGACY_CHECKSUM_VALIDATION_SUFFIX.getBytes())) { + throw new IllegalArgumentException( + ErrorConstants.BASE58_INVALID_CHECKSUM); + } + break; + + default: + break; + } + + } catch (Exception ex) { + throw new Base58ManipulationError(ErrorConstants.BASE58_DECODING_ERROR, ex); + } + + return decodedKey; + } + + /** + * Validate checksum by RipeMD160 digestion + * + * @param inputKey - input key to validate + * @param checkSumToValidate - checksum to validate with the checksum inside input key + * @param keyTypeByteArray - byte[] of key type used for checksum validation (e.g. + * "K1".getBytes()) + * @return This checksum returns whether the checksum comparison was invalid. + */ + private static boolean invalidRipeMD160CheckSum(@NotNull byte[] inputKey, + @NotNull byte[] checkSumToValidate, @NotNull byte[] keyTypeByteArray) { + if (inputKey.length == 0 || checkSumToValidate.length == 0 + ) { + throw new IllegalArgumentException( + ErrorConstants.BASE58_EMPTY_CHECKSUM_OR_KEY_OR_KEY_TYPE); + } + + byte[] keyWithType = Bytes.concat(inputKey, keyTypeByteArray); + byte[] digestRIPEMD160 = digestRIPEMD160(keyWithType); + byte[] checkSumFromInputKey = Arrays.copyOfRange(digestRIPEMD160, 0, CHECKSUM_BYTES); + + //This checksum returns whether the checksum comparison was invalid. + return !Arrays.equals(checkSumToValidate, checkSumFromInputKey); + } + + /** + * Validate checksum by double Sha256 + * + * @param inputKey - input key to validate + * @param checkSumToValidate - checksum to validate with the checksum inside input key + * @return This checksum returns whether the checksum comparison was invalid. + */ + private static boolean invalidSha256x2CheckSum(@NotNull byte[] inputKey, + @NotNull byte[] checkSumToValidate) { + if (inputKey.length == 0 || checkSumToValidate.length == 0) { + throw new IllegalArgumentException(ErrorConstants.BASE58_EMPTY_CHECKSUM_OR_KEY); + } + + byte[] sha256x2 = Sha256Hash.hashTwice(inputKey); + byte[] checkSumFromInputKey = Arrays.copyOfRange(sha256x2, 0, CHECKSUM_BYTES); + + //This checksum returns whether the checksum comparison was invalid. + return !Arrays.equals(checkSumToValidate, checkSumFromInputKey); + } + + /** + * Digesting input byte[] to RIPEMD160 format + * + * @param input - input byte[] + * @return RIPEMD160 format + */ + @NotNull + private static byte[] digestRIPEMD160(@NotNull byte[] input) { + RIPEMD160Digest digest = new RIPEMD160Digest(); + byte[] output = new byte[digest.getDigestSize()]; + digest.update(input, 0, input.length); + digest.doFinal(output, 0); + + return output; + } + + /** + * Extracting Checksum for RIPEMD160 digest format + * + * @param pemKey - input PEM key + * @return checksum + */ + @NotNull + private static byte[] extractCheckSumRIPEMD160(@NotNull byte[] pemKey, + byte[] keyTypeByteArray) { + if (keyTypeByteArray != null) { + pemKey = Bytes.concat(pemKey, keyTypeByteArray); + } + + byte[] ripemd160Digest = digestRIPEMD160(pemKey); + + return Arrays.copyOfRange(ripemd160Digest, 0, CHECKSUM_BYTES); + } + + /** + * Extracting checksum for Sha256x2 format + * + * @param pemKey - input pem key + * @return checksum + */ + @NotNull + private static byte[] extractCheckSumSha256x2(@NotNull byte[] pemKey) { + byte[] sha256x2 = Sha256Hash.hashTwice(pemKey); + + return Arrays.copyOfRange(sha256x2, 0, CHECKSUM_BYTES); + } + + /** + * Decompresses a public key based on the algorithm used to generate it. + * + * @param compressedPublicKey Compressed public key as byte[] + * @param algorithmEmployed Algorithm used during key creation + * @return Decompressed public key as byte[] + * @throws EOSFormatterError when public key decompression fails. + */ + @NotNull + private static byte[] decompressPublickey(byte[] compressedPublicKey, + AlgorithmEmployed algorithmEmployed) + throws EOSFormatterError { + try { + ECParameterSpec parameterSpec = ECNamedCurveTable + .getParameterSpec(algorithmEmployed.getString()); + ECPoint ecPoint = parameterSpec.getCurve().decodePoint(compressedPublicKey); + byte[] x = ecPoint.getXCoord().getEncoded(); + byte[] y = ecPoint.getYCoord().getEncoded(); + if (y.length > STANDARD_KEY_LENGTH) { + y = Arrays.copyOfRange(y, 1, y.length); + } + return Bytes.concat(new byte[]{UNCOMPRESSED_PUBLIC_KEY_BYTE_INDICATOR}, x, y); + } catch (Exception e) { + throw new EOSFormatterError(ErrorConstants.PUBLIC_KEY_DECOMPRESSION_ERROR, e); + } + } + + /** + * Compresses a public key based on the algorithm used to generate it. + * + * @param compressedPublicKey Decompressed public key as byte[] + * @param algorithmEmployed Algorithm used during key creation + * @return Compressed public key as byte[] + * @throws EOSFormatterError when public key compression fails. + */ + @NotNull + private static byte[] compressPublickey(byte[] compressedPublicKey, + AlgorithmEmployed algorithmEmployed) + throws EOSFormatterError { + byte compressionPrefix; + try { + ECParameterSpec parameterSpec = ECNamedCurveTable + .getParameterSpec(algorithmEmployed.getString()); + ECPoint ecPoint = parameterSpec.getCurve().decodePoint(compressedPublicKey); + byte[] x = ecPoint.getXCoord().getEncoded(); + byte[] y = ecPoint.getYCoord().getEncoded(); + + //Check whether y is negative(odd in field) or positive(even in field) and assign compressionPrefix + BigInteger bigIntegerY = new BigInteger(Hex.toHexString(y), 16); + BigInteger bigIntegerTwo = BigInteger.valueOf(2); + BigInteger remainder = bigIntegerY.mod(bigIntegerTwo); + + if (remainder.equals(BigInteger.ZERO)) { + compressionPrefix = COMPRESSED_PUBLIC_KEY_BYTE_INDICATOR_POSITIVE_Y; + } else { + compressionPrefix = COMPRESSED_PUBLIC_KEY_BYTE_INDICATOR_NEGATIVE_Y; + } + + return Bytes.concat(new byte[]{compressionPrefix}, x); + } catch (Exception e) { + throw new EOSFormatterError(ErrorConstants.PUBLIC_KEY_COMPRESSION_ERROR, e); + } + } + + /** + * Takes the S value of an ECDSA DER encoded signature and converts it to a low value. + * + * @param s S value from signature + * @param keyType Algorithm used to generate private key that signed the message. + * @return Low S value + * @throws LowSVerificationError when the S value determination fails. + */ + private static BigInteger checkAndHandleLowS(BigInteger s, AlgorithmEmployed keyType) + throws LowSVerificationError { + if (!isLowS(s, keyType)) { + switch (keyType) { + case SECP256R1: + return CURVE_R1.getN().subtract(s); + + default: + return CURVE_K1.getN().subtract(s); + } + } + + return s; + } + + /** + * Takes the S value of an ECDSA DER encoded signature and determines whether the value is low. + * + * @param s S value from signature + * @param keyType Algorithm used to generate private key that signed the message. + * @return boolean indicating whether S value is low + * @throws LowSVerificationError when the S value determination fails. + */ + private static boolean isLowS(BigInteger s, AlgorithmEmployed keyType) + throws LowSVerificationError { + int compareResult; + + switch (keyType) { + case SECP256R1: + compareResult = s.compareTo(HALF_CURVE_ORDER_R1); + break; + + case SECP256K1: + compareResult = s.compareTo(HALF_CURVE_ORDER_K1); + break; + + default: + throw new LowSVerificationError(ErrorConstants.UNSUPPORTED_ALGORITHM); + } + + return compareResult == 0 || compareResult == -1; + } + + /** + * Adding checksum to signature + * + * @param signature - signature to get checksum added + */ + private static byte[] addCheckSumToSignature(byte[] signature, byte[] keyTypeByteArray) { + byte[] signatureWithKeyType = Bytes.concat(signature, keyTypeByteArray); + byte[] signatureRipemd160 = digestRIPEMD160(signatureWithKeyType); + byte[] checkSum = Arrays.copyOfRange(signatureRipemd160, 0, CHECKSUM_BYTES); + return Bytes.concat(signature, checkSum); + } + + /** + * Check if the input signature is canonical + * + * @param signature - signature to check for canonical + * @return whether the input signature is canonical + */ + private static boolean isCanonical(byte[] signature) { + return (signature[1] & ((Integer) 0x80).byteValue()) == ((Integer) 0x00).byteValue() + && !(signature[1] == ((Integer) 0x00).byteValue() + && ((signature[2] & ((Integer) 0x80).byteValue()) == ((Integer) 0x00).byteValue())) + && (signature[33] & ((Integer) 0x80).byteValue()) == ((Integer) 0x00).byteValue() + && !(signature[33] == ((Integer) 0x00).byteValue() + && ((signature[34] & ((Integer) 0x80).byteValue()) == ((Integer) 0x00) + .byteValue())); + } + + /** + * Getting recovery id from R and S + * + * @param r - R in DER of Signature + * @param s - S in DER of Signature + * @param sha256HashMessage - Sha256Hash of signed message + * @param publicKey - public key to validate + * @param keyType - key type + * @return - Recovery id of the signature. From 0 to 3. Return -1 if find nothing. + */ + private static int getRecoveryId(BigInteger r, BigInteger s, Sha256Hash sha256HashMessage, + byte[] publicKey, AlgorithmEmployed keyType) { + for (int i = 0; i < NUMBER_OF_POSSIBLE_PUBLIC_KEYS; i++) { + byte[] recoveredPublicKey = recoverPublicKeyFromSignature(i, r, s, sha256HashMessage, + true, keyType); + + if (Arrays.equals(publicKey, recoveredPublicKey)) { + return i; + } + } + + return -1; + } + + /** + * * Copyright 2011 Google Inc. * Copyright 2014 Andreas Schildbach * Copyright 2014-2016 the + * libsecp256k1 contributors * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. * You may obtain a copy of + * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by + * applicable law or agreed to in writing, software * distributed under the License is + * distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. * See the License for the specific language governing permissions and * + * limitations under the License. + *

+ * The method was modified to match what we need + * + *

Given the components of a signature and a selector value, recover and return the public + * key that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

+ * + *

The recId is an index from 0 to 3 which indicates which of the 4 possible keys is the + * correct one. Because the key recovery operation yields multiple potential keys, the correct + * key must either be stored alongside the signature, or you must be willing to try each recId + * in turn until you find one that outputs the key you are expecting.

+ * + *

If this method returns null it means recovery was not possible and recId should be + * iterated.

+ * + *

Given the above two points, a correct usage of this method is inside a for loop from 0 to + * 3, and if the output is null OR a key that is not the one you expect, you try again with the + * next recId.

+ * + * @param recId Which possible key to recover. + * @param r the R components of the signature, wrapped. + * @param s the S components of the signature, wrapped. + * @param message Hash of the data that was signed. + * @param compressed Whether or not the original pubkey was compressed. + * @param keyType key type + * @return An ECKey containing only the public part, or null if recovery wasn't possible. + */ + private static byte[] recoverPublicKeyFromSignature(int recId, BigInteger r, BigInteger s, + @NotNull Sha256Hash message, boolean compressed, AlgorithmEmployed keyType) { + checkArgument(recId >= 0, "recId must be positive"); + checkArgument(r.signum() >= 0, "r must be positive"); + checkArgument(s.signum() >= 0, "s must be positive"); + + // 1.0 For j from 0 to h (h == recId here and the loop is outside this function) + // 1.1 Let x = r + jn + + BigInteger n; // Curve order. + ECPoint g; + ECCurve.Fp curve; + + switch (keyType) { + case SECP256R1: + n = ecParamsR1.getN(); + g = ecParamsR1.getG(); + curve = (ECCurve.Fp) ecParamsR1.getCurve(); + break; + + default: + n = ecParamsK1.getN(); + g = ecParamsK1.getG(); + curve = (ECCurve.Fp) ecParamsK1.getCurve(); + break; + } + + BigInteger i = BigInteger.valueOf((long) recId / 2); + BigInteger x = r.add(i.multiply(n)); + + // 1.2. Convert the integer x to an octet string X of length mlen using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉. + // 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this conversion routine outputs “invalid”, then + // do another iteration of Step 1. + // + // More concisely, what these points mean is to use X as a compressed public key. + BigInteger prime = curve.getQ(); + if (x.compareTo(prime) >= 0) { + // Cannot have point co-ordinates larger than this as everything takes place modulo Q. + return null; + } + // Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities. + // So it's encoded in the recId. + ECPoint R = decompressKey(x, (recId & 1) == 1, keyType); + // 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility). + if (!R.multiply(n).isInfinity()) { + return null; + } + // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification. + BigInteger e = message.toBigInteger(); + // 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating recId) + // 1.6.1. Compute a candidate public key as: + // Q = mi(r) * (sR - eG) + // + // Where mi(x) is the modular multiplicative inverse. We transform this into the following: + // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) + // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). In the above equation + // ** is point multiplication and + is point addition (the EC group operator). + // + // We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive + // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8. + BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n); + BigInteger rInv = r.modInverse(n); + BigInteger srInv = rInv.multiply(s).mod(n); + BigInteger eInvrInv = rInv.multiply(eInv).mod(n); + ECPoint q = ECAlgorithms.sumOfTwoMultiplies(g, eInvrInv, R, srInv); + return q.getEncoded(compressed); + } + + /** + * * Copyright 2011 Google Inc. * Copyright 2014 Andreas Schildbach * Copyright 2014-2016 the + * libsecp256k1 contributors * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. * You may obtain a copy of + * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by + * applicable law or agreed to in writing, software * distributed under the License is + * distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. * See the License for the specific language governing permissions and * + * limitations under the License. + *

+ * The method was modified to match what we need + *

+ * Decompress a compressed public key (x co-ord and low-bit of y-coord). + */ + private static ECPoint decompressKey(BigInteger xBN, boolean yBit, AlgorithmEmployed keyType) { + ECCurve.Fp curve; + + switch (keyType) { + case SECP256R1: + curve = (ECCurve.Fp) ecParamsR1.getCurve(); + break; + + default: + curve = (ECCurve.Fp) ecParamsK1.getCurve(); + break; + } + + X9IntegerConverter x9 = new X9IntegerConverter(); + byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(curve)); + compEnc[0] = (byte) (yBit ? COMPRESSED_PUBLIC_KEY_BYTE_INDICATOR_NEGATIVE_Y + : COMPRESSED_PUBLIC_KEY_BYTE_INDICATOR_POSITIVE_Y); + return curve.decodePoint(compEnc); + } + + +} diff --git a/app/src/main/java/com/tangem/wallet/eos/utilities/PEMProcessor.java b/app/src/main/java/com/tangem/wallet/eos/utilities/PEMProcessor.java new file mode 100644 index 0000000000..d4c6396fc0 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/utilities/PEMProcessor.java @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2017-2019 block.one all rights reserved. + */ + +package com.tangem.wallet.eos.utilities; + +import com.tangem.wallet.eos.enums.AlgorithmEmployed; +import com.tangem.wallet.eos.error.ErrorConstants; +import com.tangem.wallet.eos.error.utilities.Base58ManipulationError; +import com.tangem.wallet.eos.error.utilities.EOSFormatterError; +import com.tangem.wallet.eos.error.utilities.PEMProcessorError; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.DLSequence; +import org.bouncycastle.asn1.sec.SECObjectIdentifiers; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.ec.CustomNamedCurves; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.math.ec.FixedPointCombMultiplier; +import org.bouncycastle.math.ec.FixedPointUtil; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; +import org.jetbrains.annotations.NotNull; + +import java.io.CharArrayReader; +import java.io.IOException; +import java.io.Reader; +import java.math.BigInteger; + +/** + * This is a wrapper class for PEMObjects that throws a {@link PEMProcessorError} if an invalid + * PEMObject is passed into the constructor. Once initialized the PEMProcessor can be used to + * return the type, DER format, or algorithm used to create the PEMObject. + */ +public class PEMProcessor { + + /** + * PEM private key type on header + */ + private static final String PRIVATE_KEY_TYPE = "EC PRIVATE KEY"; + + /** + * Private key start index on ASN.1 sequence + */ + private static final int PRIVATE_KEY_START_INDEX = 2; + + //region CURVE Constants + /** + * Constant name of secp256r1 curves + */ + private static final String SECP256_R1 = "secp256r1"; + + /** + * Constant name of secp256k1 curves + */ + private static final String SECP256_K1 = "secp256k1"; + + /** + * EC parameters holder of secp256r1 key type + */ + private static final X9ECParameters CURVE_PARAMS_R1 = CustomNamedCurves.getByName(SECP256_R1); + + /** + * EC parameters holder of secp256k1 key type + */ + private static final X9ECParameters CURVE_PARAMS_K1 = CustomNamedCurves.getByName(SECP256_K1); + + /** + * EC holder of secp256r1 key type + */ + private static final ECDomainParameters CURVE_R1; + + /** + * EC holder of secp256k1 key type + */ + private static final ECDomainParameters CURVE_K1; + + /** + * Signum to convert a negative value to a positive Big Integer + */ + private static final int BIG_INTEGER_POSITIVE = 1; + + + static { + // secp256r1 + FixedPointUtil.precompute(CURVE_PARAMS_R1.getG()); + CURVE_R1 = new ECDomainParameters( + CURVE_PARAMS_R1.getCurve(), + CURVE_PARAMS_R1.getG(), + CURVE_PARAMS_R1.getN(), + CURVE_PARAMS_R1.getH()); + + // secp256k1 + CURVE_K1 = new ECDomainParameters( + CURVE_PARAMS_K1.getCurve(), + CURVE_PARAMS_K1.getG(), + CURVE_PARAMS_K1.getN(), + CURVE_PARAMS_K1.getH()); + } + //endregion + + private PemObject pemObject; + private String pemObjectString; + + /** + * Initialize PEMProcessor with PEM content in String format. + * + * @param pemObject - input PEM content in String format. + * @throws PEMProcessorError When failing to read pem data from the input. + */ + public PEMProcessor(String pemObject) throws PEMProcessorError { + this.pemObjectString = pemObject; + try (Reader reader = new CharArrayReader(this.pemObjectString.toCharArray()); + PemReader pemReader = new PemReader(reader)) { + this.pemObject = pemReader.readPemObject(); + if (this.pemObject == null) { + throw new PEMProcessorError(ErrorConstants.INVALID_PEM_OBJECT); + } + + } catch (Exception e) { + throw new PEMProcessorError(ErrorConstants.ERROR_PARSING_PEM_OBJECT, e); + } + + } + + /** + * Gets the PEM Object key type (i.e. PRIVATE KEY, PUBLIC KEY). + * + * @return key type as string + */ + @NotNull + public String getType() { + return pemObject.getType(); + } + + /** + * Gets the DER encoded format of the key from its PEM format. + * + * @return DER format of key as string + */ + @NotNull + public String getDERFormat() { + return Hex.toHexString(pemObject.getContent()); + } + + /** + * Gets the algorithm used to generate the key from its PEM format. + * + * @return The algorithm used to generate the key. + * @throws PEMProcessorError if the algorithm fetch leads to an exception. + */ + @NotNull + public AlgorithmEmployed getAlgorithm() throws PEMProcessorError { + + Object pemObjectParsed = parsePEMObject(); + + String oid; + if (pemObjectParsed instanceof SubjectPublicKeyInfo) { + oid = ((SubjectPublicKeyInfo) pemObjectParsed).getAlgorithm().getParameters() + .toString(); + } else if (pemObjectParsed instanceof PEMKeyPair) { + oid = ((PEMKeyPair) pemObjectParsed).getPrivateKeyInfo().getPrivateKeyAlgorithm() + .getParameters().toString(); + } else { + throw new PEMProcessorError(ErrorConstants.DER_TO_PEM_CONVERSION); + } + + if (SECObjectIdentifiers.secp256r1.getId().equals(oid)) { + return AlgorithmEmployed.SECP256R1; + } else if (SECObjectIdentifiers.secp256k1.getId().equals(oid)) { + return AlgorithmEmployed.SECP256K1; + } else { + throw new PEMProcessorError(ErrorConstants.UNSUPPORTED_ALGORITHM + oid); + } + } + + /** + * Gets the key as a byte array from its PEM format. + * + * @return key as byte[] + * @throws PEMProcessorError when key data is unobtainable. + */ + @NotNull + public byte[] getKeyData() throws PEMProcessorError { + + Object pemObjectParsed = parsePEMObject(); + + if (pemObjectParsed instanceof SubjectPublicKeyInfo) { + return ((SubjectPublicKeyInfo) pemObjectParsed).getPublicKeyData().getBytes(); + } else if (pemObjectParsed instanceof PEMKeyPair) { + + DLSequence sequence; + try (ASN1InputStream asn1InputStream = new ASN1InputStream( + Hex.decode(this.getDERFormat()))) { + sequence = (DLSequence) asn1InputStream.readObject(); + } catch (IOException e) { + throw new PEMProcessorError(e); + } + for (Object obj : sequence) { + if (obj instanceof DEROctetString) { + byte[] key = new byte[0]; + try { + key = ((DEROctetString) obj).getEncoded(); + } catch (IOException e) { + throw new PEMProcessorError(e); + } + return Arrays.copyOfRange(key, PRIVATE_KEY_START_INDEX, key.length); + } + } + throw new PEMProcessorError(ErrorConstants.KEY_DATA_NOT_FOUND); + + } else { + throw new PEMProcessorError(ErrorConstants.DER_TO_PEM_CONVERSION); + } + } + + /** + * Extract EOS public key + * + * @param isLegacy - Set to true if the legacy format of the key is desired. This uses "EOS" + * to prefix the key data and only applies to keys generated with the secp256k1 algorithm. The + * new format prefixes the key data with "PUB_K1_". + * @return EOS format public key of the current private key + * @throws PEMProcessorError when the public key extraction fails. + */ + public String extractEOSPublicKeyFromPrivateKey(boolean isLegacy) throws PEMProcessorError { + if (!this.getType().equals(PRIVATE_KEY_TYPE)) { + throw new PEMProcessorError(ErrorConstants.PUBLIC_KEY_COULD_NOT_BE_EXTRACTED_FROM_PRIVATE_KEY); + } + + AlgorithmEmployed keyCurve = this.getAlgorithm(); + BigInteger privateKeyBI = new BigInteger(BIG_INTEGER_POSITIVE, this.getKeyData()); + BigInteger n; + ECPoint g; + + switch (keyCurve) { + case SECP256R1: + n = CURVE_R1.getN(); + g = CURVE_R1.getG(); + break; + + default: + n = CURVE_K1.getN(); + g = CURVE_K1.getG(); + break; + } + + if (privateKeyBI.bitLength() > n.bitLength()) { + privateKeyBI = privateKeyBI.mod(n); + } + + byte[] publicKeyByteArray = new FixedPointCombMultiplier().multiply(g, privateKeyBI).getEncoded(true); + + try { + return EOSFormatter.encodePublicKey(publicKeyByteArray, keyCurve, isLegacy); + } catch (Base58ManipulationError e) { + throw new PEMProcessorError(e); + } + } + + /** + * Extract PEM public key + * + * @param isLegacy Whether to return the legacy format of the key. This uses "EOS" + * to prefix the key data and only applies to keys generated with the secp256k1 algorithm. The + * new format prefixes the key data with "PUB_K1_". + * @return EOS format public key of the current private key + * @throws PEMProcessorError when public key extraction fails. + */ + public String extractPEMPublicKeyFromPrivateKey(boolean isLegacy) throws PEMProcessorError { + try { + return EOSFormatter.convertEOSPublicKeyToPEMFormat(extractEOSPublicKeyFromPrivateKey(isLegacy)); + } catch (EOSFormatterError e) { + throw new PEMProcessorError(e); + } + } + + /** + * Gets EC Curve's domain parameter by curve type + * + * @param curve - type + * @return ECDomainParameters of input curve + * @throws PEMProcessorError would be throw if input curve is not supported. + */ + public static ECDomainParameters getCurveDomainParameters(AlgorithmEmployed curve) throws PEMProcessorError { + switch (curve) { + case SECP256R1: + case PRIME256V1: + return CURVE_R1; + case SECP256K1: + return CURVE_K1; + default: + throw new PEMProcessorError(ErrorConstants.UNSUPPORTED_ALGORITHM); + } + } + + /** + * Parses PEM object. + * + * @return Parsed PEM object as Object. + * @throws PEMProcessorError when PEM parsing fails. + */ + @NotNull + private Object parsePEMObject() throws PEMProcessorError { + try (Reader reader = new CharArrayReader(this.pemObjectString.toCharArray()); + PEMParser pemParser = new PEMParser(reader)) { + return pemParser.readObject(); + } catch (IOException e) { + throw new PEMProcessorError(ErrorConstants.ERROR_READING_PEM_OBJECT, e); + } + } +} diff --git a/app/src/main/java/com/tangem/wallet/eos/utilities/Utils.java b/app/src/main/java/com/tangem/wallet/eos/utilities/Utils.java new file mode 100644 index 0000000000..b5b9e1040f --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/eos/utilities/Utils.java @@ -0,0 +1,48 @@ +package com.tangem.wallet.eos.utilities; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +/** + * This class provides generic utility methods + */ +public class Utils { + + private Utils() {} + + /** + * Clone an object + * + * @param object input object + * @param - Class of the object + * @return the cloned object. + * @throws IOException Any exception thrown by the underlying OutputStream. + * @throws ClassNotFoundException Class of a serialized object cannot be found. + */ + public static T clone(T object) throws IOException, ClassNotFoundException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); + objectOutputStream.writeObject(object); // Could clone only the Transaction (i.e. this.transaction) + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); + ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); + return (T) objectInputStream.readObject(); + } + + /** + * Getting a GSON object with a date time pattern + * @param datePattern - input date time pattern + * @return Configured GSON object with input. + */ + public static Gson getGson(String datePattern) { + return new GsonBuilder() + .setDateFormat(datePattern) + .disableHtmlEscaping() + .create(); + } +} diff --git a/build.gradle b/build.gradle index 712dc864a1..58b21916a3 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.android.tools.build:gradle:3.4.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' }