diff --git a/app/src/main/java/com/tangem/data/network/model/RippleResponse.kt b/app/src/main/java/com/tangem/data/network/model/RippleResponse.kt
index d8d54ba683..4a865aca67 100644
--- a/app/src/main/java/com/tangem/data/network/model/RippleResponse.kt
+++ b/app/src/main/java/com/tangem/data/network/model/RippleResponse.kt
@@ -36,7 +36,11 @@ data class RippleResult(
//for RIPPLE_SERVER_STATE
@SerializedName("state")
- var state: RippleState? = null
+ var state: RippleState? = null,
+
+ //for "Account not found error"
+ @SerializedName("error_code")
+ var error_code: Int? = null
)
data class RippleAccountData(
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 f539bb9443..5936584616 100644
--- a/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java
+++ b/app/src/main/java/com/tangem/domain/wallet/CoinEngine.java
@@ -216,7 +216,7 @@ public abstract class CoinEngine {
public abstract boolean validateAddress(String address);
- public abstract String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException, CborException, IOException;
+ public abstract String calculateAddress(byte[] pkUncompressed) throws NoSuchProviderException, NoSuchAlgorithmException, CborException, IOException, Exception;
public abstract Amount convertToAmount(InternalAmount internalAmount) throws Exception;
diff --git a/app/src/main/java/com/tangem/domain/wallet/xrp/XrpData.java b/app/src/main/java/com/tangem/domain/wallet/xrp/XrpData.java
index 180f454a37..a44db475f8 100644
--- a/app/src/main/java/com/tangem/domain/wallet/xrp/XrpData.java
+++ b/app/src/main/java/com/tangem/domain/wallet/xrp/XrpData.java
@@ -17,6 +17,8 @@ public class XrpData extends CoinData {
private Long reserve = 20000000L;
+ private Boolean accountNotFound = false;
+
@Override
public void loadFromBundle(Bundle B) {
super.loadFromBundle(B);
@@ -29,6 +31,8 @@ public class XrpData extends CoinData {
else sequence = null;
if (B.containsKey("Reserve")) reserve = B.getLong("Reserve");
else reserve = 20000000L;
+ if (B.containsKey("AccoundNotFound")) accountNotFound = B.getBoolean("AccoundNotFound");
+ else reserve = 20000000L;
}
@Override
@@ -39,6 +43,7 @@ public class XrpData extends CoinData {
if (balanceUnconfirmed != null) B.putLong("BalanceUnconfirmed", balanceUnconfirmed);
if (sequence != null) B.putLong("Sequence", sequence);
if (reserve != null) B.putLong("Reserve", reserve);
+ if (accountNotFound != null) B.putBoolean("AccoundNotFound", accountNotFound);
} catch (Exception e) {
Log.e("Can't save to bundle ", e.getMessage());
}
@@ -51,6 +56,7 @@ public class XrpData extends CoinData {
balanceUnconfirmed = null;
sequence = null;
reserve = 20000000L;
+ accountNotFound = false;
}
// balanceUnconfirmed is just the latest balance, it equals balanceConfirmed if no unconfirmed transaction present
@@ -89,6 +95,14 @@ public class XrpData extends CoinData {
return new CoinEngine.InternalAmount(BigDecimal.valueOf(reserve), "Drops");
}
+ public boolean isAccountNotFound() {
+ return accountNotFound;
+ }
+
+ public void setAccountNotFound(boolean accountFound) {
+ this.accountNotFound = accountFound;
+ }
+
public boolean hasBalanceInfo() {
return balanceConfirmed != null || balanceUnconfirmed != null;
}
diff --git a/app/src/main/java/com/tangem/domain/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/domain/wallet/xrp/XrpEngine.java
index 48aa9a47ce..fda44e7931 100644
--- a/app/src/main/java/com/tangem/domain/wallet/xrp/XrpEngine.java
+++ b/app/src/main/java/com/tangem/domain/wallet/xrp/XrpEngine.java
@@ -8,7 +8,9 @@ import com.ripple.core.coretypes.AccountID;
import com.ripple.core.coretypes.uint.UInt32;
import com.ripple.core.types.known.tx.signed.SignedTransaction;
import com.ripple.core.types.known.tx.txns.Payment;
+import com.ripple.crypto.ecdsa.ECDSASignature;
import com.ripple.encodings.addresses.Addresses;
+import com.ripple.utils.HashUtils;
import com.tangem.card_common.data.TangemCard;
import com.tangem.card_common.reader.CardProtocol;
import com.tangem.card_common.tasks.SignTask;
@@ -24,7 +26,13 @@ import com.tangem.util.CryptoUtil;
import com.tangem.util.DecimalDigitsInputFilter;
import com.tangem.wallet.R;
+import org.spongycastle.asn1.ASN1EncodableVector;
+import org.spongycastle.asn1.ASN1Integer;
+import org.spongycastle.asn1.DERSequence;
+
import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
public class XrpEngine extends CoinEngine {
@@ -66,7 +74,7 @@ public class XrpEngine extends CoinEngine {
public String getBalanceHTML() {
Amount balance = getBalance();
if (balance != null) {
- return " " + balance.toDescriptionString(getDecimals()) + "
+ " + convertToAmount(coinData.getReserveInInternalUnits()).toDescriptionString(getDecimals()) + " reserve";
+ return " " + balance.toDescriptionString(getDecimals()) + "
+ " + convertToAmount(coinData.getReserveInInternalUnits()).toDescriptionString(getDecimals()) + " reserve";
} else {
return "";
}
@@ -201,6 +209,13 @@ public class XrpEngine extends CoinEngine {
@Override
public boolean validateBalance(BalanceValidator balanceValidator) {
try {
+ if (coinData.isAccountNotFound()) {
+ balanceValidator.setScore(0);
+ balanceValidator.setFirstLine("Account not found");
+ balanceValidator.setSecondLine("Load 20+ XRP to create account");
+ return false;
+ }
+
if (((ctx.getCard().getOfflineBalance() == null) && !ctx.getCoinData().isBalanceReceived()) || (!ctx.getCoinData().isBalanceReceived() && (ctx.getCard().getRemainingSignatures() != ctx.getCard().getMaxSignatures()))) {
balanceValidator.setScore(0);
balanceValidator.setFirstLine("Unknown balance");
@@ -263,7 +278,7 @@ public class XrpEngine extends CoinEngine {
return balance.toEquivalentString(coinData.getRate());
}
- public String calculateAddress(byte[] pkCompressed) {
+ public String calculateAddress(byte[] pkCompressed) throws Exception {
byte[] canonisedPubKey = canonisePubKey(pkCompressed);
byte[] accountId = CryptoUtil.sha256ripemd160(canonisedPubKey);
String address = Addresses.encodeAccountID(accountId);
@@ -301,9 +316,6 @@ public class XrpEngine extends CoinEngine {
@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;
}
@@ -328,15 +340,16 @@ public class XrpEngine extends CoinEngine {
}
}
- public byte[] canonisePubKey(byte[] pkCompressed) {
+ public byte[] canonisePubKey(byte[] pkCompressed) throws Exception {
byte[] canonicalPubKey = new byte[33];
if (pkCompressed.length == 32) {
canonicalPubKey[0] = (byte) 0xED;
System.arraycopy(pkCompressed, 0, canonicalPubKey, 1, 32);
- } else {
+ } else if (pkCompressed.length == 33)
canonicalPubKey = pkCompressed;
- }
+ else
+ throw new Exception("Invalid pubkey length");
return canonicalPubKey;
}
@@ -373,9 +386,14 @@ public class XrpEngine extends CoinEngine {
}
@Override
- public byte[][] getHashesToSign() {
+ public byte[][] getHashesToSign() throws Exception {
byte[][] dataForSign = new byte[1][];
- dataForSign[0] = signedTx.signingData;
+ if (ctx.getCard().getWalletPublicKeyRar().length == 33)
+ dataForSign[0] = HashUtils.halfSha512(signedTx.signingData);
+ else if (ctx.getCard().getWalletPublicKeyRar().length == 32)
+ dataForSign[0] = signedTx.signingData;
+ else
+ throw new Exception("Invalid pubkey length");
return dataForSign;
}
@@ -396,7 +414,21 @@ public class XrpEngine extends CoinEngine {
@Override
public byte[] onSignCompleted(byte[] signFromCard) throws Exception {
- signedTx.addSign(signFromCard);
+ if (ctx.getCard().getWalletPublicKeyRar().length == 33) {
+ int size = signFromCard.length / 2;
+ BigInteger r = new BigInteger(1, Arrays.copyOfRange(signFromCard, 0, size));
+ BigInteger s = new BigInteger(1, Arrays.copyOfRange(signFromCard, size, size * 2));
+ s = CryptoUtil.toCanonicalised(s);
+ ECDSASignature sig = new ECDSASignature(r, s);
+ byte[] sigDer = sig.encodeToDER();
+ if (!ECDSASignature.isStrictlyCanonical(sigDer)) {
+ throw new IllegalStateException("Signature is not strictly canonical");
+ }
+ signedTx.addSign(sigDer);
+ } else if (ctx.getCard().getWalletPublicKeyRar().length == 32)
+ signedTx.addSign(signFromCard);
+ else
+ throw new Exception("Invalid pubkey length");
byte[] txForSend = BTCUtils.fromHex(signedTx.tx_blob);
notifyOnNeedSendTransaction(txForSend);
return txForSend;
@@ -414,12 +446,15 @@ public class XrpEngine extends CoinEngine {
switch (method) {
case ServerApiRipple.RIPPLE_ACCOUNT_INFO: {
try {
- String walletAddress = rippleResponse.getResult().getAccount_data().getAccount();
- if (!walletAddress.equals(coinData.getWallet())) {
- throw new Exception("Invalid wallet address in answer!");
+ if (rippleResponse.getResult().getAccount_data() != null) {
+ String walletAddress = rippleResponse.getResult().getAccount_data().getAccount();
+ if (!walletAddress.equals(coinData.getWallet())) {
+ throw new Exception("Invalid wallet address in answer!");
+ }
+ coinData.setBalanceConfirmed(Long.parseLong(rippleResponse.getResult().getAccount_data().getBalance()));
}
-
- coinData.setBalanceConfirmed(Long.parseLong(rippleResponse.getResult().getAccount_data().getBalance()));
+ else if (rippleResponse.getResult().getError_code().equals(19)) // "Account not found"
+ coinData.setAccountNotFound(true);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "FAIL RIPPLE_ACCOUNT_INFO Exception");
diff --git a/app/src/main/java/com/tangem/ui/fragment/LoadedWallet.kt b/app/src/main/java/com/tangem/ui/fragment/LoadedWallet.kt
index 346f7900be..cafb001a58 100644
--- a/app/src/main/java/com/tangem/ui/fragment/LoadedWallet.kt
+++ b/app/src/main/java/com/tangem/ui/fragment/LoadedWallet.kt
@@ -594,7 +594,7 @@ class LoadedWallet : androidx.fragment.app.Fragment(), NfcAdapter.ReaderCallback
tvBalance.text = html
}
- else -> tvBalance.text = getString(R.string.no_data_string)
+ else -> tvBalance.text = ""
}
tvWallet.text = ctx.coinData!!.wallet