From 29e47d0782ac079fad978dd0771aca2bb4e77407 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 11 Jun 2020 18:13:38 +0300 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle | 1 - .../java/com/tangem/wallet/xrp/XrpEngine.java | 32 +++++------ .../com/tangem/wallet/xrp/XrpTaggedAddress.kt | 53 +++++++++++++++++++ 3 files changed, 67 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/com/tangem/wallet/xrp/XrpTaggedAddress.kt diff --git a/app/build.gradle b/app/build.gradle index 027978da9f..efe15811f0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -137,7 +137,6 @@ dependencies { //dependencies for XRP implementation files('libs/ripple-core-0.0.1.jar') - implementation 'io.xpring:xpring4j:2.2.0' //4 dependencies for ripple-core TODO: move to module? implementation 'net.i2p.crypto:eddsa:0.3.0' implementation 'org.bouncycastle:bcprov-jdk15on:1.61' diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java index a667537b8f..f077d29bb9 100644 --- a/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpEngine.java @@ -34,8 +34,6 @@ import java.util.Arrays; import io.reactivex.SingleObserver; import io.reactivex.observers.DisposableSingleObserver; -import io.xpring.xrpl.ClassicAddress; -import io.xpring.xrpl.Utils; public class XrpEngine extends CoinEngine { @@ -142,12 +140,7 @@ public class XrpEngine extends CoinEngine { Addresses.decodeAccountID(address); return true; } catch (Exception e) { - // X-address - ClassicAddress classicAddress = Utils.decodeXAddress(address); - if (classicAddress == null) { - return false; - } - return !classicAddress.isTest(); + return XrpXAddressService.Companion.validate(address); } } @@ -367,10 +360,10 @@ public class XrpEngine extends CoinEngine { } //X-address - ClassicAddress classicAddress = Utils.decodeXAddress(destination); - if (classicAddress != null) { - destination = classicAddress.address(); - destinationTag = classicAddress.tag().get(); + XrpXAddressDecoded decodedXAddress = XrpXAddressService.Companion.decode(destination); + if (decodedXAddress != null) { + destination = decodedXAddress.getAddress(); + destinationTag = decodedXAddress.getDestinationTag(); } if (IncFee) { @@ -564,7 +557,7 @@ public class XrpEngine extends CoinEngine { public void onSuccess(String method, RippleResponse rippleResponse) { Log.i(TAG, "onSuccess: " + method); switch (method) { - case ServerApiRipple.RIPPLE_ACCOUNT_INFO: { + case ServerApiRipple.RIPPLE_ACCOUNT_INFO: { //check if target account is created try { if (rippleResponse.getResult().getError_code().equals(19)) { // "Account not found" coinData.setTargetAccountCreated(false); @@ -630,13 +623,12 @@ public class XrpEngine extends CoinEngine { if (validateAddress(resolvedAddress)) { coinData.setResolvedPayIdAddress(resolvedAddress); - //check if target account is created serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, resolvedAddress, ""); //TODO: maybe just assume PayID account is created? } else { ctx.setError("Unknown address format in PayID response"); } } catch (Exception e) { - ctx.setError("FAIL payID Exception"); + ctx.setError("Unknown response format on PayID request"); } if (serverApiRipple.isRequestsSequenceCompleted() && serverApiPayId.isRequestsSequenceCompleted()) { blockchainRequestsCallbacks.onComplete(!ctx.hasError()); @@ -648,15 +640,19 @@ public class XrpEngine extends CoinEngine { @Override public void onError(Throwable e) { Log.i(TAG, "onFail: " + "payID" + " " + e.getMessage()); - ctx.setError(e.getMessage()); + ctx.setError("PayID error:" + e.getMessage()); blockchainRequestsCallbacks.onComplete(false); } }; serverApiPayId.getAddress(targetAddress, observer); } else { - //check if target account is created - serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, targetAddress, ""); + XrpXAddressDecoded xAddressDecoded = XrpXAddressService.Companion.decode(targetAddress); + if (xAddressDecoded == null) { // classic address + serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, targetAddress, ""); + } else { // X-address + serverApiRipple.requestData(ServerApiRipple.RIPPLE_ACCOUNT_INFO, xAddressDecoded.getAddress(), ""); + } } } diff --git a/app/src/main/java/com/tangem/wallet/xrp/XrpTaggedAddress.kt b/app/src/main/java/com/tangem/wallet/xrp/XrpTaggedAddress.kt new file mode 100644 index 0000000000..b0e63aa0e0 --- /dev/null +++ b/app/src/main/java/com/tangem/wallet/xrp/XrpTaggedAddress.kt @@ -0,0 +1,53 @@ +package com.tangem.wallet.xrp + +import com.ripple.encodings.addresses.Addresses +import com.ripple.encodings.base58.B58 +import org.kethereum.extensions.toBigInteger + +class XrpXAddressService { + companion object { + private val xrpBase58 = B58("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz") + private val xrpMainnetPrefix = byteArrayOf(0x05, 0x44) + private val zeroTagBytes = ByteArray(4) { 0 } + + fun validate(address: String): Boolean { + return decode(address) != null + } + + fun decode(address: String): XrpXAddressDecoded? { + try { + val addressBytes = xrpBase58.decodeChecked(address) + if (addressBytes.size != 31) return null + + val prefix = addressBytes.slice(0..1).toByteArray() + if (!prefix.contentEquals(xrpMainnetPrefix)) return null + + val accountBytes = addressBytes.slice(2..21).toByteArray() + val classicAddress = Addresses.encodeAccountID(accountBytes) + + val flag = addressBytes[22] + + val tagBytes = addressBytes.slice(23..26).toByteArray() + val reservedTagBytes = addressBytes.slice(27..30).toByteArray() + if (!reservedTagBytes.contentEquals(zeroTagBytes)) return null + + var tag: Int? = null + when (flag) { + 0.toByte() -> if (!tagBytes.contentEquals(zeroTagBytes)) return null + 1.toByte() -> { + tag = tagBytes.reversedArray().toBigInteger().toInt() + } + else -> return null + } + return XrpXAddressDecoded(classicAddress, tag) + } catch (e: Exception) { + return null + } + } + } +} + +data class XrpXAddressDecoded( + val address: String, + val destinationTag: Int? +) \ No newline at end of file