diff --git a/.idea/misc.xml b/.idea/misc.xml index 703e5d4b89..cd2f3a654d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -5,6 +5,41 @@ + + + + diff --git a/app/build.gradle b/app/build.gradle index c0da9a2b7a..5facfb67ff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -54,6 +54,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' implementation 'androidx.biometric:biometric:1.0.0-alpha03' + implementation 'co.nstant.in:cbor:0.8' implementation 'com.google.android.material:material:1.1.0-alpha02' implementation 'com.google.dagger:dagger:2.21' kapt 'com.google.dagger:dagger-compiler:2.21' @@ -65,6 +66,7 @@ dependencies { implementation 'com.scottyab:rootbeer-lib:0.0.7' implementation 'com.squareup.retrofit2:converter-gson:2.5.0' implementation 'com.squareup.retrofit2:retrofit:2.5.0' + implementation 'com.squareup.retrofit2:converter-scalars:2.1.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' implementation 'com.skyfishjy.ripplebackground:library:1.0.1' implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0' diff --git a/app/src/main/java/com/tangem/data/Blockchain.java b/app/src/main/java/com/tangem/data/Blockchain.java index 9e95661c57..11a9ec45dd 100644 --- a/app/src/main/java/com/tangem/data/Blockchain.java +++ b/app/src/main/java/com/tangem/data/Blockchain.java @@ -16,7 +16,8 @@ public enum Blockchain { BitcoinCash("BCH", "BCH", 100000000.0, R.drawable.ic_logo_bitcoin_cash, "Bitcoin Cash"), Litecoin("LTC", "LTC", 100000000.0, R.drawable.ic_logo_bitcoin, "Litecoin"), Rootstock("RSK", "RBTC", 1.0, R.drawable.ic_logo_bitcoin, "Rootstock"), - RootstockToken("Token", "RBTC", 1.0, R.drawable.ic_logo_bat_token, "Rootstock"); + RootstockToken("Token", "RBTC", 1.0, R.drawable.ic_logo_bat_token, "Rootstock"), + Cardano("CARDANO", "ADA", 1000000.0,R.drawable.ic_logo_bitcoin, "Cardano"); Blockchain(String ID, String currency, double multiplier, int imageResource, String officialName) { mID = ID; diff --git a/app/src/main/java/com/tangem/data/network/AdaliteApi.java b/app/src/main/java/com/tangem/data/network/AdaliteApi.java new file mode 100644 index 0000000000..8af94a92c5 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/AdaliteApi.java @@ -0,0 +1,30 @@ +package com.tangem.data.network; + +import com.tangem.data.network.model.AdaliteBody; +import com.tangem.data.network.model.AdaliteResponse; +import com.tangem.data.network.model.AdaliteResponseUtxo; + +import java.util.List; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.Headers; +import retrofit2.http.POST; +import retrofit2.http.Path; + +public interface AdaliteApi { + @GET(ServerApiAdalite.ADALITE_ADDRESS) + Call adaliteAddress(@Path("address") String address); + + @Headers("Content-Type: application/json") + @POST(ServerApiAdalite.ADALITE_UNSPENT_OUTPUTS) + Call adaliteUnspent(@Body String address); + +// @GET(ServerApiAdalite.ADALITE_TRANSACTION) +// Call adaliteTransaction(@Path("txId") String txId); + + @Headers("Content-Type: application/json") + @POST(ServerApiAdalite.ADALITE_SEND) + Call adaliteSend(@Body AdaliteBody adaliteBody); +} diff --git a/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java b/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java new file mode 100644 index 0000000000..b057787494 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/ServerApiAdalite.java @@ -0,0 +1,144 @@ +package com.tangem.data.network; + +import android.util.Log; + +import com.tangem.data.network.model.AdaliteBody; +import com.tangem.data.network.model.AdaliteResponse; +import com.tangem.data.network.model.AdaliteResponseUtxo; + +import java.util.List; + +import androidx.annotation.NonNull; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.converter.scalars.ScalarsConverterFactory; + +public class ServerApiAdalite { + private static String TAG = ServerApiAdalite.class.getSimpleName(); + + public static final String ADALITE_ADDRESS = "/api/addresses/summary/{address}"; + public static final String ADALITE_UNSPENT_OUTPUTS = "/api/bulk/addresses/utxo"; + // public static final String ADALITE_TRANSACTION = "/api/txs/raw/{txId}}"; + public static final String ADALITE_SEND = "/api/v2/txs/signed"; + + private int requestsCount = 0; + + public static String lastNode; + + public boolean isRequestsSequenceCompleted() { + Log.i(TAG, String.format("isRequestsSequenceCompleted: %s (%d requests left)", String.valueOf(requestsCount <= 0), requestsCount)); + return requestsCount <= 0; + } + + private ResponseListener responseListener; + + public interface ResponseListener { + void onSuccess(String method, AdaliteResponse adaliteResponse); + + void onSuccess(String method, AdaliteResponseUtxo adaliteResponseUtxo); + + void onSuccess(String method, List listResponse); + + void onFail(String method, String message); + } + + public void setResponseListener(ResponseListener listener) { + responseListener = listener; + } + + public void requestData(String method, String wallet, String tx) { + requestsCount++; + String adaliteURL = "https://explorer2.adalite.io"; //TODO: make random selection + this.lastNode = adaliteURL; //TODO: show node instead of URL + + Retrofit retrofitAdalite = new Retrofit.Builder() + .baseUrl(adaliteURL) + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + + AdaliteApi adaliteApi = retrofitAdalite.create(AdaliteApi.class); + + switch (method) { + case ADALITE_ADDRESS: + + Call addressCall = adaliteApi.adaliteAddress(wallet); + addressCall.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.code() == 200) { + requestsCount--; + responseListener.onSuccess(method, response.body()); + Log.i(TAG, "requestData " + method + " onResponse " + response.code()); + } else { + responseListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "requestData " + method + " onResponse " + response.code()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + responseListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); + } + }); + break; + + case ADALITE_UNSPENT_OUTPUTS: + Call outputsCall = adaliteApi.adaliteUnspent("[\"" + wallet + "\"]"); + outputsCall.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.code() == 200) { + requestsCount--; + responseListener.onSuccess(method, response.body()); + Log.i(TAG, "requestData " + method + " onResponse " + response.code()); + } else { + responseListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "requestData " + method + " onResponse " + response.code()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + responseListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); + } + }); + break; + + case ADALITE_SEND: + Call sendCall = adaliteApi.adaliteSend(new AdaliteBody(tx)); + sendCall.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.code() == 200) { + requestsCount--; + responseListener.onSuccess(method, response.body()); + Log.i(TAG, "requestData " + method + " onResponse " + response.code()); + } else { + responseListener.onFail(method, String.valueOf(response.code())); + Log.e(TAG, "requestData " + method + " onResponse " + response.code()); + } + } + + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + responseListener.onFail(method, String.valueOf(t.getMessage())); + Log.e(TAG, "requestData " + method + " onFailure " + t.getMessage()); + } + }); + break; + + default: + responseListener.onFail(method, "undeclared method"); + Log.e(TAG, "requestData " + method + " onFailure - undeclared method"); + break; + } + + } +} + diff --git a/app/src/main/java/com/tangem/data/network/model/AdaliteBody.java b/app/src/main/java/com/tangem/data/network/model/AdaliteBody.java new file mode 100644 index 0000000000..a8208d01cf --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/model/AdaliteBody.java @@ -0,0 +1,9 @@ +package com.tangem.data.network.model; + +public class AdaliteBody { + private String signedTx; + + public AdaliteBody(String signedTx) { + this.signedTx = signedTx; + } +} diff --git a/app/src/main/java/com/tangem/data/network/model/AdaliteResponse.kt b/app/src/main/java/com/tangem/data/network/model/AdaliteResponse.kt new file mode 100644 index 0000000000..f0a3a1ca44 --- /dev/null +++ b/app/src/main/java/com/tangem/data/network/model/AdaliteResponse.kt @@ -0,0 +1,37 @@ +package com.tangem.data.network.model + +import com.google.gson.annotations.SerializedName + +data class AdaliteResponse( + @SerializedName("Right") + var right: AddressData? = null +) + +data class AdaliteResponseUtxo( + @SerializedName("Right") + var right: List +) + +data class AddressData( + @SerializedName("caAddress") + var caAddress: String? = null, + + @SerializedName("caBalance") + var caBalance: AdaliteCoins? = null +) + +data class AdaliteCoins( + @SerializedName("getCoin") + var getCoin: Long? = null +) + +data class UtxoData( + @SerializedName("cuId") + var cuId: String? = null, + + @SerializedName("cuOutIndex") + var cuOutIndex: Int? = null, + + @SerializedName("cuCoins") + var cuCoins: AdaliteCoins? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/wallet/CoinData.java b/app/src/main/java/com/tangem/domain/wallet/CoinData.java index 9447ee6935..9ec1663bdb 100644 --- a/app/src/main/java/com/tangem/domain/wallet/CoinData.java +++ b/app/src/main/java/com/tangem/domain/wallet/CoinData.java @@ -143,7 +143,7 @@ public abstract class CoinData { public void clearInfo() { setIsBalanceEqual(false); - setBalanceReceived(false); // TODO check + setBalanceReceived(false); setValidationNodeDescription(""); minFee=null; maxFee=null; diff --git a/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java b/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java index 19fc78fe8c..caa4efd4f2 100644 --- a/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java @@ -6,6 +6,7 @@ import android.text.InputFilter; import com.tangem.tangemcommon.reader.CardProtocol; import com.tangem.tangemcommon.tasks.SignTask; +import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; @@ -15,6 +16,8 @@ import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.Locale; +import co.nstant.in.cbor.CborException; + /** * Created by Ilia on 15.02.2018. */ @@ -189,7 +192,6 @@ public abstract class CoinEngine { public abstract boolean checkNewTransactionAmount(Amount amount); - // TODO - move minFeeInInternalUnits to CoinData public abstract boolean checkNewTransactionAmountAndFee(Amount amount, Amount fee, Boolean isFeeIncluded); public abstract boolean validateBalance(BalanceValidator balanceValidator); @@ -214,7 +216,7 @@ public abstract class CoinEngine { public abstract boolean validateAddress(String address); - public abstract String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException; + public abstract String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException, CborException, IOException; public abstract Amount convertToAmount(InternalAmount internalAmount) throws Exception; diff --git a/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt b/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt index 9d99f0121f..00e26bc4cb 100644 --- a/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt +++ b/app/src/main/java/com/tangem/domain/wallet/CoinEngineFactory.kt @@ -7,6 +7,7 @@ import com.tangem.domain.wallet.eth.EthEngine import com.tangem.domain.wallet.token.TokenEngine import com.tangem.domain.wallet.bch.BtcCashEngine import com.tangem.data.Blockchain +import com.tangem.domain.wallet.cardano.CardanoEngine import com.tangem.domain.wallet.ltc.LtcEngine import com.tangem.domain.wallet.nftToken.NftTokenEngine import com.tangem.domain.wallet.rsk.RskEngine @@ -33,6 +34,7 @@ object CoinEngineFactory { Blockchain.Litecoin -> LtcEngine() Blockchain.Rootstock -> RskEngine() Blockchain.RootstockToken -> RskTokenEngine() + Blockchain.Cardano -> CardanoEngine() else -> null } } @@ -57,6 +59,8 @@ object CoinEngineFactory { RskEngine(context) else if (Blockchain.RootstockToken == context.blockchain) RskTokenEngine(context) + else if (Blockchain.Cardano == context.blockchain) + CardanoEngine(context) else return null } catch (e: Exception) { diff --git a/app/src/main/java/com/tangem/domain/wallet/bch/BitcoinCashNode.kt b/app/src/main/java/com/tangem/domain/wallet/bch/BitcoinCashNode.kt index 7123f17dd2..66eedee951 100644 --- a/app/src/main/java/com/tangem/domain/wallet/bch/BitcoinCashNode.kt +++ b/app/src/main/java/com/tangem/domain/wallet/bch/BitcoinCashNode.kt @@ -8,8 +8,7 @@ enum class BitcoinCashNode(val host: String, val port: Int, val proto: String) { N_005("bch.crypto.mldlabs.com", 50002, "ssl"), N_006("electron.coinucopia.io", 50002, "ssl"), N_007("blackie.c3-soft.com", 50002, "ssl"), - N_008("electroncash.ueo.ch", 51002, "ssl"), - N_009("electrum.imaginary.cash", 50002, "ssl"), - N_010("bitcoincash.quangld.com", 50002, "ssl"), - N_011("bch.stitthappens.com", 50002, "ssl"), + N_008("electrum.imaginary.cash", 50002, "ssl"), + N_009("bitcoincash.quangld.com", 50002, "ssl"), + N_010("bch.stitthappens.com", 50002, "ssl"), } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/wallet/btc/BitcoinNode.kt b/app/src/main/java/com/tangem/domain/wallet/btc/BitcoinNode.kt index 6a0c66d540..2af5648db2 100644 --- a/app/src/main/java/com/tangem/domain/wallet/btc/BitcoinNode.kt +++ b/app/src/main/java/com/tangem/domain/wallet/btc/BitcoinNode.kt @@ -2,44 +2,31 @@ package com.tangem.domain.wallet.btc enum class BitcoinNode(val host: String, val port: Int, val proto: String) { N_001("electrum.anduck.net", 50001, "tcp"), - N_002("ip119.ip-54-37-91.eu", 50001, "tcp"), - N_003("electrum.qtornado.com", 50001, "tcp"), - N_004("ip239.ip-54-36-234.eu", 50001, "tcp"), - N_005("electrum-server.ninja", 50001, "tcp"), - N_006("vps.hsmiths.com", 8080, "tcp"), - N_007("ip120.ip-54-37-91.eu", 50001, "tcp"), - N_008("ip101.ip-54-37-91.eu", 50001, "tcp"), - N_009("e-x.not.fyi", 50001, "tcp"), - N_010("electrum.vom-stausee.de", 50001, "tcp"), - N_011("electrum2.eff.ro", 50001, "tcp"), - N_012("electrum.coinucopia.io", 50001, "tcp"), - N_013("b.ooze.cc", 50002, "ssl"), - N_014("electrum.nute.net", 50002, "ssl"), - N_015("electrum.coinop.cc", 50002, "ssl"), - N_016("electrum.vom-stausee.de", 50002, "ssl"), - N_017("ip119.ip-54-37-91.eu", 50002, "ssl"), - N_018("ip101.ip-54-37-91.eu", 50002, "ssl"), - N_019("dedi.jochen-hoenicke.de", 50002, "ssl"), - N_020("ip239.ip-54-36-234.eu", 50002, "ssl"), - N_021("e-x.not.fyi", 50002, "ssl"), - N_022("ip120.ip-54-37-91.eu", 50002, "ssl"), - N_023("electrum.villocq.com", 50002, "ssl"), - N_024("electrum.anduck.net", 50012, "ssl"), - N_025("technetium.network", 50002, "ssl"), - N_026("electrum.coinucopia.io", 50002, "ssl"), - N_027("dimon.trimon.de", 50002, "ssl"), - N_028("btc.gravitech.net", 50002, "ssl"), - N_029("hetzner01.fischl-online.de", 50002, "ssl"), - N_030("fn.48.org", 50002, "ssl"), - N_031("185.64.116.15", 50002, "ssl"), - N_032("vps.hsmiths.com", 50002, "ssl"), - N_033("104.250.141.242", 50002, "ssl"), - N_034("electrum.qtornado.com", 50002, "ssl"), - N_035("electrum-server.ninja", 50002, "ssl"), - N_036("electrum2.eff.ro", 50002, "ssl"), - N_037("electrum.hsmiths.com", 995, "ssl"), - N_038("electrum.hsmiths.com", 50002, "ssl"), - N_039("139.162.14.142", 50002, "ssl"), - N_040("tardis.bauerj.eu", 50002, "ssl"), - N_041("such.ninja", 50002, "ssl"), + N_002("electrum.qtornado.com", 50001, "tcp"), + N_003("e-x.not.fyi", 50001, "tcp"), + N_004("electrum.vom-stausee.de", 50001, "tcp"), + N_005("electrum2.eff.ro", 50001, "tcp"), + N_006("electrum.coinucopia.io", 50001, "tcp"), + N_007("electrum.nute.net", 50002, "ssl"), + N_008("electrum.coinop.cc", 50002, "ssl"), + N_009("electrum.vom-stausee.de", 50002, "ssl"), + N_010("dedi.jochen-hoenicke.de", 50002, "ssl"), + N_011("e-x.not.fyi", 50002, "ssl"), + N_012("ip120.ip-54-37-91.eu", 50002, "ssl"), + N_013("electrum.villocq.com", 50002, "ssl"), + N_014("electrum.anduck.net", 50012, "ssl"), + N_015("technetium.network", 50002, "ssl"), + N_016("electrum.coinucopia.io", 50002, "ssl"), + N_017("dimon.trimon.de", 50002, "ssl"), + N_018("btc.gravitech.net", 50002, "ssl"), + N_019("hetzner01.fischl-online.de", 50002, "ssl"), + N_020("fn.48.org", 50002, "ssl"), + N_021("vps.hsmiths.com", 50002, "ssl"), + N_022("electrum.qtornado.com", 50002, "ssl"), + N_023("electrum-server.ninja", 50002, "ssl"), + N_024("electrum2.eff.ro", 50002, "ssl"), + N_025("electrum.hsmiths.com", 995, "ssl"), + N_026("electrum.hsmiths.com", 50002, "ssl"), + N_027("139.162.14.142", 50002, "ssl"), + N_028("tardis.bauerj.eu", 50002, "ssl"), } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java b/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java index b2809112fe..8c63bb608d 100644 --- a/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/btc/BtcEngine.java @@ -195,7 +195,6 @@ public class BtcEngine extends CoinEngine { @Override public Uri getShareWalletUri() { - byte[] x = new byte[] {0,0,0,0,0,0,0,0}; if (ctx.getCard().getDenomination() != null && !ctx.getCard().getDenominationText().equals("0.00")) { return Uri.parse("bitcoin:" + ctx.getCoinData().getWallet() + "?amount=" + convertToAmount(convertToInternalAmount(ctx.getCard().getDenomination())).toValueString(8)); } else { diff --git a/app/src/main/java/com/tangem/domain/wallet/cardano/Blake2b.java b/app/src/main/java/com/tangem/domain/wallet/cardano/Blake2b.java new file mode 100644 index 0000000000..b50c045435 --- /dev/null +++ b/app/src/main/java/com/tangem/domain/wallet/cardano/Blake2b.java @@ -0,0 +1,2098 @@ +/* !!! Doost !!! */ + +/* + A Java implementation of BLAKE2B cryptographic digest algorithm. + + Joubin Mohammad Houshyar + bushwick, nyc + 02-14-2014 + + -- + + To the extent possible under law, the author(s) have dedicated all copyright + and related and neighboring rights to this software to the public domain + worldwide. This software is distributed without any warranty. + + You should have received a copy of the CC0 Public Domain Dedication along with + this software. If not, see . +*/ + +package com.tangem.domain.wallet.cardano; + +import java.io.PrintStream; +import java.io.Serializable; +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; // JCE not supported / anticipated .. +import java.util.Arrays; + +import static com.tangem.domain.wallet.cardano.Blake2b.Engine.Assert.assertFail; +import static com.tangem.domain.wallet.cardano.Blake2b.Engine.Assert.exclusiveLowerBound; +import static com.tangem.domain.wallet.cardano.Blake2b.Engine.Assert.inclusiveLowerBound; +import static com.tangem.domain.wallet.cardano.Blake2b.Engine.Assert.inclusiveUpperBound; +import static com.tangem.domain.wallet.cardano.Blake2b.Engine.LittleEndian.readInt; +import static com.tangem.domain.wallet.cardano.Blake2b.Engine.LittleEndian.readLong; +import static com.tangem.domain.wallet.cardano.Blake2b.Engine.LittleEndian.writeInt; +import static com.tangem.domain.wallet.cardano.Blake2b.Engine.LittleEndian.writeLong; + + + +/** */ +public interface Blake2b { + // --------------------------------------------------------------------- + // Specification + // --------------------------------------------------------------------- + public interface Spec { + /** pblock size of blake2b */ + int param_bytes = 64; + + /** pblock size of blake2b */ + int block_bytes = 128; + + /** maximum digest size */ + int max_digest_bytes = 64; + + /** maximum key sie */ + int max_key_bytes = 64; + + /** maximum salt size */ + int max_salt_bytes = 16; + + /** maximum personalization string size */ + int max_personalization_bytes = 16; + + /** length of h space vector array */ + int state_space_len = 8; + + /** max tree fanout value */ + int max_tree_fantout = 0xFF; + + /** max tree depth value */ + int max_tree_depth = 0xFF; + + /** max tree leaf length value.Note that this has uint32 semantics + and thus 0xFFFFFFFF is used as max value limit. */ + int max_tree_leaf_length = 0xFFFFFFFF; + + /** max node offset value. Note that this has uint64 semantics + and thus 0xFFFFFFFFFFFFFFFFL is used as max value limit. */ + long max_node_offset = 0xFFFFFFFFFFFFFFFFL; + + /** max tree inner length value */ + int max_tree_inner_length = 0xFF; + + /** initialization values map ref-Spec IV[i] -> slice iv[i*8:i*8+7] */ + long[] IV = { + 0x6a09e667f3bcc908L, + 0xbb67ae8584caa73bL, + 0x3c6ef372fe94f82bL, + 0xa54ff53a5f1d36f1L, + 0x510e527fade682d1L, + 0x9b05688c2b3e6c1fL, + 0x1f83d9abfb41bd6bL, + 0x5be0cd19137e2179L + }; + } + + // --------------------------------------------------------------------- + // API + // --------------------------------------------------------------------- + // TODO add ByteBuffer variants + + /** + * A serializable / JSON-izable object usable for pausing a hash-in-process + * which can then be resumed with the same Parameter the original digest + * was constructed with, and fed additional bytes to conclude the hash. + */ + public static final class ResumeHandle implements Serializable { + /** per spec */ + public long[] h = new long [ 8 ]; + /** per spec */ + public long[] t = new long [ 2 ]; + /** per spec */ + public long[] f = new long [ 2 ]; + /** per spec (tree) */ + public boolean last_node = false; + /** pulled up 2b optimal */ + public long[] m = new long [16]; + /** pulled up 2b optimal */ + public long[] v = new long [16]; + + /** compressor cache buffer */ + public byte[] buffer; + /** compressor cache buffer offset/cached data length */ + public int buflen; + + /** digest length from init param - copied here on init */ + public int outlen; + + public int type; + + /** + * Create a reconstituted Black2b digest from + * + * @param param + * @return + */ + public Blake2b resume(Param param) { + assert this.buffer != null && this.buffer.length == Spec.block_bytes; + assert this.h != null && this.h.length == 8 + && this.t != null && this.t.length == 2 + && this.f != null && this.f.length == 2 + && this.m != null && this.m.length == 16 + && this.v != null && this.v.length == 16 : "Data is corrupted"; + assert this.outlen == param.getDigestLength() : "Not originally initialized from this param"; + assert this.type == 1 || this.type == 2 : "Unknown type " + this.type; + Engine.State state = new Engine.State(outlen, this.type == 1); + System.arraycopy(this.h, 0, state.h, 0, state.h.length); + System.arraycopy(this.t, 0, state.t, 0, state.t.length); + System.arraycopy(this.f, 0, state.f, 0, state.f.length); + System.arraycopy(this.m, 0, m, 0, m.length); + System.arraycopy(this.v, 0, v, 0, v.length); + System.arraycopy(this.buffer, 0, state.buffer, 0, state.buffer.length); + state.buflen = buflen; + return type == 1 ? new Mac(param, state) : new Digest(param, state); + } + } + + /** */ + void update(byte[] input) ; + + /** */ + void update(byte input) ; + + /** */ + void update(byte[] input, int offset, int len) ; + + /** */ + byte[] digest() ; + + /** */ + byte[] digest(byte[] input) ; + + /** */ + void digest(byte[] output, int offset, int len) ; + + /** */ + void reset() ; + + ResumeHandle state(); + // --------------------------------------------------------------------- + // Blake2b Message Digest + // --------------------------------------------------------------------- + + /** Generalized Blake2b digest. */ + public static class Digest extends Engine implements Blake2b { + private Digest (final Param p) { super (p); } + private Digest () { super (); } + private Digest(Param p, State state) { + super(state, p); + } + + public static Digest newInstance () { + return new Digest (); + } + public static Digest newInstance (final int digestLength) { + return new Digest (new Param().setDigestLength(digestLength)); + } + public static Digest newInstance (Param p) { + return new Digest (p); + } + public static Digest newInstance (Param p, State state) { + return new Digest(p, state); + } + } + + // --------------------------------------------------------------------- + // Blake2b Message Authentication Code + // --------------------------------------------------------------------- + + /** Message Authentication Code (MAC) digest. */ + public static class Mac extends Engine implements Blake2b { + private Mac (final Param p, State state) { super (state, p); } + private Mac (final Param p) { super (p); } + private Mac () { super (); } + + /** Blake2b.MAC 512 - using default Blake2b.Spec settings with given key */ + public static Mac newInstance (final byte[] key) { + return new Mac (new Param().setKey(key)); + } + /** Blake2b.MAC - using default Blake2b.Spec settings with given key, with given digest length */ + public static Mac newInstance (final byte[] key, final int digestLength) { + return new Mac (new Param().setKey(key).setDigestLength(digestLength)); + } + /** Blake2b.MAC - using default Blake2b.Spec settings with given java.security.Key, with given digest length */ + public static Mac newInstance (final Key key, final int digestLength) { + return new Mac (new Param().setKey(key).setDigestLength(digestLength)); + } + /** Blake2b.MAC - using the specified Parameters. + * @param p asserted valid configured Param with key */ + public static Mac newInstance (Param p) { + assert p != null : "Param (p) is null"; + assert p.hasKey() : "Param (p) not configured with a key"; + return new Mac (p); + } + } + + // --------------------------------------------------------------------- + // Blake2b Incremental Message Digest (Tree) + // --------------------------------------------------------------------- + + /** + * Note that Tree is just a convenience class; incremental hash (tree) + * can be done directly with the Digest class. + *
+ * Further node, that tree does NOT accumulate the leaf hashes -- + * you need to do that + */ + public static class Tree { + + final int depth; + final int fanout; + final int leaf_length; + final int inner_length; + final int digest_length; + + /** + * + * @param fanout + * @param depth + * @param leaf_length size of data input for leaf nodes. + * @param inner_length note this is used also as digest-length for non-root nodes. + * @param digest_length final hash out digest-length for the tree + */ + public Tree ( + final int depth, + final int fanout, + final int leaf_length, + final int inner_length, + final int digest_length + ) { + this.fanout = fanout; + this.depth = depth; + this.leaf_length = leaf_length; + this.inner_length = inner_length; + this.digest_length = digest_length; + } + private Param treeParam() { + return new Param(). + setDepth(depth).setFanout(fanout).setLeafLength(leaf_length).setInnerLength(inner_length); + } + /** returns the Digest for tree node @ (depth, offset) */ + public final Digest getNode (final int depth, final int offset) { + final Param nodeParam = treeParam().setNodeDepth(depth).setNodeOffset(offset).setDigestLength(inner_length); + return Digest.newInstance(nodeParam); + } + /** returns the Digest for root node */ + public final Digest getRoot () { + final int depth = this.depth - 1; + final Param rootParam = treeParam().setNodeDepth(depth).setNodeOffset(0L).setDigestLength(digest_length); + return Digest.newInstance(rootParam); + } + } + + // --------------------------------------------------------------------- + // Engine + // --------------------------------------------------------------------- + static class Engine implements Blake2b { + + // --------------------------------------------------------------------- + // Blake2b State(+) per reference implementation + // --------------------------------------------------------------------- + // REVU: address last_node TODO part of the Tree/incremental + static final class State { + /** per spec */ + private final long[] h = new long [ 8 ]; + /** per spec */ + private final long[] t = new long [ 2 ]; + /** per spec */ + private final long[] f = new long [ 2 ]; + /** per spec (tree) */ + private boolean last_node = false; + /** pulled up 2b optimal */ + private final long[] m = new long [16]; + /** pulled up 2b optimal */ + private final long[] v = new long [16]; + + /** compressor cache buffer */ + private final byte[] buffer; + /** compressor cache buffer offset/cached data length */ + private int buflen; + + /** digest length from init param - copied here on init */ + private final int outlen; + + private final int digestType; + + State(int digestLength, boolean isMac) { + this.buffer = new byte [ Spec.block_bytes ]; + this.outlen = digestLength; + // do not use zero, so we can detect serialization errors + this.digestType = isMac ? 1 : 2; + } + + public ResumeHandle toResumableForm() { + ResumeHandle state = new ResumeHandle(); + state.h = h; + state.t = t; + state.f = f; + state.last_node = last_node; + state.m = m; + state.v = v; + state.buffer = buffer; + state.buflen = buflen; + state.outlen = outlen; + state.type = digestType; + return state; + } + } + + private State state; + /** configuration params */ + private final Param param; + + /** read only */ + private static byte[] zeropad = new byte [ Spec.block_bytes ]; + + /** a little bit of semantics */ + interface flag { + int last_block = 0; + int last_node = 1; + } + /** to support update(byte) */ + private final byte[] oneByte = new byte[1]; + + // --------------------------------------------------------------------- + // Ctor & Initialization + // --------------------------------------------------------------------- + + /** Basic use constructor pending (TODO) JCA/JCE compliance */ + Engine () { + this( new Param() ); + } + + Engine(State state, Param param) { + assert state != null : "state is null"; + assert param != null : "param is null"; + this.state = state; + this.param = param; + } + + /** User provided Param for custom configurations */ + Engine (final Param param) { + assert param != null : "param is null"; + this.param = param; + state = new State(param.getDigestLength(), this instanceof Mac); + if ( param.getDepth() > Param.Default.depth ) { + final int ndepth = param.getNodeDepth(); + final long nxoff = param.getNodeOffset(); + if (ndepth == param.getDepth() - 1) { + state.last_node = true; + assert nxoff == 0 : "root must have offset of zero"; + } else if ( nxoff == param.getFanout() - 1) { + this.state.last_node = true; + } + } + + initialize(); + } + + public ResumeHandle state() { + return state.toResumableForm(); + } + + private void initialize () { + // state vector h - copy values to address reset() requests + System.arraycopy( param.initialized_H(), 0, this.state.h, 0, Spec.state_space_len); + + // if we have a key update initial block + // Note param has zero padded key_bytes to Spec.max_key_bytes + if(param.hasKey){ + this.update (param.key_bytes, 0, Spec.block_bytes); + } + } + + public static void main(String... args) { + Blake2b mac = Mac.newInstance("LOVE".getBytes()); + final byte[] hash = mac.digest("Salaam!".getBytes()); +// Debug.dumpBuffer(System.out, "-- mac hash --", hash); + } + + // --------------------------------------------------------------------- + // interface: Blake2b API + // --------------------------------------------------------------------- + + /** {@inheritDoc} */ + @Override final public void reset () { + // reset cache + this.state.buflen = 0; + for(int i=0; i < state.buffer.length; i++){ + state.buffer[ i ] = (byte) 0; + } + + // reset flags + this.state.f[ 0 ] = 0L; + this.state.f[ 1 ] = 0L; + + // reset counters + this.state.t[ 0 ] = 0L; + this.state.t[ 1 ] = 0L; + + // reset state vector + // NOTE: keep as last stmt as init calls update0 for MACs. + initialize(); + } + + /** {@inheritDoc} */ + @Override final public void update (final byte[] b, int off, int len) { + if (b == null) { + throw new IllegalArgumentException("input buffer (b) is null"); + } + /* zero or more calls to compress */ + final long[] t = state.t; + final byte[] buffer = state.buffer; + while (len > 0) { + if ( state.buflen == 0) { + /* try compressing direct from input ? */ + while ( len > Spec.block_bytes ) { + t[0] += Spec.block_bytes; + t[1] += (t[0] < 0 && state.buflen > -t[0]) ? 1 : 0; + compress( b, off); + len -= Spec.block_bytes; + off += Spec.block_bytes; + } + } else if ( state.buflen == Spec.block_bytes ) { + /* flush */ + t[0] += Spec.block_bytes; + t[1] += t[0] == 0 ? 1 : 0; + compress( buffer, 0 ); + state.buflen = 0; + continue; + } + + // "are we there yet?" + if( len == 0 ) return; + + final int cap = Spec.block_bytes - state.buflen; + final int fill = len > cap ? cap : len; + System.arraycopy( b, off, buffer, state.buflen, fill ); + state.buflen += fill; + len -= fill; + off += fill; + } + } + + /** {@inheritDoc} */ + @Override final public void update (byte b) { + oneByte[0] = b; + update (oneByte, 0, 1); + } + + /** {@inheritDoc} */ + @Override final public void update(byte[] input) { + update (input, 0, input.length); + } + + /** {@inheritDoc} */ + @Override final public void digest(byte[] output, int off, int len) { + // zero pad last block; set last block flags; and compress + System.arraycopy( zeropad, 0, state.buffer, state.buflen, Spec.block_bytes - state.buflen); + if(state.buflen > 0) { + this.state.t[0] += state.buflen; + this.state.t[1] += this.state.t[0] == 0 ? 1 : 0; + } + + this.state.f[ flag.last_block ] = 0xFFFFFFFFFFFFFFFFL; + this.state.f[ flag.last_node ] = this.state.last_node ? 0xFFFFFFFFFFFFFFFFL : 0x0L; + + // compres and write final out (truncated to len) to output + compress( state.buffer, 0 ); + hashout( output, off, len ); + + reset(); + } + + /** {@inheritDoc} */ + @Override final public byte[] digest () throws IllegalArgumentException { + final byte[] out = new byte [state.outlen]; + digest ( out, 0, state.outlen ); + return out; + } + + /** {@inheritDoc} */ + @Override final public byte[] digest (byte[] input) { + update(input, 0, input.length); + return digest(); + } + + // --------------------------------------------------------------------- + // Internal Ops + // --------------------------------------------------------------------- + + /** + * write out the digest output from the 'h' registers. + * truncate full output if necessary. + */ + private void hashout (final byte[] out, final int offset, final int hashlen) { + // write max number of whole longs + final int lcnt = hashlen >>> 3; + long v = 0; + int i = offset; + final long[] h = state.h; + for (int w = 0; w < lcnt; w++) { + v = h [ w ]; + out [ i ] = (byte) v; v >>>= 8; + out [ i+1 ] = (byte) v; v >>>= 8; + out [ i+2 ] = (byte) v; v >>>= 8; + out [ i+3 ] = (byte) v; v >>>= 8; + out [ i+4 ] = (byte) v; v >>>= 8; + out [ i+5 ] = (byte) v; v >>>= 8; + out [ i+6 ] = (byte) v; v >>>= 8; + out [ i+7 ] = (byte) v; + i+=8; + } + + // basta? + if( hashlen == Spec.max_digest_bytes) return; + + // write the remaining bytes of a partial long value + v = h [ lcnt ]; + i = lcnt << 3; + while( i < hashlen ) { + out [ offset + i ] = (byte) v; v >>>= 8; ++i; + } + } + + //////////////////////////////////////////////////////////////////////// + /// Compression Kernel /////////////////////////////////////////// BEGIN + //////////////////////////////////////////////////////////////////////// + + /** compress Spec.block_bytes data from b, from offset */ + private void compress (final byte[] b, final int offset) { + + // set m registers + final long[] m = state.m; + m[ 0] = LittleEndian.readLong(b, offset); + m[ 1] = LittleEndian.readLong(b, offset + 8); + m[ 2] = LittleEndian.readLong(b, offset + 16); + m[ 3] = LittleEndian.readLong(b, offset + 24); + m[ 4] = LittleEndian.readLong(b, offset + 32); + m[ 5] = LittleEndian.readLong(b, offset + 40); + m[ 6] = LittleEndian.readLong(b, offset + 48); + m[ 7] = LittleEndian.readLong(b, offset + 56); + m[ 8] = LittleEndian.readLong(b, offset + 64); + m[ 9] = LittleEndian.readLong(b, offset + 72); + m[10] = LittleEndian.readLong(b, offset + 80); + m[11] = LittleEndian.readLong(b, offset + 88); + m[12] = LittleEndian.readLong(b, offset + 96); + m[13] = LittleEndian.readLong(b, offset + 104); + m[14] = LittleEndian.readLong(b, offset + 112); + m[15] = LittleEndian.readLong(b, offset + 120); + + // set v registers + final long[] v = state.v; + final long[] h = state.h; + final long[] t = state.t; + final long[] f = state.f; + v[ 0] = h[0]; + v[ 1] = h[1]; + v[ 2] = h[2]; + v[ 3] = h[3]; + v[ 4] = h[4]; + v[ 5] = h[5]; + v[ 6] = h[6]; + v[ 7] = h[7]; + v[ 8] = 0x6a09e667f3bcc908L; + v[ 9] = 0xbb67ae8584caa73bL; + v[10] = 0x3c6ef372fe94f82bL; + v[11] = 0xa54ff53a5f1d36f1L; + v[12] = t [0] ^ 0x510e527fade682d1L; + v[13] = t [1] ^ 0x9b05688c2b3e6c1fL; + v[14] = f [0] ^ 0x1f83d9abfb41bd6bL; + v[15] = f [1] ^ 0x5be0cd19137e2179L; + + // do the rounds + round_0(v, m); + round_1(v, m); + round_2(v, m); + round_3(v, m); + round_4(v, m); + round_5(v, m); + round_6(v, m); + round_7(v, m); + round_8(v, m); + round_9(v, m); + round_0(v, m); // round 10 is identical to round 0 + round_1(v, m); // round 11 is identical to round 1 + + // Update state vector h + h[0] ^= v[0] ^ v[8]; + h[1] ^= v[1] ^ v[9]; + h[2] ^= v[2] ^ v[10]; + h[3] ^= v[3] ^ v[11]; + h[4] ^= v[4] ^ v[12]; + h[5] ^= v[5] ^ v[13]; + h[6] ^= v[6] ^ v[14]; + h[7] ^= v[7] ^ v[15]; + + /* kaamil */ + } + private void round_0(final long[] v, final long[] m) { + v[ 0] = v[ 0] + v[ 4] + m[0]; + v[12] ^= v[ 0]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 0] = v[ 0] + v[ 4] + m [1]; + v[12] ^= v[ 0]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + + v[ 1] = v[ 1] + v[ 5] + m[2]; + v[13] ^= v[ 1]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 1] = v[ 1] + v[ 5] + m[3]; + v[13] ^= v[ 1]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 2] = v[ 2] + v[ 6] + m[4]; + v[14] ^= v[ 2]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 2] = v[ 2] + v[ 6] + m[5]; + v[14] ^= v[ 2]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 3] = v[ 3] + v[ 7] + m[6]; + v[15] ^= v[ 3]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 3] = v[ 3] + v[ 7] + m[7]; + v[15] ^= v[ 3]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 0] = v[ 0] + v[ 5] + m[8]; + v[15] ^= v[ 0]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 0] = v[ 0] + v[ 5] + m[9]; + v[15] ^= v[ 0]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 1] = v[ 1] + v[ 6] + m[10]; + v[12] ^= v[ 1]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 1] = v[ 1] + v[ 6] + + m[11]; + v[12] ^= v[ 1]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 2] = v[ 2] + v[ 7] + m[12]; + v[13] ^= v[ 2]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 2] = v[ 2] + v[ 7] + m[13]; + v[13] ^= v[ 2]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 3] = v[ 3] + v[ 4] + m[14]; + v[14] ^= v[ 3]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 3] = v[ 3] + v[ 4] + m[15]; + v[14] ^= v[ 3]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + } + private void round_1(final long[] v, final long[] m) { + v[ 0] = v[ 0] + v[ 4] + m[14]; + v[12] ^= v[ 0]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 0] = v[ 0] + v[ 4] + m [10]; + v[12] ^= v[ 0]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + + v[ 1] = v[ 1] + v[ 5] + m[4]; + v[13] ^= v[ 1]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 1] = v[ 1] + v[ 5] + m[8]; + v[13] ^= v[ 1]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 2] = v[ 2] + v[ 6] + m[9]; + v[14] ^= v[ 2]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 2] = v[ 2] + v[ 6] + m[15]; + v[14] ^= v[ 2]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 3] = v[ 3] + v[ 7] + m[13]; + v[15] ^= v[ 3]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 3] = v[ 3] + v[ 7] + m[6]; + v[15] ^= v[ 3]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 0] = v[ 0] + v[ 5] + m[1]; + v[15] ^= v[ 0]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 0] = v[ 0] + v[ 5] + m[12]; + v[15] ^= v[ 0]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 1] = v[ 1] + v[ 6] + m[0]; + v[12] ^= v[ 1]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 1] = v[ 1] + v[ 6] + + m[2]; + v[12] ^= v[ 1]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 2] = v[ 2] + v[ 7] + m[11]; + v[13] ^= v[ 2]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 2] = v[ 2] + v[ 7] + m[7]; + v[13] ^= v[ 2]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 3] = v[ 3] + v[ 4] + m[5]; + v[14] ^= v[ 3]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 3] = v[ 3] + v[ 4] + m[3]; + v[14] ^= v[ 3]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + } + private void round_2(final long[] v, final long[] m) { + v[ 0] = v[ 0] + v[ 4] + m[11]; + v[12] ^= v[ 0]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 0] = v[ 0] + v[ 4] + m [8]; + v[12] ^= v[ 0]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + + v[ 1] = v[ 1] + v[ 5] + m[12]; + v[13] ^= v[ 1]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 1] = v[ 1] + v[ 5] + m[0]; + v[13] ^= v[ 1]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 2] = v[ 2] + v[ 6] + m[5]; + v[14] ^= v[ 2]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 2] = v[ 2] + v[ 6] + m[2]; + v[14] ^= v[ 2]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 3] = v[ 3] + v[ 7] + m[15]; + v[15] ^= v[ 3]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 3] = v[ 3] + v[ 7] + m[13]; + v[15] ^= v[ 3]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 0] = v[ 0] + v[ 5] + m[10]; + v[15] ^= v[ 0]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 0] = v[ 0] + v[ 5] + m[14]; + v[15] ^= v[ 0]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 1] = v[ 1] + v[ 6] + m[3]; + v[12] ^= v[ 1]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 1] = v[ 1] + v[ 6] + + m[6]; + v[12] ^= v[ 1]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 2] = v[ 2] + v[ 7] + m[7]; + v[13] ^= v[ 2]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 2] = v[ 2] + v[ 7] + m[1]; + v[13] ^= v[ 2]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 3] = v[ 3] + v[ 4] + m[9]; + v[14] ^= v[ 3]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 3] = v[ 3] + v[ 4] + m[4]; + v[14] ^= v[ 3]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + } + private void round_3(final long[] v, final long[] m) { + v[ 0] = v[ 0] + v[ 4] + m[7]; + v[12] ^= v[ 0]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 0] = v[ 0] + v[ 4] + m [9]; + v[12] ^= v[ 0]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + + v[ 1] = v[ 1] + v[ 5] + m[3]; + v[13] ^= v[ 1]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 1] = v[ 1] + v[ 5] + m[1]; + v[13] ^= v[ 1]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 2] = v[ 2] + v[ 6] + m[13]; + v[14] ^= v[ 2]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 2] = v[ 2] + v[ 6] + m[12]; + v[14] ^= v[ 2]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 3] = v[ 3] + v[ 7] + m[11]; + v[15] ^= v[ 3]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 3] = v[ 3] + v[ 7] + m[14]; + v[15] ^= v[ 3]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 0] = v[ 0] + v[ 5] + m[2]; + v[15] ^= v[ 0]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 0] = v[ 0] + v[ 5] + m[6]; + v[15] ^= v[ 0]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 1] = v[ 1] + v[ 6] + m[5]; + v[12] ^= v[ 1]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 1] = v[ 1] + v[ 6] + + m[10]; + v[12] ^= v[ 1]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 2] = v[ 2] + v[ 7] + m[4]; + v[13] ^= v[ 2]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 2] = v[ 2] + v[ 7] + m[0]; + v[13] ^= v[ 2]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 3] = v[ 3] + v[ 4] + m[15]; + v[14] ^= v[ 3]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 3] = v[ 3] + v[ 4] + m[8]; + v[14] ^= v[ 3]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + } + private void round_4(final long[] v, final long[] m) { + v[ 0] = v[ 0] + v[ 4] + m[9]; + v[12] ^= v[ 0]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 0] = v[ 0] + v[ 4] + m [0]; + v[12] ^= v[ 0]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + + v[ 1] = v[ 1] + v[ 5] + m[5]; + v[13] ^= v[ 1]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 1] = v[ 1] + v[ 5] + m[7]; + v[13] ^= v[ 1]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 2] = v[ 2] + v[ 6] + m[2]; + v[14] ^= v[ 2]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 2] = v[ 2] + v[ 6] + m[4]; + v[14] ^= v[ 2]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 3] = v[ 3] + v[ 7] + m[10]; + v[15] ^= v[ 3]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 3] = v[ 3] + v[ 7] + m[15]; + v[15] ^= v[ 3]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 0] = v[ 0] + v[ 5] + m[14]; + v[15] ^= v[ 0]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 0] = v[ 0] + v[ 5] + m[1]; + v[15] ^= v[ 0]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 1] = v[ 1] + v[ 6] + m[11]; + v[12] ^= v[ 1]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 1] = v[ 1] + v[ 6] + + m[12]; + v[12] ^= v[ 1]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 2] = v[ 2] + v[ 7] + m[6]; + v[13] ^= v[ 2]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 2] = v[ 2] + v[ 7] + m[8]; + v[13] ^= v[ 2]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 3] = v[ 3] + v[ 4] + m[3]; + v[14] ^= v[ 3]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 3] = v[ 3] + v[ 4] + m[13]; + v[14] ^= v[ 3]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + } + private void round_5(final long[] v, final long[] m) { + v[ 0] = v[ 0] + v[ 4] + m[2]; + v[12] ^= v[ 0]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 0] = v[ 0] + v[ 4] + m [12]; + v[12] ^= v[ 0]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + + v[ 1] = v[ 1] + v[ 5] + m[6]; + v[13] ^= v[ 1]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 1] = v[ 1] + v[ 5] + m[10]; + v[13] ^= v[ 1]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 2] = v[ 2] + v[ 6] + m[0]; + v[14] ^= v[ 2]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 2] = v[ 2] + v[ 6] + m[11]; + v[14] ^= v[ 2]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 3] = v[ 3] + v[ 7] + m[8]; + v[15] ^= v[ 3]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 3] = v[ 3] + v[ 7] + m[3]; + v[15] ^= v[ 3]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 0] = v[ 0] + v[ 5] + m[4]; + v[15] ^= v[ 0]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 0] = v[ 0] + v[ 5] + m[13]; + v[15] ^= v[ 0]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 1] = v[ 1] + v[ 6] + m[7]; + v[12] ^= v[ 1]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 1] = v[ 1] + v[ 6] + + m[5]; + v[12] ^= v[ 1]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 2] = v[ 2] + v[ 7] + m[15]; + v[13] ^= v[ 2]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 2] = v[ 2] + v[ 7] + m[14]; + v[13] ^= v[ 2]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 3] = v[ 3] + v[ 4] + m[1]; + v[14] ^= v[ 3]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 3] = v[ 3] + v[ 4] + m[9]; + v[14] ^= v[ 3]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + } + private void round_6(final long[] v, final long[] m) { + v[ 0] = v[ 0] + v[ 4] + m[12]; + v[12] ^= v[ 0]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 0] = v[ 0] + v[ 4] + m [5]; + v[12] ^= v[ 0]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + + v[ 1] = v[ 1] + v[ 5] + m[1]; + v[13] ^= v[ 1]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 1] = v[ 1] + v[ 5] + m[15]; + v[13] ^= v[ 1]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 2] = v[ 2] + v[ 6] + m[14]; + v[14] ^= v[ 2]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 2] = v[ 2] + v[ 6] + m[13]; + v[14] ^= v[ 2]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 3] = v[ 3] + v[ 7] + m[4]; + v[15] ^= v[ 3]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 3] = v[ 3] + v[ 7] + m[10]; + v[15] ^= v[ 3]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 0] = v[ 0] + v[ 5] + m[0]; + v[15] ^= v[ 0]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 0] = v[ 0] + v[ 5] + m[7]; + v[15] ^= v[ 0]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 1] = v[ 1] + v[ 6] + m[6]; + v[12] ^= v[ 1]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 1] = v[ 1] + v[ 6] + + m[3]; + v[12] ^= v[ 1]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 2] = v[ 2] + v[ 7] + m[9]; + v[13] ^= v[ 2]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 2] = v[ 2] + v[ 7] + m[2]; + v[13] ^= v[ 2]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 3] = v[ 3] + v[ 4] + m[8]; + v[14] ^= v[ 3]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 3] = v[ 3] + v[ 4] + m[11]; + v[14] ^= v[ 3]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + } + private void round_7(final long[] v, final long[] m) { + v[ 0] = v[ 0] + v[ 4] + m[13]; + v[12] ^= v[ 0]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 0] = v[ 0] + v[ 4] + m [11]; + v[12] ^= v[ 0]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + + v[ 1] = v[ 1] + v[ 5] + m[7]; + v[13] ^= v[ 1]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 1] = v[ 1] + v[ 5] + m[14]; + v[13] ^= v[ 1]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 2] = v[ 2] + v[ 6] + m[12]; + v[14] ^= v[ 2]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 2] = v[ 2] + v[ 6] + m[1]; + v[14] ^= v[ 2]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 3] = v[ 3] + v[ 7] + m[3]; + v[15] ^= v[ 3]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 3] = v[ 3] + v[ 7] + m[9]; + v[15] ^= v[ 3]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 0] = v[ 0] + v[ 5] + m[5]; + v[15] ^= v[ 0]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 0] = v[ 0] + v[ 5] + m[0]; + v[15] ^= v[ 0]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 1] = v[ 1] + v[ 6] + m[15]; + v[12] ^= v[ 1]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 1] = v[ 1] + v[ 6] + + m[4]; + v[12] ^= v[ 1]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 2] = v[ 2] + v[ 7] + m[8]; + v[13] ^= v[ 2]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 2] = v[ 2] + v[ 7] + m[6]; + v[13] ^= v[ 2]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 3] = v[ 3] + v[ 4] + m[2]; + v[14] ^= v[ 3]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 3] = v[ 3] + v[ 4] + m[10]; + v[14] ^= v[ 3]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + } + private void round_8(final long[] v, final long[] m) { + v[ 0] = v[ 0] + v[ 4] + m[6]; + v[12] ^= v[ 0]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 0] = v[ 0] + v[ 4] + m [15]; + v[12] ^= v[ 0]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + + v[ 1] = v[ 1] + v[ 5] + m[14]; + v[13] ^= v[ 1]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 1] = v[ 1] + v[ 5] + m[9]; + v[13] ^= v[ 1]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 2] = v[ 2] + v[ 6] + m[11]; + v[14] ^= v[ 2]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 2] = v[ 2] + v[ 6] + m[3]; + v[14] ^= v[ 2]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 3] = v[ 3] + v[ 7] + m[0]; + v[15] ^= v[ 3]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 3] = v[ 3] + v[ 7] + m[8]; + v[15] ^= v[ 3]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 0] = v[ 0] + v[ 5] + m[12]; + v[15] ^= v[ 0]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 0] = v[ 0] + v[ 5] + m[2]; + v[15] ^= v[ 0]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 1] = v[ 1] + v[ 6] + m[13]; + v[12] ^= v[ 1]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 1] = v[ 1] + v[ 6] + + m[7]; + v[12] ^= v[ 1]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 2] = v[ 2] + v[ 7] + m[1]; + v[13] ^= v[ 2]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 2] = v[ 2] + v[ 7] + m[4]; + v[13] ^= v[ 2]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 3] = v[ 3] + v[ 4] + m[10]; + v[14] ^= v[ 3]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 3] = v[ 3] + v[ 4] + m[5]; + v[14] ^= v[ 3]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + } + private void round_9(final long[] v, final long[] m) { + v[ 0] = v[ 0] + v[ 4] + m[10]; + v[12] ^= v[ 0]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 0] = v[ 0] + v[ 4] + m [2]; + v[12] ^= v[ 0]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[ 8] = v[ 8] + v[12]; + v[ 4] ^= v[ 8]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + + v[ 1] = v[ 1] + v[ 5] + m[8]; + v[13] ^= v[ 1]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 1] = v[ 1] + v[ 5] + m[4]; + v[13] ^= v[ 1]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 9] = v[ 9] + v[13]; + v[ 5] ^= v[ 9]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 2] = v[ 2] + v[ 6] + m[7]; + v[14] ^= v[ 2]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 2] = v[ 2] + v[ 6] + m[6]; + v[14] ^= v[ 2]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[10] = v[10] + v[14]; + v[ 6] ^= v[10]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 3] = v[ 3] + v[ 7] + m[1]; + v[15] ^= v[ 3]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 3] = v[ 3] + v[ 7] + m[5]; + v[15] ^= v[ 3]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[11] = v[11] + v[15]; + v[ 7] ^= v[11]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 0] = v[ 0] + v[ 5] + m[15]; + v[15] ^= v[ 0]; + v[15] = ( v[15] << 32 ) | ( v[15] >>> 32 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] >>> 24 ) | ( v[ 5] << 40 ); + v[ 0] = v[ 0] + v[ 5] + m[11]; + v[15] ^= v[ 0]; + v[15] = ( v[15] >>> 16 ) | ( v[15] << 48 ); + v[10] = v[10] + v[15]; + v[ 5] ^= v[10]; + v[ 5] = ( v[ 5] << 1 ) | ( v[ 5] >>> 63 ); + + v[ 1] = v[ 1] + v[ 6] + m[9]; + v[12] ^= v[ 1]; + v[12] = ( v[12] << 32 ) | ( v[12] >>> 32 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] >>> 24 ) | ( v[ 6] << 40 ); + v[ 1] = v[ 1] + v[ 6] + + m[14]; + v[12] ^= v[ 1]; + v[12] = ( v[12] >>> 16 ) | ( v[12] << 48 ); + v[11] = v[11] + v[12]; + v[ 6] ^= v[11]; + v[ 6] = ( v[ 6] << 1 ) | ( v[ 6] >>> 63 ); + + v[ 2] = v[ 2] + v[ 7] + m[3]; + v[13] ^= v[ 2]; + v[13] = ( v[13] << 32 ) | ( v[13] >>> 32 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] >>> 24 ) | ( v[ 7] << 40 ); + v[ 2] = v[ 2] + v[ 7] + m[12]; + v[13] ^= v[ 2]; + v[13] = ( v[13] >>> 16 ) | ( v[13] << 48 ); + v[ 8] = v[ 8] + v[13]; + v[ 7] ^= v[ 8]; + v[ 7] = ( v[ 7] << 1 ) | ( v[ 7] >>> 63 ); + + v[ 3] = v[ 3] + v[ 4] + m[13]; + v[14] ^= v[ 3]; + v[14] = ( v[14] << 32 ) | ( v[14] >>> 32 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] >>> 24 ) | ( v[ 4] << 40 ); + v[ 3] = v[ 3] + v[ 4] + m[0]; + v[14] ^= v[ 3]; + v[14] = ( v[14] >>> 16 ) | ( v[14] << 48 ); + v[ 9] = v[ 9] + v[14]; + v[ 4] ^= v[ 9]; + v[ 4] = ( v[ 4] << 1 ) | ( v[ 4] >>> 63 ); + } + + //////////////////////////////////////////////////////////////////////// + /// Compression Kernel //////////////////////////////////////////// FINI + //////////////////////////////////////////////////////////////////////// + + /* TEMP - remove at will */ + public static class Debug { + public static void dumpState (Engine e, final String mark) { + System.out.format("-- MARK == @ %s @ ===========\n", mark); + dumpArray("register t", e.state.t); + dumpArray("register h", e.state.h); + dumpArray("register f", e.state.f); + dumpArray("register offset", new long[]{e.state.buflen}); + System.out.format("-- END MARK =================\n"); + } + public static void dumpArray (final String label, final long[] b) { + System.out.format ( "-- %s -- :\n{\n", label ); + for( int j = 0; j < b.length ; ++j ) { + System.out.format ( " [%2d] : %016X\n", j, b[j]); + } + System.out.format ( "}\n" ); + } + public static void dumpBuffer (final PrintStream out, final String label, final byte[] b) { + dumpBuffer(out, label, b, 0, b.length); + } + public static void dumpBuffer (final PrintStream out, final byte[] b) { + dumpBuffer(out, null, b, 0, b.length); + } + public static void dumpBuffer (final PrintStream out, final byte[] b, final int offset, final int len) { + dumpBuffer(out, null, b, offset, len); + } + public static void dumpBuffer (final PrintStream out, final String label, final byte[] b, final int offset, final int len) { + if(label != null) + out.format ( "-- %s -- :\n", label ); + out.format("{\n ", label); + for( int j = 0; j < len ; ++j ) { + out.format ("%02X", b[j + offset]); + if(j+1 < len) { + if ((j+1)%8==0) out.print("\n "); + else out.print(' '); + } + } + out.format("\n}\n"); + } + } + /* TEMP - remove at will */ + + // --------------------------------------------------------------------- + // Helper for assert error messages + // --------------------------------------------------------------------- + public static final class Assert { + public final static String exclusiveUpperBound = "'%s' %d is >= %d"; + public final static String inclusiveUpperBound = "'%s' %d is > %d"; + public final static String exclusiveLowerBound = "'%s' %d is <= %d"; + public final static String inclusiveLowerBound = "'%s' %d is < %d"; + static String assertFail(final String name, final T v, final String err, final T spec) { + new Exception().printStackTrace(); + return String.format(err, name, v, spec); + } + } + // --------------------------------------------------------------------- + // Little Endian Codecs (inlined in the compressor) + /* + * impl note: these are not library funcs and used in hot loops, so no + * null or bounds checks are performed. For our purposes, this is OK. + */ + // --------------------------------------------------------------------- + + public static class LittleEndian { + private static final byte[] hex_digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + private static final byte[] HEX_digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + /** @return hex rep of byte (lower case). */ + static public String toHexStr (final byte[] b) { + return toHexStr (b, false); // because String class is slower. + } + static public String toHexStr (final byte[] b, boolean upperCase) { + final int len = b.length; + final byte[] digits = new byte[ len * 2 ]; + final byte[] hex_rep = upperCase ? HEX_digits : hex_digits ; + for (int i = 0; i < len; i++) { + digits [ i*2 ] = hex_rep [ (byte) (b[i] >> 4 & 0x0F) ]; + digits [ i*2+1 ] = hex_rep [ (byte) (b[i] & 0x0F) ]; + } + return new String(digits); + } + public static int readInt (final byte[] b, final int off) { + int v0 = ((int)b [ off ] & 0xFF ); + v0 |= ((int)b [ off + 1 ] & 0xFF ) << 8; + v0 |= ((int)b [ off + 2 ] & 0xFF ) << 16; + return v0 | ((int)b [ off + 3 ] ) << 24; + } + /** Little endian - byte[] to long */ + public static long readLong (final byte[] b, final int off) { + long v0 = ((long)b [ off ] & 0xFF ); + v0 |= ((long)b [ off + 1 ] & 0xFF ) << 8; + v0 |= ((long)b [ off + 2 ] & 0xFF ) << 16; + v0 |= ((long)b [ off + 3 ] & 0xFF ) << 24; + v0 |= ((long)b [ off + 4 ] & 0xFF ) << 32; + v0 |= ((long)b [ off + 5 ] & 0xFF ) << 40; + v0 |= ((long)b [ off + 6 ] & 0xFF ) << 48; + return v0 | ((long)b [ off + 7 ] ) << 56; + } + /** Little endian - long to byte[] */ + public static void writeLong (long v, final byte[] b, final int off) { + b [ off ] = (byte) v; v >>>= 8; + b [ off + 1 ] = (byte) v; v >>>= 8; + b [ off + 2 ] = (byte) v; v >>>= 8; + b [ off + 3 ] = (byte) v; v >>>= 8; + b [ off + 4 ] = (byte) v; v >>>= 8; + b [ off + 5 ] = (byte) v; v >>>= 8; + b [ off + 6 ] = (byte) v; v >>>= 8; + b [ off + 7 ] = (byte) v; + } + /** Little endian - int to byte[] */ + public static void writeInt (int v, final byte[] b, final int off) { + b [ off ] = (byte) v; v >>>= 8; + b [ off + 1 ] = (byte) v; v >>>= 8; + b [ off + 2 ] = (byte) v; v >>>= 8; + b [ off + 3 ] = (byte) v; + } + } + } + // --------------------------------------------------------------------- + // digest parameter (block) + // --------------------------------------------------------------------- + /** Blake2b configuration parameters block per spec */ + // REVU: need to review a revert back to non-lazy impl TODO: do & bench + public static class Param implements AlgorithmParameterSpec { + interface Xoff { + int digest_length = 0; + int key_length = 1; + int fanout = 2; + int depth = 3; + int leaf_length = 4; + int node_offset = 8; + int node_depth = 16; + int inner_length = 17; + int reserved = 18; + int salt = 32; + int personal = 48; + } + public interface Default { + byte digest_length = Spec.max_digest_bytes; + byte key_length = 0; + byte fanout = 1; + byte depth = 1; + int leaf_length = 0; + long node_offset = 0; + byte node_depth = 0; + byte inner_length = 0; + } + /** default bytes of Blake2b parameter block */ + final static byte[] default_bytes = new byte[ Spec.param_bytes ]; + /** initialize default_bytes */ + static { + default_bytes [ Xoff.digest_length ] = Default.digest_length; + default_bytes [ Xoff.key_length ] = Default.key_length; + default_bytes [ Xoff.fanout ] = Default.fanout; + default_bytes [ Xoff.depth ] = Default.depth; + /* def. leaf_length is 0 fill and already set by new byte[] */ + /* def. node_offset is 0 fill and already set by new byte[] */ + default_bytes [ Xoff.node_depth ] = Default.node_depth; + default_bytes [ Xoff.inner_length] = Default.inner_length; + /* def. salt is 0 fill and already set by new byte[] */ + /* def. personal is 0 fill and already set by new byte[] */ + } + /** default Blake2b h vector */ + final static long[] default_h = new long [ Spec.state_space_len ]; + static { + default_h [0] = readLong( default_bytes, 0 ); + default_h [1] = readLong( default_bytes, 8 ); + default_h [2] = readLong( default_bytes, 16 ); + default_h [3] = readLong( default_bytes, 24 ); + default_h [4] = readLong( default_bytes, 32 ); + default_h [5] = readLong( default_bytes, 40 ); + default_h [6] = readLong( default_bytes, 48 ); + default_h [7] = readLong( default_bytes, 56 ); + + default_h [0] ^= Spec.IV [0]; + default_h [1] ^= Spec.IV [1]; + default_h [2] ^= Spec.IV [2]; + default_h [3] ^= Spec.IV [3]; + default_h [4] ^= Spec.IV [4]; + default_h [5] ^= Spec.IV [5]; + default_h [6] ^= Spec.IV [6]; + default_h [7] ^= Spec.IV [7]; + } + + /** */ + private boolean hasKey = false; + /** not sure how to make this secure - TODO */ + private byte[] key_bytes = null; + /** */ + private byte[] bytes = null; + /** */ + private final long[] h = new long [ Spec.state_space_len ]; + + /** */ + public Param() { + System.arraycopy( default_h, 0, h, 0, Spec.state_space_len ); + } + /** */ + public long[] initialized_H () { + return h; + } + /** package only - copy returned - do not use in functional loops */ + public byte[] getBytes() { + lazyInitBytes(); + byte[] copy = new byte[ bytes.length ]; + System.arraycopy( bytes, 0, copy, 0, bytes.length ); + return copy; + } + + final byte getByteParam (final int xoffset) { + byte[] _bytes = bytes; + if(_bytes == null) _bytes = Param.default_bytes; + return _bytes[ xoffset]; + } + final int getIntParam (final int xoffset) { + byte[] _bytes = bytes; + if(_bytes == null) _bytes = Param.default_bytes; + return readInt ( _bytes, xoffset); + } + final long getLongParam (final int xoffset) { + byte[] _bytes = bytes; + if(_bytes == null) _bytes = Param.default_bytes; + return readLong ( _bytes, xoffset); + } + // TODO same for tree params depth, fanout, inner, node-depth, node-offset + public final int getDigestLength() { + return (int) getByteParam ( Xoff.digest_length ); + } + public final int getKeyLength() { + return (int) getByteParam ( Xoff.key_length ); + } + public final int getFanout() { + return (int) getByteParam ( Xoff.fanout ); + } + public final int getDepth() { + return (int) getByteParam ( Xoff.depth ); + } + public final int getLeafLength() { + return getIntParam ( Xoff.leaf_length ); + } + public final long getNodeOffset() { + return getLongParam ( Xoff.node_offset ); + } + public final int getNodeDepth() { + return (int) getByteParam ( Xoff.node_depth ); + } + public final int getInnerLength() { + return (int) getByteParam ( Xoff.inner_length ); + } + + public final boolean hasKey() { return this.hasKey; } + + @Override public Param clone() { + final Param clone = new Param(); + System.arraycopy(this.h, 0, clone.h, 0, h.length); + clone.lazyInitBytes(); + System.arraycopy(this.bytes, 0, clone.bytes, 0, this.bytes.length); + + if(this.hasKey){ + clone.hasKey = this.hasKey; + clone.key_bytes = new byte [Spec.max_key_bytes * 2]; + System.arraycopy(this.key_bytes, 0, clone.key_bytes, 0, this.key_bytes.length); + } + return clone; + } + //////////////////////////////////////////////////////////////////////// + /// lazy setters - write directly to the bytes image of param block //// + //////////////////////////////////////////////////////////////////////// + final void lazyInitBytes () { + if( bytes == null ) { + bytes = new byte [ Spec.param_bytes ]; + System.arraycopy(Param.default_bytes, 0, bytes, 0, Spec.param_bytes); + } + } + /* 0-7 inclusive */ + public final Param setDigestLength(int len) { + assert len > 0 : assertFail("len", len, exclusiveLowerBound, 0); + assert len <= Spec.max_digest_bytes : assertFail("len", len, inclusiveUpperBound, Spec.max_digest_bytes); + + lazyInitBytes(); + bytes[ Xoff.digest_length ] = (byte) len; + h[ 0 ] = readLong( bytes, 0 ); + h[ 0 ] ^= Spec.IV [ 0 ]; + return this; + } + public final Param setKey (final Key key) { + assert key != null : "key is null"; + final byte[] keybytes = key.getEncoded(); + assert keybytes != null : "key.encoded() is null"; + + return this.setKey(keybytes); + } + public final Param setKey (final byte[] key) { + assert key != null : "key is null"; + assert key.length >= 0 : assertFail("key.length", key.length, inclusiveUpperBound, 0); + assert key.length <= Spec.max_key_bytes : assertFail("key.length", key.length, inclusiveUpperBound, Spec.max_key_bytes); + + // zeropad keybytes + this.key_bytes = new byte [Spec.max_key_bytes * 2]; + System.arraycopy ( key, 0, this.key_bytes, 0, key.length ); + lazyInitBytes(); + bytes[ Xoff.key_length ] = (byte) key.length; // checked c ref; this is correct + h[ 0 ] = readLong( bytes, 0 ); + h[ 0 ] ^= Spec.IV [ 0 ]; + this.hasKey = true; + return this; + } + public final Param setFanout(int fanout) { + assert fanout > 0 : assertFail("fanout", fanout, exclusiveLowerBound, 0); + + lazyInitBytes(); + bytes[ Xoff.fanout ] = (byte) fanout; + h[ 0 ] = readLong( bytes, 0 ); + h[ 0 ] ^= Spec.IV [ 0 ]; + return this; + } + public final Param setDepth(int depth) { + assert depth > 0 : assertFail("depth", depth, exclusiveLowerBound, 0); + + lazyInitBytes(); + bytes[ Xoff.depth ] = (byte) depth; + h[ 0 ] = readLong( bytes, 0 ); + h[ 0 ] ^= Spec.IV [ 0 ]; + return this; + } + public final Param setLeafLength(int leaf_length) { + assert leaf_length >= 0 : assertFail("leaf_length", leaf_length, inclusiveLowerBound, 0); + + lazyInitBytes(); + writeInt (leaf_length, bytes, Xoff.leaf_length); + h[ 0 ] = readLong( bytes, 0 ); + h[ 0 ] ^= Spec.IV [ 0 ]; + return this; + } + + /* 8-15 inclusive */ + public final Param setNodeOffset(long node_offset) { + assert node_offset >= 0 : assertFail("node_offset", node_offset, inclusiveLowerBound, 0); + + lazyInitBytes(); + writeLong(node_offset, bytes, Xoff.node_offset); + h[ 1 ] = readLong( bytes, Xoff.node_offset ); + h[ 1 ] ^= Spec.IV [ 1 ]; + return this; + } + + /* 16-23 inclusive */ + public final Param setNodeDepth(int node_depth) { + assert node_depth >= 0 : assertFail("node_depth", node_depth, inclusiveLowerBound, 0); + + lazyInitBytes(); + bytes[ Xoff.node_depth ] = (byte) node_depth; + h[ 2 ] = readLong( bytes, Xoff.node_depth ); + h[ 2 ] ^= Spec.IV [ 2 ]; + h[ 3 ] = readLong( bytes, Xoff.node_depth + 8); + h[ 3 ] ^= Spec.IV [ 3 ]; + return this; + } + public final Param setInnerLength(int inner_length) { + assert inner_length >= 0 : assertFail("inner_length", inner_length, inclusiveLowerBound, 0); + + lazyInitBytes(); + bytes[ Xoff.inner_length] = (byte) inner_length; + h[ 2 ] = readLong( bytes, Xoff.node_depth ); + h[ 2 ] ^= Spec.IV [ 2 ]; + h[ 3 ] = readLong( bytes, Xoff.node_depth + 8); + h[ 3 ] ^= Spec.IV [ 3 ]; + return this; + } + + /* 24-31 masked by reserved and remain unchanged */ + + /* 32-47 inclusive */ + public final Param setSalt(final byte[] salt) { + assert salt != null : "salt is null"; + assert salt.length <= Spec.max_salt_bytes : assertFail("salt.length", salt.length, inclusiveUpperBound, Spec.max_salt_bytes); + + lazyInitBytes(); + Arrays.fill ( bytes, Xoff.salt, Xoff.salt + Spec.max_salt_bytes, (byte)0); + System.arraycopy( salt, 0, bytes, Xoff.salt, salt.length ); + h[ 4 ] = readLong( bytes, Xoff.salt ); + h[ 4 ] ^= Spec.IV [ 4 ]; + h[ 5 ] = readLong( bytes, Xoff.salt + 8 ); + h[ 5 ] ^= Spec.IV [ 5 ]; + return this; + } + + /* 48-63 inclusive */ + public final Param setPersonal(byte[] personal) { + assert personal != null : "personal is null"; + assert personal.length <= Spec.max_personalization_bytes : assertFail("personal.length", personal.length, inclusiveUpperBound, Spec.max_personalization_bytes); + + lazyInitBytes(); + Arrays.fill ( bytes, Xoff.personal, Xoff.personal + Spec.max_personalization_bytes, (byte)0); + System.arraycopy( personal, 0, bytes, Xoff.personal, personal.length ); + h[ 6 ] = readLong( bytes, Xoff.personal ); + h[ 6 ] ^= Spec.IV [ 6 ]; + h[ 7 ] = readLong( bytes, Xoff.personal + 8 ); + h[ 7 ] ^= Spec.IV [ 7 ]; + return this; + } + //////////////////////////////////////////////////////////////////////// + /// lazy setters /////////////////////////////////////////////////// END + //////////////////////////////////////////////////////////////////////// + } +} diff --git a/app/src/main/java/com/tangem/domain/wallet/cardano/CardanoData.java b/app/src/main/java/com/tangem/domain/wallet/cardano/CardanoData.java new file mode 100644 index 0000000000..afe9f7b101 --- /dev/null +++ b/app/src/main/java/com/tangem/domain/wallet/cardano/CardanoData.java @@ -0,0 +1,122 @@ +package com.tangem.domain.wallet.cardano; + +import android.os.Bundle; +import android.util.Log; + +import com.tangem.domain.wallet.CoinData; +import com.tangem.domain.wallet.CoinEngine; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +public class CardanoData extends CoinData { + public CardanoData() { + super(); + } + + private Long balance; + + public String getUnspentInputsDescription() { + try { +// int gatheredUnspents = 0; + if( unspentOutputs ==null ) return ""; +// for (int i = 0; i < unspentOutputs.size(); i++) { +// if (unspentOutputs.get(i).Raw != null && unspentOutputs.get(i).Raw.length() > 1) gatheredUnspents++; +// } + return String.valueOf(unspentOutputs.size()) + " unspents";// (" + String.valueOf(gatheredUnspents) + " received)"; + } + catch (Exception e) + { + e.printStackTrace(); + return ""; + } + } + + public static class UnspentOutput { + public String txID; + public Long Amount; + public Integer Index; + + public Bundle getAsBundle() { + Bundle B = new Bundle(); + B.putString("txID", txID); + B.putLong("Amount", Amount); + B.putInt("Index", Index); + return B; + } + + public void loadFromBundle(Bundle B) { + txID = B.getString("txID"); + Amount = B.getLong("Amount"); + Index = B.getInt("Index"); + } + } + + + private List unspentOutputs = null; + + public List getUnspentOutputs() { + if (unspentOutputs == null) unspentOutputs = new ArrayList<>(); + return unspentOutputs; + } + + @Override + public void loadFromBundle(Bundle B) { + super.loadFromBundle(B); + + if (B.containsKey("Balance")) balance = B.getLong("Balance"); + else balance = null; + if (B.containsKey("UnspentTransactions")) { + unspentOutputs = new ArrayList<>(); + Bundle BB = B.getBundle("UnspentTransactions"); + Integer i = 0; + while (BB.containsKey(i.toString())) { + UnspentOutput t = new UnspentOutput(); + t.loadFromBundle(BB.getBundle(i.toString())); + unspentOutputs.add(t); + i++; + } + } + } + + @Override + public void saveToBundle(Bundle B) { + super.saveToBundle(B); + try { + if (unspentOutputs != null) { + Bundle BB = new Bundle(); + for (Integer i = 0; i < unspentOutputs.size(); i++) { + BB.putBundle(i.toString(), unspentOutputs.get(i).getAsBundle()); + } + B.putBundle("UnspentTransactions", BB); + } + if (balance != null) B.putLong("Balance", balance); + } catch (Exception e) { + Log.e("Can't save to bundle ", e.getMessage()); + } + } + + @Override + public void clearInfo() { + super.clearInfo(); + balance = null; + unspentOutputs = null; + } + + public CoinEngine.InternalAmount getBalanceInInternalUnits() { + return new CoinEngine.InternalAmount(BigDecimal.valueOf(balance),"Lovelace"); + } + + public void setBalance(Long balance) { + this.balance = balance; + } + + public Long getBalance() { + return balance; + } + + public boolean hasBalanceInfo() { + return balance != null; + } +} diff --git a/app/src/main/java/com/tangem/domain/wallet/cardano/CardanoEngine.java b/app/src/main/java/com/tangem/domain/wallet/cardano/CardanoEngine.java new file mode 100644 index 0000000000..8561f4c0ff --- /dev/null +++ b/app/src/main/java/com/tangem/domain/wallet/cardano/CardanoEngine.java @@ -0,0 +1,759 @@ +package com.tangem.domain.wallet.cardano; + +import android.net.Uri; +import android.text.InputFilter; +import android.util.Base64; +import android.util.Log; + +import com.tangem.data.network.ServerApiAdalite; +import com.tangem.data.network.model.AdaliteResponse; +import com.tangem.data.network.model.AdaliteResponseUtxo; +import com.tangem.data.network.model.UtxoData; +import com.tangem.domain.wallet.BTCUtils; +import com.tangem.domain.wallet.BalanceValidator; +import com.tangem.domain.wallet.Base58; +import com.tangem.domain.wallet.CoinData; +import com.tangem.domain.wallet.CoinEngine; +import com.tangem.domain.wallet.TangemContext; +import com.tangem.tangemcommon.data.TangemCard; +import com.tangem.tangemcommon.reader.CardProtocol; +import com.tangem.tangemcommon.tasks.SignTask; +import com.tangem.tangemcommon.util.Util; +import com.tangem.util.DecimalDigitsInputFilter; +import com.tangem.wallet.R; + +import org.spongycastle.crypto.Digest; +import org.spongycastle.crypto.util.DigestFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; +import java.util.zip.CRC32; + +import co.nstant.in.cbor.CborBuilder; +import co.nstant.in.cbor.CborDecoder; +import co.nstant.in.cbor.CborEncoder; +import co.nstant.in.cbor.CborException; +import co.nstant.in.cbor.builder.ArrayBuilder; +import co.nstant.in.cbor.model.Array; +import co.nstant.in.cbor.model.ByteString; +import co.nstant.in.cbor.model.DataItem; +import co.nstant.in.cbor.model.UnsignedInteger; + +import static com.tangem.domain.wallet.Base58.decodeBase58; +import static com.tangem.domain.wallet.Base58.encodeBase58; + +public class CardanoEngine extends CoinEngine { + + private static final String TAG = CardanoEngine.class.getSimpleName(); + + private static final long protocolMagic = 764824073; + + public CardanoData coinData = null; + + public CardanoEngine(TangemContext context) throws Exception { + super(context); + if (context.getCoinData() == null) { + coinData = new CardanoData(); + context.setCoinData(coinData); + } else if (context.getCoinData() instanceof CardanoData) { + coinData = (CardanoData) context.getCoinData(); + } else { + throw new Exception("Invalid type of Blockchain data for CardanoEngine"); + } + } + + public CardanoEngine() { + super(); + } + + private static int getDecimals() { + return 6; + } + + + private void checkBlockchainDataExists() throws Exception { + if (coinData == null) throw new Exception("No blockchain data"); + } + + @Override + public boolean awaitingConfirmation() { + return false; + } + + @Override + public String getBalanceHTML() { + Amount balance = getBalance(); + if (balance != null) { + return balance.toDescriptionString(getDecimals()); + } else { + return ""; + } + } + + @Override + public String getBalanceCurrency() { + return "ADA"; + } + + @Override + public String getOfflineBalanceHTML() { + InternalAmount offlineInternalAmount = convertToInternalAmount(ctx.getCard().getOfflineBalance()); + Amount offlineAmount = convertToAmount(offlineInternalAmount); + return offlineAmount.toDescriptionString(getDecimals()); + } + + @Override + public boolean isBalanceNotZero() { + if (coinData == null) return false; + if (coinData.getBalanceInInternalUnits() == null) return false; + return coinData.getBalanceInInternalUnits().notZero(); + } + + @Override + public boolean hasBalanceInfo() { + if (coinData == null) return false; + return coinData.hasBalanceInfo(); + } + + @Override + public boolean isExtractPossible() { + if (!hasBalanceInfo()) { + ctx.setMessage(R.string.cannot_obtain_data_from_blockchain); + } else if (!isBalanceNotZero()) { + ctx.setMessage(R.string.wallet_empty); + } else if (awaitingConfirmation()) { + ctx.setMessage(R.string.please_wait_while_previous); + } else if (coinData.getUnspentOutputs().size() == 0) { + ctx.setMessage(R.string.please_wait_for_confirmation); + } else { + return true; + } + return false; + } + + @Override + public String getFeeCurrency() { + return "ADA"; + } + + @Override + public boolean validateAddress(String address) { + if (address == null || address.isEmpty()) { + return false; + } + + byte[] decAddress = Base58.decodeBase58(address); + + if (decAddress == null || decAddress.length == 0) { + return false; + } + + ByteArrayInputStream bais = new ByteArrayInputStream(decAddress); + try { + List addressList = ((Array) new CborDecoder(bais).decode().get(0)).getDataItems(); + byte[] addressBytes = ((ByteString) addressList.get(0)).getBytes(); + long addressChecksum = ((UnsignedInteger) addressList.get(1)).getValue().longValue(); + + final CRC32 crc32 = new CRC32(); + crc32.update(addressBytes); + long calculatedChecksum = crc32.getValue(); + + if (addressChecksum != calculatedChecksum) { + return false; + } + + } catch (Exception e) { + return false; + } + + return true; + } + + @Override + public boolean isNeedCheckNode() { + return true; + } + + @Override + public Uri getWalletExplorerUri() { + return Uri.parse("https://cardanoexplorer.com/address/" + ctx.getCoinData().getWallet()); + } + + @Override + public Uri getShareWalletUri() { + return Uri.parse(ctx.getCoinData().getWallet()); + } + + @Override + public InputFilter[] getAmountInputFilters() { + return new InputFilter[]{new DecimalDigitsInputFilter(getDecimals())}; + } + + @Override + public boolean checkNewTransactionAmount(Amount amount) { + if (coinData == null) return false; + if (amount.compareTo(convertToAmount(coinData.getBalanceInInternalUnits())) > 0) { + return false; + } + return true; + } + + @Override + public boolean checkNewTransactionAmountAndFee(Amount amountValue, Amount feeValue, Boolean isIncludeFee) { + InternalAmount fee; + InternalAmount amount; + + try { + checkBlockchainDataExists(); + amount = convertToInternalAmount(amountValue); + fee = convertToInternalAmount(feeValue); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + if (fee == null || amount == null) + return false; + + if (fee.isZero() || amount.isZero()) + return false; + + if (isIncludeFee && (amount.compareTo(coinData.getBalanceInInternalUnits()) > 0 || amount.compareTo(fee) < 0)) + return false; + + if (!isIncludeFee && amount.add(fee).compareTo(coinData.getBalanceInInternalUnits()) > 0) + return false; + + return true; + } + + @Override + public boolean validateBalance(BalanceValidator balanceValidator) { + try { + if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) { + balanceValidator.setScore(0); + balanceValidator.setFirstLine("Unknown balance"); + balanceValidator.setSecondLine("Balance cannot be verified. Swipe down to refresh."); + return false; + } + + if (coinData.isBalanceReceived()) {// && coinData.isBalanceEqual()) { TODO:check + balanceValidator.setScore(100); + balanceValidator.setFirstLine("Verified balance"); + balanceValidator.setSecondLine("Balance confirmed in blockchain"); + if (coinData.getBalanceInInternalUnits().isZero()) { + balanceValidator.setFirstLine("Empty wallet"); + balanceValidator.setSecondLine(""); + } + } + + if ((ctx.getCard().getOfflineBalance() != null) && !coinData.isBalanceReceived() && (ctx.getCard().getRemainingSignatures() == ctx.getCard().getMaxSignatures()) && coinData.getBalanceInInternalUnits().notZero()) { + balanceValidator.setScore(80); + balanceValidator.setFirstLine("Verified offline balance"); + balanceValidator.setSecondLine("Can't obtain balance from blockchain. Restore internet connection to be more confident. "); + } + + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public Amount getBalance() { + if (!hasBalanceInfo()) return null; + return convertToAmount(coinData.getBalanceInInternalUnits()); + } + + @Override + public String evaluateFeeEquivalent(String fee) { + if (!coinData.getAmountEquivalentDescriptionAvailable()) return ""; + try { + Amount feeAmount = new Amount(fee, getFeeCurrency()); + return feeAmount.toEquivalentString(coinData.getRate()); + } catch (Exception e) { + return ""; + } + } + + @Override + public String getBalanceEquivalent() { + if (coinData == null || !coinData.getAmountEquivalentDescriptionAvailable()) return ""; + Amount balance = getBalance(); + if (balance == null) return ""; + return balance.toEquivalentString(coinData.getRate()); + } + + @Override + public String calculateAddress(byte[] pkUncompressed) throws CborException, IOException { + + ByteArrayOutputStream exBaos = new ByteArrayOutputStream(); + exBaos.write(pkUncompressed); + exBaos.write(BTCUtils.fromHex("0000000000000000000000000000000000000000000000000000000000000000")); + byte[] pkExtended = exBaos.toByteArray(); + + exBaos.reset(); + new CborEncoder(exBaos).encode(new CborBuilder() + .addArray() + .add(0) + .addArray() + .add(0) + .add(pkExtended) + .end() + .addMap() + .end() + .end() + .build()); + byte[] forSha3 = exBaos.toByteArray(); + + Digest sha3 = DigestFactory.createSHA3_256(); + sha3.update(forSha3, 0, forSha3.length); + byte[] forBlake = new byte[32]; + sha3.doFinal(forBlake, 0); + + final Blake2b blake2b = Blake2b.Digest.newInstance(28); + byte[] pkHash = blake2b.digest(forBlake); + + //pkHash + attributes + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new CborEncoder(baos).encode(new CborBuilder() + .addArray() + .add(pkHash) + .addMap()//additional attributes + .end() + .add(0)//address type + .end() + .build()); + byte[] addr = baos.toByteArray(); + + final CRC32 crc32 = new CRC32(); + crc32.update(addr); + long checksum = crc32.getValue(); + + DataItem addrItem = new CborBuilder().add(addr).build().get(0); + addrItem.setTag(24); + + //addr + checksum + baos.reset(); + new CborEncoder(baos).encode(new CborBuilder() + .addArray() + .add(addrItem) + .add(checksum) + .end() + .build()); + + byte[] hexAddress = baos.toByteArray(); + return encodeBase58(hexAddress); + } + + @Override + public Amount convertToAmount(InternalAmount internalAmount) { + BigDecimal d = internalAmount.divide(new BigDecimal("1000000")); + return new Amount(d, getBalanceCurrency()); + } + + @Override + public Amount convertToAmount(String strAmount, String currency) { + return new Amount(strAmount, currency); + } + + @Override + public InternalAmount convertToInternalAmount(Amount amount) { + BigDecimal d = amount.multiply(new BigDecimal("1000000")); + return new InternalAmount(d, "Lovelace"); + } + + @Override + public InternalAmount convertToInternalAmount(byte[] bytes) { + if (bytes == null) return null; + byte[] reversed = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; + return new InternalAmount(Util.byteArrayToLong(reversed), "Lovelace"); + } + + @Override + public byte[] convertToByteArray(InternalAmount internalAmount) { + byte[] bytes = Util.longToByteArray(internalAmount.longValueExact()); +// byte[] reversed = new byte[bytes.length]; TODO: check if needed +// for (int i = 0; i < bytes.length; i++) reversed[i] = bytes[bytes.length - i - 1]; +// return reversed; + return bytes; + } + + @Override + public CoinData createCoinData() { + return new CardanoData(); + } + + @Override + public String getUnspentInputsDescription() { + return coinData.getUnspentInputsDescription(); + } + + @Override + public SignTask.TransactionToSign constructTransaction(Amount amountValue, Amount feeValue, boolean IncFee, String targetAddress) throws Exception { + checkBlockchainDataExists(); + + String myAddress = ctx.getCoinData().getWallet(); + byte[] pbKey = ctx.getCard().getWalletPublicKey(); + List utxoList = coinData.getUnspentOutputs(); + long fullAmount = coinData.getBalance(); + +// long fullAmount = 0; TODO: check +// for (int i = 0; i < unspentOutputs.size(); ++i) { +// fullAmount += unspentOutputs.get(i).value; +// } + + long fees = convertToInternalAmount(feeValue).longValueExact(); + long amount = convertToInternalAmount(amountValue).longValueExact(); + long change = fullAmount - amount; + if (IncFee) { + amount = amount - fees; + } else { + change = change - fees; + } + + final long amountFinal = amount; + final long changeFinal = change; + + if (amount + fees > fullAmount) { + throw new CardProtocol.TangemException_WrongAmount(String.format("Balance (%d) < change (%d) + amount (%d)", fullAmount, change, amount)); + } + + CborBuilder cborBuilder = new CborBuilder(); + ArrayBuilder txArray = cborBuilder.addArray(); + ArrayBuilder> inputsArray = txArray.startArray(); + ArrayBuilder> outputsArray = txArray.startArray(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + //Inputs + for (CardanoData.UnspentOutput utxo : utxoList) { + baos.reset(); + + //txID + inputPos + new CborEncoder(baos).encode(new CborBuilder() + .addArray() + .add(Util.fromHexString(utxo.txID)) + .add(utxo.Index) + .end() + .build()); + byte[] input = baos.toByteArray(); + + DataItem inputItem = new CborBuilder().add(input).build().get(0); + inputItem.setTag(24); + + //input type + input + inputsArray + .addArray() + .add(0) + .add(inputItem) + .end(); + } + + //1st output + DataItem targetAddressItem = new CborDecoder(new ByteArrayInputStream(decodeBase58(targetAddress))).decode().get(0); + outputsArray + .addArray() + .add(targetAddressItem) + .add(amountFinal) + .end(); + //2nd output (optional) + if (changeFinal > 0) { + DataItem myAddressItem = new CborDecoder(new ByteArrayInputStream(decodeBase58(myAddress))).decode().get(0); + outputsArray + .addArray() + .add(myAddressItem) + .add(changeFinal) + .end(); + } + + inputsArray.end(); + outputsArray.end(); + + txArray.addMap().end(); + + baos.reset(); + new CborEncoder(baos).encode(cborBuilder.build()); + byte[] txBody = baos.toByteArray(); + + final Blake2b blake2b = Blake2b.Digest.newInstance(32);//28? + byte[] txHash = blake2b.digest(txBody); + + baos.reset(); + new CborEncoder(baos).encode(new CborBuilder().add(protocolMagic).build()); + byte[] magic = baos.toByteArray(); + + //dataToSign prefix + baos.reset(); + baos.write(new byte[]{(byte) 0x01}); + baos.write(magic); + baos.write(new byte[]{(byte) 0x58, (byte) 0x20}); + + baos.write(txHash); + byte[] dataToSign = baos.toByteArray(); + + + return new SignTask.TransactionToSign() { + + @Override + public boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod) { + return signingMethod == TangemCard.SigningMethod.Sign_Hash || signingMethod == TangemCard.SigningMethod.Sign_Raw; + } + + @Override + public byte[][] getHashesToSign() throws Exception { + byte[][] dataForSign = new byte[1][]; + dataForSign[0] = dataToSign; + return dataForSign; + } + + @Override + public byte[] getRawDataToSign() throws Exception { + throw new Exception("Signing Raw Data is not supported for Cardano"); + } + + @Override + public String getHashAlgToSign() { + return "sha-256x2"; + } + + @Override + public byte[] getIssuerTransactionSignature(byte[] dataToSignByIssuer) throws Exception { + throw new Exception("Issuer validation not supported!"); + } + + @Override + public byte[] onSignCompleted(byte[] signFromCard) throws Exception { + ByteArrayOutputStream exBaos = new ByteArrayOutputStream(); + exBaos.write(ctx.getCard().getWalletPublicKey()); + exBaos.write(BTCUtils.fromHex("0000000000000000000000000000000000000000000000000000000000000000")); + byte[] pkExtended = exBaos.toByteArray(); + + //pubkey + signature + baos.reset(); + new CborEncoder(baos).encode(new CborBuilder() + .addArray() + .add(pkExtended) + .add(signFromCard) + .end() + .build()); + byte[] witnessBody = baos.toByteArray(); + DataItem witnessBodyItem = new CborBuilder().add(witnessBody).build().get(0); + witnessBodyItem.setTag(24); + + //array of witnesses + CborBuilder witnessBuilder = new CborBuilder(); + ArrayBuilder witnessArrayBuilder = witnessBuilder.addArray(); + + //witness type + witness body + for (CardanoData.UnspentOutput utxo : coinData.getUnspentOutputs()) { + witnessArrayBuilder + .addArray() + .add(0) + .add(witnessBodyItem) + .end(); + } + + baos.reset(); + new CborEncoder(baos).encode(witnessBuilder.build()); + byte[] witness = baos.toByteArray(); + + baos.reset(); + baos.write(new byte[]{(byte) 0x82}); + baos.write(txBody); + baos.write(witness); +// new CborEncoder(baos).encode(new CborBuilder() +// .addArray() +// .add(txBody) +// .add(witness) +// .end() +// .build()); + byte[] txForSend = baos.toByteArray(); + notifyOnNeedSendTransaction(txForSend); + //String hex = BTCUtils.toHex(txForSend); + return txForSend; + } + }; + } + + private int calculateEstimatedTransactionSize(String targetAddress, Amount amount) { + Amount feeDummy = new Amount(new BigDecimal(0.2).setScale(getDecimals(), RoundingMode.DOWN), getFeeCurrency()); + try { + OnNeedSendTransaction onNeedSendTransactionBackup = onNeedSendTransaction; + onNeedSendTransaction =(tx)->{}; // empty function to bypass exception + + SignTask.TransactionToSign ttsDummy = constructTransaction(amount, feeDummy, true, targetAddress); + byte[] txForSendDummy = ttsDummy.onSignCompleted(new byte[64]); + + onNeedSendTransaction = onNeedSendTransactionBackup; + Log.e(TAG, "txForSendDummy.length=" + String.valueOf(txForSendDummy.length)); + return txForSendDummy.length; + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "Can't calculate transaction size -> use default!"); + return 1000; + } + } + + @Override + public void requestBalanceAndUnspentTransactions(BlockchainRequestsCallbacks blockchainRequestsCallbacks) { + final ServerApiAdalite serverApiAdalite = new ServerApiAdalite(); + + ServerApiAdalite.ResponseListener adaliteListener = new ServerApiAdalite.ResponseListener() { + @Override + public void onSuccess(String method, AdaliteResponse adaliteResponse) { + Log.i(TAG, "onSuccess: " + method); + try { + String walletAddress = adaliteResponse.getRight().getCaAddress(); + if (!walletAddress.equals(coinData.getWallet())) { + // todo - check + throw new Exception("Invalid wallet address in answer!"); + } + coinData.setBalanceReceived(true); + coinData.setBalance(adaliteResponse.getRight().getCaBalance().getGetCoin()); + coinData.setValidationNodeDescription(ServerApiAdalite.lastNode); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, "FAIL ADALITE_ADDRESS Exception"); + } + + if (serverApiAdalite.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + + + @Override + public void onSuccess(String method, AdaliteResponseUtxo adaliteResponseUtxo) { + Log.i(TAG, "onSuccess: " + method); + try { + coinData.getUnspentOutputs().clear(); + for (UtxoData utxo : adaliteResponseUtxo.getRight()) { + CardanoData.UnspentOutput unspentOutput = new CardanoData.UnspentOutput(); + unspentOutput.txID = utxo.getCuId(); + unspentOutput.Amount = utxo.getCuCoins().getGetCoin(); + unspentOutput.Index = utxo.getCuOutIndex(); + coinData.getUnspentOutputs().add(unspentOutput); + } + + } catch (Exception e) { + e.printStackTrace(); + } + + if (serverApiAdalite.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(!ctx.hasError()); + } else { + blockchainRequestsCallbacks.onProgress(); + Log.e(TAG, "FAIL INSIGHT_UNSPENT_OUTPUTS Exception"); + } + } + + @Override + public void onSuccess(String method, List listResponse) { + Log.e(TAG, "Wrong response type for requestBalanceAndUnspentTransactions"); + ctx.setError("Wrong response type for requestBalanceAndUnspentTransactions"); + } + + @Override + public void onFail(String method, String message) { + Log.i(TAG, "onFail: " + method + " " + message); + ctx.setError(message); + if (serverApiAdalite.isRequestsSequenceCompleted()) { + blockchainRequestsCallbacks.onComplete(false); + } else { + blockchainRequestsCallbacks.onProgress(); + } + } + }; + + serverApiAdalite.setResponseListener(adaliteListener); + + serverApiAdalite.requestData(ServerApiAdalite.ADALITE_ADDRESS, ctx.getCoinData().getWallet(), ""); + serverApiAdalite.requestData(ServerApiAdalite.ADALITE_UNSPENT_OUTPUTS, ctx.getCoinData().getWallet(), ""); + } + + @Override + public void requestFee(BlockchainRequestsCallbacks blockchainRequestsCallbacks, String targetAddress, Amount amount) { + final double A = 0.155381; + final double B = 0.000043946; + int calcSize = calculateEstimatedTransactionSize(targetAddress, amount); + + double fee = A + B * calcSize; + Amount feeAmount = new Amount(new BigDecimal(fee).setScale(getDecimals(), RoundingMode.UP), getFeeCurrency()); + + coinData.minFee = feeAmount; + coinData.normalFee = feeAmount; + coinData.maxFee = feeAmount; + + blockchainRequestsCallbacks.onComplete(true); + } + + @Override + public void requestSendTransaction(BlockchainRequestsCallbacks blockchainRequestsCallbacks, byte[] txForSend) { + final ServerApiAdalite serverApiAdalite = new ServerApiAdalite(); + final String txStr = Base64.encodeToString(txForSend, Base64.NO_WRAP); + // BTCUtils.toHex(txForSend); + + final ServerApiAdalite.ResponseListener responseListener = new ServerApiAdalite.ResponseListener() { + @Override + public void onSuccess(String method, AdaliteResponse adaliteResponse) { + Log.e(TAG, "Wrong response type for requestSendTransaction"); + ctx.setError("Wrong response type for requestSendTransaction"); + } + + @Override + public void onSuccess(String method, AdaliteResponseUtxo adaliteResponseUtxo) { + Log.e(TAG, "Wrong response type for requestSendTransaction"); + ctx.setError("Wrong response type for requestSendTransaction"); + } + + @Override + public void onSuccess(String method, List listResponse) { + if (method.equals(ServerApiAdalite.ADALITE_SEND)) { + String resultString = listResponse.toString(); + try { + if (resultString.isEmpty()) { + ctx.setError("No response from node"); + blockchainRequestsCallbacks.onComplete(false); + } else { // TODO: Make check for a valid send response + ctx.setError(null); + blockchainRequestsCallbacks.onComplete(true); + } + } catch (Exception e) { + if (e.getMessage() != null) { + ctx.setError(e.getMessage()); + blockchainRequestsCallbacks.onComplete(false); + } else { + ctx.setError(e.getClass().getName()); + blockchainRequestsCallbacks.onComplete(false); + Log.e(TAG, resultString); + } + } + } + } + + @Override + public void onFail(String method, String message) { + if (!serverApiAdalite.isRequestsSequenceCompleted()) { + ctx.setError(message); + blockchainRequestsCallbacks.onComplete(false); + } + } + }; + serverApiAdalite.setResponseListener(responseListener); + + serverApiAdalite.requestData(ServerApiAdalite.ADALITE_SEND, "", txStr); + } + + @Override + public boolean allowSelectFeeLevel() { + return false; + } +} diff --git a/app/src/main/java/com/tangem/domain/wallet/cardano/CardanoUtils.java b/app/src/main/java/com/tangem/domain/wallet/cardano/CardanoUtils.java new file mode 100644 index 0000000000..a32e926e0b --- /dev/null +++ b/app/src/main/java/com/tangem/domain/wallet/cardano/CardanoUtils.java @@ -0,0 +1,4 @@ +package com.tangem.domain.wallet.cardano; + +public final class CardanoUtils { +} diff --git a/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java b/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java index 192e105830..438643f7db 100644 --- a/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/ducatus/DucatusEngine.java @@ -253,7 +253,7 @@ public class DucatusEngine extends BtcEngine { return false; } - if (coinData.isBalanceReceived() && coinData.isBalanceEqual()) { + if (coinData.isBalanceReceived()) {// && coinData.isBalanceEqual()) { TODO:check balanceValidator.setScore(100); balanceValidator.setFirstLine("Verified balance"); balanceValidator.setSecondLine("Balance confirmed in blockchain"); diff --git a/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java b/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java index 0f0086f8d1..7ddebba10a 100644 --- a/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/eth/EthEngine.java @@ -321,8 +321,8 @@ public class EthEngine extends CoinEngine { if (!coinData.getUnconfirmedTXCount().equals(coinData.getConfirmedTXCount())) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unguaranteed balance"); - balanceValidator.setSecondLine("Transaction is in progress. Wait for confirmation in blockchain."); + balanceValidator.setFirstLine("Transaction in progress"); + balanceValidator.setSecondLine("Wait for confirmation in blockchain"); return false; } diff --git a/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java b/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java index 96d178234f..ee10a59073 100644 --- a/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java +++ b/app/src/main/java/com/tangem/domain/wallet/token/TokenEngine.java @@ -394,8 +394,8 @@ public class TokenEngine extends CoinEngine { if (!coinData.getUnconfirmedTXCount().equals(coinData.getConfirmedTXCount())) { balanceValidator.setScore(0); - balanceValidator.setFirstLine("Unguaranteed balance"); - balanceValidator.setSecondLine("Transaction is in progress. Wait for confirmation in blockchain."); + balanceValidator.setFirstLine("Transaction in progress"); + balanceValidator.setSecondLine("Wait for confirmation in blockchain"); return false; } diff --git a/app/src/main/java/com/tangem/presentation/activity/ConfirmTransactionActivity.kt b/app/src/main/java/com/tangem/presentation/activity/ConfirmTransactionActivity.kt index 92815f8889..adede77949 100644 --- a/app/src/main/java/com/tangem/presentation/activity/ConfirmTransactionActivity.kt +++ b/app/src/main/java/com/tangem/presentation/activity/ConfirmTransactionActivity.kt @@ -87,9 +87,8 @@ class ConfirmTransactionActivity : AppCompatActivity(), NfcAdapter.ReaderCallbac btnSend.visibility = View.INVISIBLE - val allowFeeLevelSelection = engine.allowSelectFeeLevel() - for (lol in rgFee.touchables) { - lol.isEnabled = allowFeeLevelSelection + if (!engine.allowSelectFeeLevel()) { + rgFee.visibility = View.INVISIBLE } // set listeners diff --git a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt index 35a0d6eed3..9a25817c4d 100644 --- a/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt +++ b/app/src/main/java/com/tangem/presentation/fragment/LoadedWallet.kt @@ -729,6 +729,7 @@ class LoadedWallet : androidx.fragment.app.Fragment(), NfcAdapter.ReaderCallback Blockchain.Litecoin -> "litecoin" Blockchain.Rootstock -> "bitcoin" Blockchain.RootstockToken -> "bitcoin" + Blockchain.Cardano -> "cardano" else -> { throw Exception("Can''t get rate for blockchain " + ctx.blockchainName) } diff --git a/app/src/main/res/layout/activity_confirm_transaction.xml b/app/src/main/res/layout/activity_confirm_transaction.xml index acb261225c..decf945d60 100644 --- a/app/src/main/res/layout/activity_confirm_transaction.xml +++ b/app/src/main/res/layout/activity_confirm_transaction.xml @@ -263,7 +263,7 @@ + app:layout_constraintWidth_min="130dp" /> R.drawable.card_ru030 card.batch == "0021" -> R.drawable.card_ru031 card.batch == "0022" -> R.drawable.card_ru032 + card.batch == "FF32" -> R.drawable.card_ff32 else -> null } diff --git a/tangemserver-android/src/main/res/drawable/card_ff32.png b/tangemserver-android/src/main/res/drawable/card_ff32.png new file mode 100644 index 0000000000..3e0cd61995 Binary files /dev/null and b/tangemserver-android/src/main/res/drawable/card_ff32.png differ