diff --git a/app/build.gradle b/app/build.gradle index b01d90050c..d29fa74731 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' @@ -134,4 +139,6 @@ dependencies { //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/network/ServerApiEos.java b/app/src/main/java/com/tangem/data/network/ServerApiEos.java index 7b5082bebc..81f666b50b 100644 --- a/app/src/main/java/com/tangem/data/network/ServerApiEos.java +++ b/app/src/main/java/com/tangem/data/network/ServerApiEos.java @@ -2,11 +2,16 @@ package com.tangem.data.network; import android.util.Log; -import com.tangem.wallet.CoinEngine; +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; @@ -27,4 +32,17 @@ public class ServerApiEos { 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/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 cc1af2824a..b289fac550 100644 --- a/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt +++ b/app/src/main/java/com/tangem/wallet/CoinEngineFactory.kt @@ -7,8 +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.EosData -import com.tangem.wallet.EOS.EosEngine +import com.tangem.wallet.eos.EosEngine import com.tangem.wallet.binance.BinanceEngine import com.tangem.wallet.cardano.CardanoData import com.tangem.wallet.cardano.CardanoEngine diff --git a/app/src/main/java/com/tangem/wallet/EOS/EosPackedTransaction.java b/app/src/main/java/com/tangem/wallet/EOS/EosPackedTransaction.java deleted file mode 100644 index 0d55286d8d..0000000000 --- a/app/src/main/java/com/tangem/wallet/EOS/EosPackedTransaction.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.wallet.EOS; - -import io.jafka.jeos.core.common.transaction.PackedTransaction; - -public class EosPackedTransaction extends PackedTransaction { - private Long expirationSec; - - public Long getExpirationSec() { - return expirationSec; - } - - public void setExpirationSec(Long expirationSec) { - this.expirationSec = expirationSec; - } -} diff --git a/app/src/main/java/com/tangem/wallet/EOS/EosPushTransactionRequest.java b/app/src/main/java/com/tangem/wallet/EOS/EosPushTransactionRequest.java deleted file mode 100644 index 0899f6a121..0000000000 --- a/app/src/main/java/com/tangem/wallet/EOS/EosPushTransactionRequest.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.tangem.wallet.EOS; - -import io.jafka.jeos.core.request.chain.transaction.PushTransactionRequest; - -import java.io.Serializable; - -public class EosPushTransactionRequest extends PushTransactionRequest implements Serializable { -} 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 similarity index 85% rename from app/src/main/java/com/tangem/wallet/EOS/EosData.java rename to app/src/main/java/com/tangem/wallet/eos/EosData.java index aee90b0118..dc438865b1 100644 --- a/app/src/main/java/com/tangem/wallet/EOS/EosData.java +++ b/app/src/main/java/com/tangem/wallet/eos/EosData.java @@ -1,4 +1,4 @@ -package com.tangem.wallet.EOS; +package com.tangem.wallet.eos; import android.os.Bundle; import android.util.Log; @@ -6,10 +6,6 @@ import android.util.Log; import com.tangem.wallet.CoinData; import com.tangem.wallet.CoinEngine; -import java.math.BigInteger; - -import io.jafka.jeos.core.request.chain.transaction.PushTransactionRequest; - public class EosData extends CoinData { private CoinEngine.Amount balance = null; @@ -45,7 +41,7 @@ public class EosData extends CoinData { try { if (balance != null) { B.putString("BalanceCurrency", balance.getCurrency()); - B.putString("BalanceDecimal", balance.toString()); + B.putString("BalanceDecimal", balance.toValueString()); } } catch (Exception e) { diff --git a/app/src/main/java/com/tangem/wallet/EOS/EosEngine.java b/app/src/main/java/com/tangem/wallet/eos/EosEngine.java similarity index 73% rename from app/src/main/java/com/tangem/wallet/EOS/EosEngine.java rename to app/src/main/java/com/tangem/wallet/eos/EosEngine.java index f7160a1483..5f44c33db1 100644 --- a/app/src/main/java/com/tangem/wallet/EOS/EosEngine.java +++ b/app/src/main/java/com/tangem/wallet/eos/EosEngine.java @@ -1,20 +1,22 @@ -package com.tangem.wallet.EOS; +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.reader.TLV; import com.tangem.card_common.tasks.SignTask; import com.tangem.card_common.util.Util; import com.tangem.data.Blockchain; import com.tangem.data.network.ServerApiEos; import com.tangem.util.CryptoUtil; import com.tangem.util.DecimalDigitsInputFilter; +import com.tangem.util.DerEncodingUtil; import com.tangem.wallet.BTCUtils; import com.tangem.wallet.BalanceValidator; import com.tangem.wallet.BuildConfig; @@ -22,20 +24,25 @@ 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.joda.time.DateTime; +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; @@ -45,11 +52,9 @@ import io.jafka.jeos.core.common.SignArg; import io.jafka.jeos.core.common.transaction.TransactionAction; import io.jafka.jeos.core.common.transaction.TransactionAuthorization; import io.jafka.jeos.core.request.chain.json2bin.TransferArg; -import io.jafka.jeos.core.request.chain.transaction.PushTransactionRequest; import io.jafka.jeos.core.response.chain.account.Account; import io.jafka.jeos.core.response.chain.transaction.PushedTransaction; import io.jafka.jeos.util.Base58; -import io.jafka.jeos.util.KeyUtil; import io.jafka.jeos.util.Raw; import io.jafka.jeos.util.ecc.Ripemd160; import io.reactivex.Observer; @@ -320,7 +325,7 @@ public class EosEngine extends CoinEngine { public String calculateAddress(byte[] pkCompressed) { String cid = Util.bytesToHex(ctx.getCard().getCID()); String address = "testem" + cid.substring(2, 4) + cid.substring(11, 15);//TODO: change - address = address.replace("0", "o").replace("6","b").replace("7,","t").replace("8","s").replace("9","g"); + 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(); @@ -331,6 +336,15 @@ public class EosEngine extends CoinEngine { // 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 { @@ -340,6 +354,7 @@ public class EosEngine extends CoinEngine { SignArg arg = eosApi.getSignArg(120); System.out.println(eosApi.getObjectMapper().writeValueAsString(arg)); + // --- prepare transaction for sign as in LocalApiImpl String quantity = amountValue.setScale(4).toString(); String memo = "test"; @@ -357,14 +372,14 @@ public class EosEngine extends CoinEngine { new TransactionAction("eosio.token", "transfer", authorizations, transferData)// ); - String blockTime = arg.getHeadBlockTime().toString(); - DateTime time = new DateTime(arg.getHeadBlockTime()).plusSeconds(arg.getExpiredSecond()); - String changedTime = time.toString(); - long epochSecond = time.getMillis() / 1000; + 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.setExpirationSec(epochSecond); + packedTransaction.setExpiration(stringTime); packedTransaction.setRefBlockNum(arg.getLastIrreversibleBlockNum()); packedTransaction.setRefBlockPrefix(arg.getRefBlockPrefix()); @@ -374,7 +389,7 @@ public class EosEngine extends CoinEngine { packedTransaction.setActions(actions); Raw raw = EosPacker.packPackedTransaction(arg.getChainId(), packedTransaction); - raw.pack(ByteBuffer.allocate(33).array());// TODO: what's this? + raw.pack(ByteBuffer.allocate(33).array()); //black magic Sha256Hash hashForSign = Sha256Hash.of(raw.bytes()); return new SignTask.TransactionToSign() { @@ -419,26 +434,49 @@ public class EosEngine extends CoinEngine { 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(r.toByteArray(), 0, pub_buf, 1, r.toByteArray().length); - System.arraycopy(s.toByteArray(), 0, pub_buf, r.toByteArray().length + 1, s.toByteArray().length); + + 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 - byte[] txForSend = SerializationUtils.serialize(req); + String reqString = new Gson().toJson(req); + byte[] txForSend = SerializationUtils.serialize(reqString); notifyOnNeedSendTransaction(txForSend); return txForSend; @@ -455,8 +493,8 @@ public class EosEngine extends CoinEngine { if (k == null) continue; byte[] recK = k.getPubKey(); - Log.e("EOS_k " + String.valueOf(i), BTCUtils.toHex(recK)); - if (k != null && Arrays.equals(recK, thisKey)) { + Log.e("EOS_k " + i, BTCUtils.toHex(recK)); + if (Arrays.equals(recK, thisKey)) { recId = i; break; } @@ -466,52 +504,82 @@ public class EosEngine extends CoinEngine { @Override public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { - Observer accountObserver = new DefaultObserver() { - @Override - public void onNext(Account account) { - String[] balanceStrings = account.getCoreLiquidBalance().split(" "); - coinData.setBalanceReceived(true); - coinData.setBalance(new Amount(balanceStrings[0], balanceStrings[1])); - } + Observer accountObserver = new DefaultObserver() { + @Override + public void onNext(Account account) { + String[] balanceStrings = account.getCoreLiquidBalance().split(" "); + coinData.setBalanceReceived(true); + coinData.setBalance(new Amount(balanceStrings[0], balanceStrings[1])); + } - @Override - public void onError(Throwable e) { - Log.e(TAG, "requestBalanceAndUnspentTransactions error" + e.getMessage()); - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } + @Override + public void 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 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 - EosPushTransactionRequest req = SerializationUtils.deserialize(txForSend); + String reqString = SerializationUtils.deserialize(txForSend); + EosPushTransactionRequest req = new Gson().fromJson(reqString, EosPushTransactionRequest.class); - try { - EosApi eosApi = EosApiFactory.create("https://api.eosdetroit.io:443"); - PushedTransaction pts = eosApi.pushTransaction(req); + 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(); + } + } + } - LocalApi localApi = EosApiFactory.createLocalApi(); - System.out.println(localApi.getObjectMapper().writeValueAsString(pts)); - blockchainRequestsCallbacks.onComplete(true); - } catch (Exception e) { - Log.e(TAG, "requestSendTransaction error" + e.getMessage()); - ctx.setError(e.getMessage()); - blockchainRequestsCallbacks.onComplete(false); - } + @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() { @@ -522,4 +590,15 @@ public class EosEngine extends CoinEngine { 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 similarity index 78% rename from app/src/main/java/com/tangem/wallet/EOS/EosPacker.java rename to app/src/main/java/com/tangem/wallet/eos/EosPacker.java index 4b578c377b..88f1081fb8 100644 --- a/app/src/main/java/com/tangem/wallet/EOS/EosPacker.java +++ b/app/src/main/java/com/tangem/wallet/eos/EosPacker.java @@ -1,4 +1,10 @@ -package com.tangem.wallet.EOS; +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; @@ -12,7 +18,14 @@ public class EosPacker extends Packer { //chain raw.pack(Hex.toBytes(chainId)); //expiration - raw.packUint32(t.getExpirationSec()); + 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 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 ba5d47436c..b257e64bb1 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' }