diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3b2fc98..e0ea990 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,8 +28,9 @@ + android:stateNotNeeded="true" + tools:replace="android:screenOrientation" /> + Licensed under the MIT License

rapidjson (https://github.com/monero-project/monero/blob/master/external/rapidjson)

Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. -

easylogging++ (https://github.com/monero-project/monero/tree/master/external/easylogging%2B%2B)

+

easylogging++ + (https://github.com/monero-project/monero/tree/master/external/easylogging%2B%2B)

Copyright (c) 2017 muflihun.com

zxcvbn4j (https://github.com/nulab/zxcvbn4j)

Copyright (c) 2014 Nulab Inc @@ -605,7 +606,9 @@ copied and put under another distribution licence

Boost

Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization @@ -766,12 +769,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

'Poppins' Font

SIL Open Font License

Copyright (c) 2014, Indian Type Foundry (info@indiantypefoundry.com).

-

This Font Software is licensed under the SIL Open Font License, Version 1.1.
- This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL

-

—————————————————————————————-
- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
- —————————————————————————————-

-

PREAMBLE
+

This Font Software is licensed under the SIL Open Font License, Version 1.1.
+ This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL +

+

—————————————————————————————-
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+ —————————————————————————————- +

+

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be @@ -782,7 +787,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.

-

DEFINITIONS
+

DEFINITIONS
“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.

@@ -795,7 +800,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. by changing formats or by porting the Font Software to a new environment.

“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.

-

PERMISSION & CONDITIONS
+

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:

@@ -817,9 +822,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.

-

TERMINATION
+

TERMINATION
This license becomes null and void if any of the above conditions are not met.

-

DISCLAIMER
+

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN diff --git a/app/src/main/cpp/monerujo.cpp b/app/src/main/cpp/monerujo.cpp index 8ae8c63..74c058b 100644 --- a/app/src/main/cpp/monerujo.cpp +++ b/app/src/main/cpp/monerujo.cpp @@ -60,8 +60,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { jenv->FindClass("com/m2049r/xmrwallet/model/Transfer"))); class_WalletListener = static_cast(jenv->NewGlobalRef( jenv->FindClass("com/m2049r/xmrwallet/model/WalletListener"))); - class_Ledger = static_cast(jenv->NewGlobalRef( - jenv->FindClass("com/m2049r/xmrwallet/ledger/Ledger"))); class_WalletStatus = static_cast(jenv->NewGlobalRef( jenv->FindClass("com/m2049r/xmrwallet/model/Wallet$Status"))); return JNI_VERSION_1_6; @@ -510,8 +508,8 @@ Java_com_m2049r_xmrwallet_model_WalletManager_startMining(JNIEnv *env, jobject i const char *_address = env->GetStringUTFChars(address, nullptr); bool success = Monero::WalletManagerFactory::getWalletManager()->startMining(std::string(_address), - background_mining, - ignore_battery); + background_mining, + ignore_battery); env->ReleaseStringUTFChars(address, _address); return static_cast(success); } @@ -537,7 +535,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_resolveOpenAlias(JNIEnv *env, jobj JNIEXPORT jboolean JNICALL Java_com_m2049r_xmrwallet_model_WalletManager_setProxyJ(JNIEnv *env, jobject instance, - jstring address) { + jstring address) { const char *_address = env->GetStringUTFChars(address, nullptr); bool rc = Monero::WalletManagerFactory::getWalletManager()->setProxy(std::string(_address)); @@ -553,7 +551,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_closeJ(JNIEnv *env, jobject instan jobject walletInstance) { Monero::Wallet *wallet = getHandle(env, walletInstance); bool closeSuccess = Monero::WalletManagerFactory::getWalletManager()->closeWallet(wallet, - false); + false); if (closeSuccess) { MyWalletListener *walletListener = getHandle(env, walletInstance, "listenerHandle"); @@ -705,7 +703,8 @@ JNIEXPORT jboolean JNICALL Java_com_m2049r_xmrwallet_model_Wallet_initJ(JNIEnv *env, jobject instance, jstring daemon_address, jlong upper_transaction_size_limit, - jstring daemon_username, jstring daemon_password, jstring proxy) { + jstring daemon_username, jstring daemon_password, + jstring proxy) { const char *_daemon_address = env->GetStringUTFChars(daemon_address, nullptr); const char *_daemon_username = env->GetStringUTFChars(daemon_username, nullptr); const char *_daemon_password = env->GetStringUTFChars(daemon_password, nullptr); @@ -968,9 +967,9 @@ Java_com_m2049r_xmrwallet_model_Wallet_createTransactionJ(JNIEnv *env, jobject i Monero::Wallet *wallet = getHandle(env, instance); Monero::PendingTransaction *tx = wallet->createTransaction(_dst_addr, _payment_id, - amount, (uint32_t) mixin_count, - _priority, - (uint32_t) accountIndex); + amount, (uint32_t) mixin_count, + _priority, + (uint32_t) accountIndex); env->ReleaseStringUTFChars(dst_addr, _dst_addr); env->ReleaseStringUTFChars(payment_id, _payment_id); @@ -993,9 +992,9 @@ Java_com_m2049r_xmrwallet_model_Wallet_createSweepTransaction(JNIEnv *env, jobje Monero::optional empty; Monero::PendingTransaction *tx = wallet->createTransaction(_dst_addr, _payment_id, - empty, (uint32_t) mixin_count, - _priority, - (uint32_t) accountIndex); + empty, (uint32_t) mixin_count, + _priority, + (uint32_t) accountIndex); env->ReleaseStringUTFChars(dst_addr, _dst_addr); env->ReleaseStringUTFChars(payment_id, _payment_id); @@ -1205,7 +1204,7 @@ Java_com_m2049r_xmrwallet_model_Wallet_getLastSubaddress(JNIEnv *env, jobject in JNIEXPORT jint JNICALL Java_com_m2049r_xmrwallet_model_TransactionHistory_getCount(JNIEnv *env, jobject instance) { Monero::TransactionHistory *history = getHandle(env, - instance); + instance); return history->count(); } @@ -1291,7 +1290,7 @@ jobject cpp2java(JNIEnv *env, const std::vector &vect JNIEXPORT jobject JNICALL Java_com_m2049r_xmrwallet_model_TransactionHistory_refreshJ(JNIEnv *env, jobject instance) { Monero::TransactionHistory *history = getHandle(env, - instance); + instance); history->refresh(); return cpp2java(env, history->getAll()); } diff --git a/app/src/main/cpp/monerujo.h b/app/src/main/cpp/monerujo.h index 0fb3444..1f58d49 100644 --- a/app/src/main/cpp/monerujo.h +++ b/app/src/main/cpp/monerujo.h @@ -54,7 +54,7 @@ extern "C" { #endif -extern const char* const MONERO_VERSION; // the actual monero core version +extern const char *const MONERO_VERSION; // the actual monero core version // from monero-core crypto/hash-ops.h - avoid #including monero code here enum { @@ -62,14 +62,16 @@ enum { HASH_DATA_AREA = 136 }; -void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height); +void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, + uint64_t height); inline void slow_hash(const void *data, const size_t length, char *hash) { cn_slow_hash(data, length, hash, 0 /*variant*/, 0 /*prehashed*/, 0 /*height*/); } inline void slow_hash_broken(const void *data, char *hash, int variant) { - cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/, 0 /*height*/); + cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/, + 0 /*height*/); } #ifdef __cplusplus diff --git a/app/src/main/java/com/btchip/BTChipException.java b/app/src/main/java/com/btchip/BTChipException.java deleted file mode 100644 index 440a3ad..0000000 --- a/app/src/main/java/com/btchip/BTChipException.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - ******************************************************************************* - * BTChip Bitcoin Hardware Wallet Java API - * (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************** - */ - -package com.btchip; - -public class BTChipException extends Exception { - - private static final long serialVersionUID = 5512803003827126405L; - - public BTChipException(String reason) { - super(reason); - } - - public BTChipException(String reason, Throwable cause) { - super(reason, cause); - } - - public BTChipException(String reason, int sw) { - super(reason); - this.sw = sw; - } - - public int getSW() { - return sw; - } - - public String toString() { - if (sw == 0) { - return "BTChip Exception : " + getMessage(); - } else { - return "BTChip Exception : " + getMessage() + " " + Integer.toHexString(sw); - } - } - - private int sw; - -} diff --git a/app/src/main/java/com/btchip/comm/BTChipTransport.java b/app/src/main/java/com/btchip/comm/BTChipTransport.java deleted file mode 100644 index cc55573..0000000 --- a/app/src/main/java/com/btchip/comm/BTChipTransport.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - ******************************************************************************* - * BTChip Bitcoin Hardware Wallet Java API - * (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn - * (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************** - */ - -package com.btchip.comm; - -import com.btchip.BTChipException; - -public interface BTChipTransport { - byte[] exchange(byte[] command); - - void close(); - - void setDebug(boolean debugFlag); -} diff --git a/app/src/main/java/com/btchip/comm/LedgerHelper.java b/app/src/main/java/com/btchip/comm/LedgerHelper.java deleted file mode 100644 index db24899..0000000 --- a/app/src/main/java/com/btchip/comm/LedgerHelper.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - ******************************************************************************* - * BTChip Bitcoin Hardware Wallet Java API - * (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn - * (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************** - */ - -package com.btchip.comm; - -import java.io.ByteArrayOutputStream; - -public class LedgerHelper { - - private static final int TAG_APDU = 0x05; - - public static byte[] wrapCommandAPDU(int channel, byte[] command, int packetSize) { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - if (packetSize < 3) { - throw new IllegalArgumentException("Can't handle Ledger framing with less than 3 bytes for the report"); - } - int sequenceIdx = 0; - int offset = 0; - output.write(channel >> 8); - output.write(channel); - output.write(TAG_APDU); - output.write(sequenceIdx >> 8); - output.write(sequenceIdx); - sequenceIdx++; - output.write(command.length >> 8); - output.write(command.length); - int blockSize = (command.length > packetSize - 7 ? packetSize - 7 : command.length); - output.write(command, offset, blockSize); - offset += blockSize; - while (offset != command.length) { - output.write(channel >> 8); - output.write(channel); - output.write(TAG_APDU); - output.write(sequenceIdx >> 8); - output.write(sequenceIdx); - sequenceIdx++; - blockSize = (command.length - offset > packetSize - 5 ? packetSize - 5 : command.length - offset); - output.write(command, offset, blockSize); - offset += blockSize; - } - if ((output.size() % packetSize) != 0) { - byte[] padding = new byte[packetSize - (output.size() % packetSize)]; - output.write(padding, 0, padding.length); - } - return output.toByteArray(); - } - - public static byte[] unwrapResponseAPDU(int channel, byte[] data, int packetSize) { - ByteArrayOutputStream response = new ByteArrayOutputStream(); - int offset = 0; - int responseLength; - int sequenceIdx = 0; - if ((data == null) || (data.length < 7 + 5)) { - return null; - } - if (data[offset++] != (channel >> 8)) { - throw new IllegalArgumentException("Invalid channel"); - } - if (data[offset++] != (channel & 0xff)) { - throw new IllegalArgumentException("Invalid channel"); - } - if (data[offset++] != TAG_APDU) { - throw new IllegalArgumentException("Invalid tag"); - } - if (data[offset++] != 0x00) { - throw new IllegalArgumentException("Invalid sequence"); - } - if (data[offset++] != 0x00) { - throw new IllegalArgumentException("Invalid sequence"); - } - responseLength = ((data[offset++] & 0xff) << 8); - responseLength |= (data[offset++] & 0xff); - if (data.length < 7 + responseLength) { - return null; - } - int blockSize = (responseLength > packetSize - 7 ? packetSize - 7 : responseLength); - response.write(data, offset, blockSize); - offset += blockSize; - while (response.size() != responseLength) { - sequenceIdx++; - if (offset == data.length) { - return null; - } - if (data[offset++] != (channel >> 8)) { - throw new IllegalArgumentException("Invalid channel"); - } - if (data[offset++] != (channel & 0xff)) { - throw new IllegalArgumentException("Invalid channel"); - } - if (data[offset++] != TAG_APDU) { - throw new IllegalArgumentException("Invalid tag"); - } - if (data[offset++] != (sequenceIdx >> 8)) { - throw new IllegalArgumentException("Invalid sequence"); - } - if (data[offset++] != (sequenceIdx & 0xff)) { - throw new IllegalArgumentException("Invalid sequence"); - } - blockSize = (responseLength - response.size() > packetSize - 5 ? packetSize - 5 : responseLength - response.size()); - if (blockSize > data.length - offset) { - return null; - } - response.write(data, offset, blockSize); - offset += blockSize; - } - return response.toByteArray(); - } - -} diff --git a/app/src/main/java/com/btchip/comm/android/BTChipTransportAndroidHID.java b/app/src/main/java/com/btchip/comm/android/BTChipTransportAndroidHID.java deleted file mode 100644 index 37c4f49..0000000 --- a/app/src/main/java/com/btchip/comm/android/BTChipTransportAndroidHID.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - ******************************************************************************* - * BTChip Bitcoin Hardware Wallet Java API - * (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn - * (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************** - */ - -package com.btchip.comm.android; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.hardware.usb.UsbManager; -import android.hardware.usb.UsbRequest; - -import com.btchip.BTChipException; -import com.btchip.comm.BTChipTransport; -import com.btchip.comm.LedgerHelper; -import com.btchip.utils.Dump; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.HashMap; - -import timber.log.Timber; - -public class BTChipTransportAndroidHID implements BTChipTransport { - - public static UsbDevice getDevice(UsbManager manager) { - HashMap deviceList = manager.getDeviceList(); - for (UsbDevice device : deviceList.values()) { - Timber.d("%04X:%04X %s, %s", device.getVendorId(), device.getProductId(), device.getManufacturerName(), device.getProductName()); - if (device.getVendorId() == VID) { - final int deviceProductId = device.getProductId(); - for (int pid : PID_HIDS) { - if (deviceProductId == pid) - return device; - } - } - } - return null; - } - - public static BTChipTransport open(UsbManager manager, UsbDevice device) throws IOException { - UsbDeviceConnection connection = manager.openDevice(device); - if (connection == null) throw new IOException("Device not connected"); - // Must only be called once permission is granted (see http://developer.android.com/reference/android/hardware/usb/UsbManager.html) - // Important if enumerating, rather than being awaken by the intent notification - UsbInterface dongleInterface = device.getInterface(0); - UsbEndpoint in = null; - UsbEndpoint out = null; - for (int i = 0; i < dongleInterface.getEndpointCount(); i++) { - UsbEndpoint tmpEndpoint = dongleInterface.getEndpoint(i); - if (tmpEndpoint.getDirection() == UsbConstants.USB_DIR_IN) { - in = tmpEndpoint; - } else { - out = tmpEndpoint; - } - } - connection.claimInterface(dongleInterface, true); - return new BTChipTransportAndroidHID(connection, dongleInterface, in, out); - } - - private static final int VID = 0x2C97; - private static final int[] PID_HIDS = {0x0001, 0x0004, 0x0005}; - - private UsbDeviceConnection connection; - private UsbInterface dongleInterface; - private UsbEndpoint in; - private UsbEndpoint out; - private byte transferBuffer[]; - private boolean debug; - - public BTChipTransportAndroidHID(UsbDeviceConnection connection, UsbInterface dongleInterface, UsbEndpoint in, UsbEndpoint out) { - this.connection = connection; - this.dongleInterface = dongleInterface; - this.in = in; - this.out = out; - transferBuffer = new byte[HID_BUFFER_SIZE]; - } - - @Override - public byte[] exchange(byte[] command) { - ByteArrayOutputStream response = new ByteArrayOutputStream(); - byte[] responseData = null; - int offset = 0; - if (debug) { - Timber.d("=> %s", Dump.dump(command)); - } - command = LedgerHelper.wrapCommandAPDU(LEDGER_DEFAULT_CHANNEL, command, HID_BUFFER_SIZE); - UsbRequest requestOut = new UsbRequest(); - requestOut.initialize(connection, out); - while (offset != command.length) { - int blockSize = (command.length - offset > HID_BUFFER_SIZE ? HID_BUFFER_SIZE : command.length - offset); - System.arraycopy(command, offset, transferBuffer, 0, blockSize); - requestOut.queue(ByteBuffer.wrap(transferBuffer), HID_BUFFER_SIZE); - connection.requestWait(); - offset += blockSize; - } - requestOut.close(); - ByteBuffer responseBuffer = ByteBuffer.allocate(HID_BUFFER_SIZE); - UsbRequest requestIn = new UsbRequest(); - requestIn.initialize(connection, in); - while ((responseData = LedgerHelper.unwrapResponseAPDU(LEDGER_DEFAULT_CHANNEL, response.toByteArray(), HID_BUFFER_SIZE)) == null) { - responseBuffer.clear(); - requestIn.queue(responseBuffer, HID_BUFFER_SIZE); - connection.requestWait(); - responseBuffer.rewind(); - responseBuffer.get(transferBuffer, 0, HID_BUFFER_SIZE); - response.write(transferBuffer, 0, HID_BUFFER_SIZE); - } - requestIn.close(); - if (debug) { - Timber.d("<= %s", Dump.dump(responseData)); - } - return responseData; - } - - @Override - public void close() { - connection.releaseInterface(dongleInterface); - connection.close(); - } - - @Override - public void setDebug(boolean debugFlag) { - this.debug = debugFlag; - } - - private static final int HID_BUFFER_SIZE = 64; - private static final int LEDGER_DEFAULT_CHANNEL = 1; - private static final int SW1_DATA_AVAILABLE = 0x61; -} diff --git a/app/src/main/java/com/btchip/utils/Dump.java b/app/src/main/java/com/btchip/utils/Dump.java deleted file mode 100644 index 2d453fc..0000000 --- a/app/src/main/java/com/btchip/utils/Dump.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - ******************************************************************************* - * BTChip Bitcoin Hardware Wallet Java API - * (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************** - */ - -package com.btchip.utils; - -import java.io.ByteArrayOutputStream; - -public class Dump { - - public static String dump(byte[] buffer, int offset, int length) { - String result = ""; - for (int i = 0; i < length; i++) { - String temp = Integer.toHexString((buffer[offset + i]) & 0xff); - if (temp.length() < 2) { - temp = "0" + temp; - } - result += temp; - } - return result; - } - - public static String dump(byte[] buffer) { - return dump(buffer, 0, buffer.length); - } - - public static byte[] hexToBin(String src) { - ByteArrayOutputStream result = new ByteArrayOutputStream(); - int i = 0; - while (i < src.length()) { - char x = src.charAt(i); - if (!((x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f'))) { - i++; - continue; - } - try { - result.write(Integer.valueOf("" + src.charAt(i) + src.charAt(i + 1), 16)); - i += 2; - } catch (Exception e) { - return null; - } - } - return result.toByteArray(); - } - - -} diff --git a/app/src/main/java/com/m2049r/levin/data/Bucket.java b/app/src/main/java/com/m2049r/levin/data/Bucket.java deleted file mode 100644 index fc9b571..0000000 --- a/app/src/main/java/com/m2049r/levin/data/Bucket.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.levin.data; - -import com.m2049r.levin.util.HexHelper; -import com.m2049r.levin.util.LevinReader; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -public class Bucket { - - // constants copied from monero p2p & epee - - public final static int P2P_COMMANDS_POOL_BASE = 1000; - public final static int COMMAND_HANDSHAKE_ID = P2P_COMMANDS_POOL_BASE + 1; - public final static int COMMAND_TIMED_SYNC_ID = P2P_COMMANDS_POOL_BASE + 2; - public final static int COMMAND_PING_ID = P2P_COMMANDS_POOL_BASE + 3; - public final static int COMMAND_REQUEST_STAT_INFO_ID = P2P_COMMANDS_POOL_BASE + 4; - public final static int COMMAND_REQUEST_NETWORK_STATE_ID = P2P_COMMANDS_POOL_BASE + 5; - public final static int COMMAND_REQUEST_PEER_ID_ID = P2P_COMMANDS_POOL_BASE + 6; - public final static int COMMAND_REQUEST_SUPPORT_FLAGS_ID = P2P_COMMANDS_POOL_BASE + 7; - - public final static long LEVIN_SIGNATURE = 0x0101010101012101L; // Bender's nightmare - - public final static long LEVIN_DEFAULT_MAX_PACKET_SIZE = 100000000; // 100MB by default - - public final static int LEVIN_PACKET_REQUEST = 0x00000001; - public final static int LEVIN_PACKET_RESPONSE = 0x00000002; - - public final static int LEVIN_PROTOCOL_VER_0 = 0; - public final static int LEVIN_PROTOCOL_VER_1 = 1; - - public final static int LEVIN_OK = 0; - public final static int LEVIN_ERROR_CONNECTION = -1; - public final static int LEVIN_ERROR_CONNECTION_NOT_FOUND = -2; - public final static int LEVIN_ERROR_CONNECTION_DESTROYED = -3; - public final static int LEVIN_ERROR_CONNECTION_TIMEDOUT = -4; - public final static int LEVIN_ERROR_CONNECTION_NO_DUPLEX_PROTOCOL = -5; - public final static int LEVIN_ERROR_CONNECTION_HANDLER_NOT_DEFINED = -6; - public final static int LEVIN_ERROR_FORMAT = -7; - - public final static int P2P_SUPPORT_FLAG_FLUFFY_BLOCKS = 0x01; - public final static int P2P_SUPPORT_FLAGS = P2P_SUPPORT_FLAG_FLUFFY_BLOCKS; - - final private long signature; - final private long cb; - final public boolean haveToReturnData; - final public int command; - final public int returnCode; - final private int flags; - final private int protcolVersion; - final byte[] payload; - - final public Section payloadSection; - - // create a request - public Bucket(int command, byte[] payload) throws IOException { - this.signature = LEVIN_SIGNATURE; - this.cb = payload.length; - this.haveToReturnData = true; - this.command = command; - this.returnCode = 0; - this.flags = LEVIN_PACKET_REQUEST; - this.protcolVersion = LEVIN_PROTOCOL_VER_1; - this.payload = payload; - payloadSection = LevinReader.readPayload(payload); - } - - // create a response - public Bucket(int command, byte[] payload, int rc) throws IOException { - this.signature = LEVIN_SIGNATURE; - this.cb = payload.length; - this.haveToReturnData = false; - this.command = command; - this.returnCode = rc; - this.flags = LEVIN_PACKET_RESPONSE; - this.protcolVersion = LEVIN_PROTOCOL_VER_1; - this.payload = payload; - payloadSection = LevinReader.readPayload(payload); - } - - public Bucket(DataInput in) throws IOException { - signature = in.readLong(); - cb = in.readLong(); - haveToReturnData = in.readBoolean(); - command = in.readInt(); - returnCode = in.readInt(); - flags = in.readInt(); - protcolVersion = in.readInt(); - - if (signature == Bucket.LEVIN_SIGNATURE) { - if (cb > Integer.MAX_VALUE) - throw new IllegalArgumentException(); - payload = new byte[(int) cb]; - in.readFully(payload); - } else - throw new IllegalStateException(); - payloadSection = LevinReader.readPayload(payload); - } - - public Section getPayloadSection() { - return payloadSection; - } - - public void send(DataOutput out) throws IOException { - out.writeLong(signature); - out.writeLong(cb); - out.writeBoolean(haveToReturnData); - out.writeInt(command); - out.writeInt(returnCode); - out.writeInt(flags); - out.writeInt(protcolVersion); - out.write(payload); - } - - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("sig: ").append(signature).append("\n"); - sb.append("cb: ").append(cb).append("\n"); - sb.append("call: ").append(haveToReturnData).append("\n"); - sb.append("cmd: ").append(command).append("\n"); - sb.append("rc: ").append(returnCode).append("\n"); - sb.append("flags:").append(flags).append("\n"); - sb.append("proto:").append(protcolVersion).append("\n"); - sb.append(HexHelper.bytesToHex(payload)).append("\n"); - sb.append(payloadSection.toString()); - return sb.toString(); - } -} diff --git a/app/src/main/java/com/m2049r/levin/data/Section.java b/app/src/main/java/com/m2049r/levin/data/Section.java deleted file mode 100644 index 9be9732..0000000 --- a/app/src/main/java/com/m2049r/levin/data/Section.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.levin.data; - -import com.m2049r.levin.util.HexHelper; -import com.m2049r.levin.util.LevinReader; -import com.m2049r.levin.util.LevinWriter; -import com.m2049r.levin.util.LittleEndianDataOutputStream; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutput; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class Section { - - // constants copied from monero p2p & epee - - static final public int PORTABLE_STORAGE_SIGNATUREA = 0x01011101; - static final public int PORTABLE_STORAGE_SIGNATUREB = 0x01020101; - - static final public byte PORTABLE_STORAGE_FORMAT_VER = 1; - - static final public byte PORTABLE_RAW_SIZE_MARK_MASK = 0x03; - static final public byte PORTABLE_RAW_SIZE_MARK_BYTE = 0; - static final public byte PORTABLE_RAW_SIZE_MARK_WORD = 1; - static final public byte PORTABLE_RAW_SIZE_MARK_DWORD = 2; - static final public byte PORTABLE_RAW_SIZE_MARK_INT64 = 3; - - static final long MAX_STRING_LEN_POSSIBLE = 2000000000; // do not let string be so big - - // data types - static final public byte SERIALIZE_TYPE_INT64 = 1; - static final public byte SERIALIZE_TYPE_INT32 = 2; - static final public byte SERIALIZE_TYPE_INT16 = 3; - static final public byte SERIALIZE_TYPE_INT8 = 4; - static final public byte SERIALIZE_TYPE_UINT64 = 5; - static final public byte SERIALIZE_TYPE_UINT32 = 6; - static final public byte SERIALIZE_TYPE_UINT16 = 7; - static final public byte SERIALIZE_TYPE_UINT8 = 8; - static final public byte SERIALIZE_TYPE_DUOBLE = 9; - static final public byte SERIALIZE_TYPE_STRING = 10; - static final public byte SERIALIZE_TYPE_BOOL = 11; - static final public byte SERIALIZE_TYPE_OBJECT = 12; - static final public byte SERIALIZE_TYPE_ARRAY = 13; - - static final public byte SERIALIZE_FLAG_ARRAY = (byte) 0x80; - - private final Map entries = new HashMap(); - - public void add(String key, Object entry) { - entries.put(key, entry); - } - - public int size() { - return entries.size(); - } - - public Set> entrySet() { - return entries.entrySet(); - } - - public Object get(String key) { - return entries.get(key); - } - - public String toString() { - final StringBuilder sb = new StringBuilder(); - sb.append("\n"); - for (Map.Entry entry : entries.entrySet()) { - sb.append(entry.getKey()).append("="); - final Object value = entry.getValue(); - if (value instanceof List) { - @SuppressWarnings("unchecked") final List list = (List) value; - for (Object listEntry : list) { - sb.append(listEntry.toString()).append("\n"); - } - } else if (value instanceof String) { - sb.append("(").append(value).append(")\n"); - } else if (value instanceof byte[]) { - sb.append(HexHelper.bytesToHex((byte[]) value)).append("\n"); - } else { - sb.append(value.toString()).append("\n"); - } - } - return sb.toString(); - } - - static public Section fromByteArray(byte[] buffer) { - try { - return LevinReader.readPayload(buffer); - } catch (IOException ex) { - throw new IllegalStateException(); - } - } - - public byte[] asByteArray() { - try { - ByteArrayOutputStream bas = new ByteArrayOutputStream(); - DataOutput out = new LittleEndianDataOutputStream(bas); - LevinWriter writer = new LevinWriter(out); - writer.writePayload(this); - return bas.toByteArray(); - } catch (IOException ex) { - throw new IllegalStateException(); - } - } -} diff --git a/app/src/main/java/com/m2049r/levin/scanner/Dispatcher.java b/app/src/main/java/com/m2049r/levin/scanner/Dispatcher.java deleted file mode 100644 index b9f8be5..0000000 --- a/app/src/main/java/com/m2049r/levin/scanner/Dispatcher.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.levin.scanner; - -import com.m2049r.xmrwallet.data.NodeInfo; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import timber.log.Timber; - -public class Dispatcher implements PeerRetriever.OnGetPeers { - static final public int NUM_THREADS = 50; - static final public int MAX_PEERS = 1000; - static final public long MAX_TIME = 30000000000L; //30 seconds - - private int peerCount = 0; - final private Set knownNodes = new HashSet<>(); // set of nodes to test - final private Set rpcNodes = new HashSet<>(); // set of RPC nodes we like - final private ExecutorService exeService = Executors.newFixedThreadPool(NUM_THREADS); - - public interface Listener { - void onGet(NodeInfo nodeInfo); - } - - private Listener listener; - - public Dispatcher(Listener listener) { - this.listener = listener; - } - - public Set getRpcNodes() { - return rpcNodes; - } - - public int getPeerCount() { - return peerCount; - } - - public boolean getMorePeers() { - return peerCount < MAX_PEERS; - } - - public void awaitTermination(int nodesToFind) { - try { - final long t = System.nanoTime(); - while (!jobs.isEmpty()) { - try { - Timber.d("Remaining jobs %d", jobs.size()); - final PeerRetriever retrievedPeer = jobs.poll().get(); - if (retrievedPeer.isGood() && getMorePeers()) - retrievePeers(retrievedPeer); - final NodeInfo nodeInfo = retrievedPeer.getNodeInfo(); - Timber.d("Retrieved %s", nodeInfo); - if ((nodeInfo.isValid() || nodeInfo.isFavourite())) { - nodeInfo.setDefaultName(); - rpcNodes.add(nodeInfo); - Timber.d("RPC: %s", nodeInfo); - // the following is not totally correct but it works (otherwise we need to - // load much more before filtering - but we don't have time - if (listener != null) listener.onGet(nodeInfo); - if (rpcNodes.size() >= nodesToFind) { - Timber.d("are we done here?"); - filterRpcNodes(); - if (rpcNodes.size() >= nodesToFind) { - Timber.d("we're done here"); - break; - } - } - } - if (System.nanoTime() - t > MAX_TIME) break; // watchdog - } catch (ExecutionException ex) { - Timber.d(ex); // tell us about it and continue - } - } - } catch (InterruptedException ex) { - Timber.d(ex); - } finally { - Timber.d("Shutting down!"); - exeService.shutdownNow(); - try { - exeService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); - } catch (InterruptedException ex) { - Timber.d(ex); - } - } - filterRpcNodes(); - } - - static final public int HEIGHT_WINDOW = 1; - - private boolean testHeight(long height, long consensus) { - return (height >= (consensus - HEIGHT_WINDOW)) - && (height <= (consensus + HEIGHT_WINDOW)); - } - - private long calcConsensusHeight() { - Timber.d("Calc Consensus height from %d nodes", rpcNodes.size()); - final Map nodeHeights = new TreeMap(); - for (NodeInfo info : rpcNodes) { - if (!info.isValid()) continue; - Integer h = nodeHeights.get(info.getHeight()); - if (h == null) - h = 0; - nodeHeights.put(info.getHeight(), h + 1); - } - long consensusHeight = 0; - long consensusCount = 0; - for (Map.Entry entry : nodeHeights.entrySet()) { - final long entryHeight = entry.getKey(); - int count = 0; - for (long i = entryHeight - HEIGHT_WINDOW; i <= entryHeight + HEIGHT_WINDOW; i++) { - Integer v = nodeHeights.get(i); - if (v == null) - v = 0; - count += v; - } - if (count >= consensusCount) { - consensusCount = count; - consensusHeight = entryHeight; - } - Timber.d("%d - %d/%d", entryHeight, count, entry.getValue()); - } - return consensusHeight; - } - - private void filterRpcNodes() { - long consensus = calcConsensusHeight(); - Timber.d("Consensus Height = %d for %d nodes", consensus, rpcNodes.size()); - for (Iterator iter = rpcNodes.iterator(); iter.hasNext(); ) { - NodeInfo info = iter.next(); - // don't remove favourites - if (!info.isFavourite()) { - if (!testHeight(info.getHeight(), consensus)) { - iter.remove(); - Timber.d("Removed %s", info); - } - } - } - } - - // TODO: does this NEED to be a ConcurrentLinkedDeque? - private ConcurrentLinkedDeque> jobs = new ConcurrentLinkedDeque<>(); - - private void retrievePeer(NodeInfo nodeInfo) { - if (knownNodes.add(nodeInfo)) { - Timber.d("\t%d:%s", knownNodes.size(), nodeInfo); - jobs.add(exeService.submit(new PeerRetriever(nodeInfo, this))); - peerCount++; // jobs.size() does not perform well - } - } - - private void retrievePeers(PeerRetriever peer) { - for (LevinPeer levinPeer : peer.getPeers()) { - if (getMorePeers()) - retrievePeer(new NodeInfo(levinPeer)); - else - break; - } - } - - public void seedPeers(Collection seedNodes) { - for (NodeInfo node : seedNodes) { - if (node.isFavourite()) { - rpcNodes.add(node); - if (listener != null) listener.onGet(node); - } - retrievePeer(node); - } - } -} diff --git a/app/src/main/java/com/m2049r/levin/scanner/LevinPeer.java b/app/src/main/java/com/m2049r/levin/scanner/LevinPeer.java deleted file mode 100644 index aba2fa8..0000000 --- a/app/src/main/java/com/m2049r/levin/scanner/LevinPeer.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.levin.scanner; - -import java.net.InetAddress; -import java.net.InetSocketAddress; - -public class LevinPeer { - final public InetSocketAddress socketAddress; - final public int version; - final public long height; - final public String top; - - - public InetSocketAddress getSocketAddress() { - return socketAddress; - } - - LevinPeer(InetAddress address, int port, int version, long height, String top) { - this.socketAddress = new InetSocketAddress(address, port); - this.version = version; - this.height = height; - this.top = top; - } -} diff --git a/app/src/main/java/com/m2049r/levin/scanner/PeerRetriever.java b/app/src/main/java/com/m2049r/levin/scanner/PeerRetriever.java deleted file mode 100644 index fcdc4a0..0000000 --- a/app/src/main/java/com/m2049r/levin/scanner/PeerRetriever.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.levin.scanner; - -import com.m2049r.levin.data.Bucket; -import com.m2049r.levin.data.Section; -import com.m2049r.levin.util.HexHelper; -import com.m2049r.levin.util.LittleEndianDataInputStream; -import com.m2049r.levin.util.LittleEndianDataOutputStream; -import com.m2049r.xmrwallet.data.NodeInfo; -import com.m2049r.xmrwallet.util.Helper; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Random; -import java.util.concurrent.Callable; - -import timber.log.Timber; - -public class PeerRetriever implements Callable { - static final public int CONNECT_TIMEOUT = 500; //ms - static final public int SOCKET_TIMEOUT = 500; //ms - static final public long PEER_ID = new Random().nextLong(); - static final private byte[] HANDSHAKE = handshakeRequest().asByteArray(); - static final private byte[] FLAGS_RESP = flagsResponse().asByteArray(); - - final private List peers = new ArrayList<>(); - - private NodeInfo nodeInfo; - private OnGetPeers onGetPeersCallback; - - public interface OnGetPeers { - boolean getMorePeers(); - } - - public PeerRetriever(NodeInfo nodeInfo, OnGetPeers onGetPeers) { - this.nodeInfo = nodeInfo; - this.onGetPeersCallback = onGetPeers; - } - - public NodeInfo getNodeInfo() { - return nodeInfo; - } - - public boolean isGood() { - return !peers.isEmpty(); - } - - public List getPeers() { - return peers; - } - - public PeerRetriever call() { - if (isGood()) // we have already been called? - throw new IllegalStateException(); - // first check for an rpc service - nodeInfo.findRpcService(); - if (onGetPeersCallback.getMorePeers()) - try { - Timber.d("%s CONN", nodeInfo.getLevinSocketAddress()); - if (!connect()) - return this; - Bucket handshakeBucket = new Bucket(Bucket.COMMAND_HANDSHAKE_ID, HANDSHAKE); - handshakeBucket.send(getDataOutput()); - - while (true) {// wait for response (which may never come) - Bucket recv = new Bucket(getDataInput()); // times out after SOCKET_TIMEOUT - if ((recv.command == Bucket.COMMAND_HANDSHAKE_ID) - && (!recv.haveToReturnData)) { - readAddressList(recv.payloadSection); - return this; - } else if ((recv.command == Bucket.COMMAND_REQUEST_SUPPORT_FLAGS_ID) - && (recv.haveToReturnData)) { - Bucket flagsBucket = new Bucket(Bucket.COMMAND_REQUEST_SUPPORT_FLAGS_ID, FLAGS_RESP, 1); - flagsBucket.send(getDataOutput()); - } else {// and ignore others - Timber.d("Ignored LEVIN COMMAND %d", recv.command); - } - } - } catch (IOException ex) { - } finally { - disconnect(); // we have what we want - byebye - Timber.d("%s DISCONN", nodeInfo.getLevinSocketAddress()); - } - return this; - } - - private void readAddressList(Section section) { - Section data = (Section) section.get("payload_data"); - int topVersion = (Integer) data.get("top_version"); - long currentHeight = (Long) data.get("current_height"); - String topId = HexHelper.bytesToHex((byte[]) data.get("top_id")); - Timber.d("PAYLOAD_DATA %d/%d/%s", topVersion, currentHeight, topId); - - @SuppressWarnings("unchecked") - List
peerList = (List
) section.get("local_peerlist_new"); - if (peerList != null) { - for (Section peer : peerList) { - Section adr = (Section) peer.get("adr"); - Integer type = (Integer) adr.get("type"); - if ((type == null) || (type != 1)) - continue; - Section addr = (Section) adr.get("addr"); - if (addr == null) - continue; - Integer ip = (Integer) addr.get("m_ip"); - if (ip == null) - continue; - Integer sport = (Integer) addr.get("m_port"); - if (sport == null) - continue; - int port = sport; - if (port < 0) // port is unsigned - port = port + 0x10000; - InetAddress inet = HexHelper.toInetAddress(ip); - // make sure this is an address we want to talk to (i.e. a remote address) - if (!inet.isSiteLocalAddress() && !inet.isAnyLocalAddress() - && !inet.isLoopbackAddress() - && !inet.isMulticastAddress() - && !inet.isLinkLocalAddress()) { - peers.add(new LevinPeer(inet, port, topVersion, currentHeight, topId)); - } - } - } - } - - private Socket socket = null; - - private boolean connect() { - if (socket != null) throw new IllegalStateException(); - try { - socket = new Socket(); - socket.connect(nodeInfo.getLevinSocketAddress(), CONNECT_TIMEOUT); - socket.setSoTimeout(SOCKET_TIMEOUT); - } catch (IOException ex) { - //Timber.d(ex); - return false; - } - return true; - } - - private boolean isConnected() { - return socket.isConnected(); - } - - private void disconnect() { - try { - dataInput = null; - dataOutput = null; - if ((socket != null) && (!socket.isClosed())) { - socket.close(); - } - } catch (IOException ex) { - Timber.d(ex); - } finally { - socket = null; - } - } - - private DataOutput dataOutput = null; - - private DataOutput getDataOutput() throws IOException { - if (dataOutput == null) - synchronized (this) { - if (dataOutput == null) - dataOutput = new LittleEndianDataOutputStream( - socket.getOutputStream()); - } - return dataOutput; - } - - private DataInput dataInput = null; - - private DataInput getDataInput() throws IOException { - if (dataInput == null) - synchronized (this) { - if (dataInput == null) - dataInput = new LittleEndianDataInputStream( - socket.getInputStream()); - } - return dataInput; - } - - static private Section handshakeRequest() { - Section section = new Section(); // root object - - Section nodeData = new Section(); - nodeData.add("local_time", (new Date()).getTime()); - nodeData.add("my_port", 0); - byte[] networkId = Helper.hexToBytes("1230f171610441611731008216a1a110"); // mainnet - nodeData.add("network_id", networkId); - nodeData.add("peer_id", PEER_ID); - section.add("node_data", nodeData); - - Section payloadData = new Section(); - payloadData.add("cumulative_difficulty", 1L); - payloadData.add("current_height", 1L); - byte[] genesisHash = - Helper.hexToBytes("418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3"); - payloadData.add("top_id", genesisHash); - payloadData.add("top_version", (byte) 1); - section.add("payload_data", payloadData); - return section; - } - - static private Section flagsResponse() { - Section section = new Section(); // root object - section.add("support_flags", Bucket.P2P_SUPPORT_FLAGS); - return section; - } -} diff --git a/app/src/main/java/com/m2049r/levin/util/HexHelper.java b/app/src/main/java/com/m2049r/levin/util/HexHelper.java deleted file mode 100644 index 3c26527..0000000 --- a/app/src/main/java/com/m2049r/levin/util/HexHelper.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.levin.util; - -import java.math.BigInteger; -import java.net.InetAddress; -import java.net.UnknownHostException; - -public class HexHelper { - - static public String bytesToHex(byte[] data) { - if ((data != null) && (data.length > 0)) - return String.format("%0" + (data.length * 2) + "X", - new BigInteger(1, data)); - else - return ""; - } - - static public InetAddress toInetAddress(int ip) { - try { - String ipAddress = String.format("%d.%d.%d.%d", (ip & 0xff), - (ip >> 8 & 0xff), (ip >> 16 & 0xff), (ip >> 24 & 0xff)); - return InetAddress.getByName(ipAddress); - } catch (UnknownHostException ex) { - throw new IllegalArgumentException(ex); - } - } -} diff --git a/app/src/main/java/com/m2049r/levin/util/LevinReader.java b/app/src/main/java/com/m2049r/levin/util/LevinReader.java deleted file mode 100644 index fd67ca5..0000000 --- a/app/src/main/java/com/m2049r/levin/util/LevinReader.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.levin.util; - -import com.m2049r.levin.data.Section; - -import java.io.ByteArrayInputStream; -import java.io.DataInput; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; - -// Full Levin reader as seen on epee - -public class LevinReader { - private DataInput in; - - private LevinReader(byte[] buffer) { - ByteArrayInputStream bis = new ByteArrayInputStream(buffer); - in = new LittleEndianDataInputStream(bis); - } - - static public Section readPayload(byte[] payload) throws IOException { - LevinReader r = new LevinReader(payload); - return r.readPayload(); - } - - private Section readPayload() throws IOException { - if (in.readInt() != Section.PORTABLE_STORAGE_SIGNATUREA) - throw new IllegalStateException(); - if (in.readInt() != Section.PORTABLE_STORAGE_SIGNATUREB) - throw new IllegalStateException(); - if (in.readByte() != Section.PORTABLE_STORAGE_FORMAT_VER) - throw new IllegalStateException(); - return readSection(); - } - - private Section readSection() throws IOException { - Section section = new Section(); - long count = readVarint(); - while (count-- > 0) { - // read section name string - String sectionName = readSectionName(); - section.add(sectionName, loadStorageEntry()); - } - return section; - } - - private Object loadStorageArrayEntry(int type) throws IOException { - type &= ~Section.SERIALIZE_FLAG_ARRAY; - return readArrayEntry(type); - } - - private List readArrayEntry(int type) throws IOException { - List list = new ArrayList(); - long size = readVarint(); - while (size-- > 0) - list.add(read(type)); - return list; - } - - private Object read(int type) throws IOException { - switch (type) { - case Section.SERIALIZE_TYPE_UINT64: - case Section.SERIALIZE_TYPE_INT64: - return in.readLong(); - case Section.SERIALIZE_TYPE_UINT32: - case Section.SERIALIZE_TYPE_INT32: - return in.readInt(); - case Section.SERIALIZE_TYPE_UINT16: - return in.readUnsignedShort(); - case Section.SERIALIZE_TYPE_INT16: - return in.readShort(); - case Section.SERIALIZE_TYPE_UINT8: - return in.readUnsignedByte(); - case Section.SERIALIZE_TYPE_INT8: - return in.readByte(); - case Section.SERIALIZE_TYPE_OBJECT: - return readSection(); - case Section.SERIALIZE_TYPE_STRING: - return readByteArray(); - default: - throw new IllegalArgumentException("type " + type - + " not supported"); - } - } - - private Object loadStorageEntry() throws IOException { - int type = in.readUnsignedByte(); - if ((type & Section.SERIALIZE_FLAG_ARRAY) != 0) - return loadStorageArrayEntry(type); - if (type == Section.SERIALIZE_TYPE_ARRAY) - return readStorageEntryArrayEntry(); - else - return readStorageEntry(type); - } - - private Object readStorageEntry(int type) throws IOException { - return read(type); - } - - private Object readStorageEntryArrayEntry() throws IOException { - int type = in.readUnsignedByte(); - if ((type & Section.SERIALIZE_FLAG_ARRAY) != 0) - throw new IllegalStateException("wrong type sequences"); - return loadStorageArrayEntry(type); - } - - private String readSectionName() throws IOException { - int nameLen = in.readUnsignedByte(); - return readString(nameLen); - } - - private byte[] read(long count) throws IOException { - if (count > Integer.MAX_VALUE) - throw new IllegalArgumentException(); - int len = (int) count; - final byte buffer[] = new byte[len]; - in.readFully(buffer); - return buffer; - } - - private String readString(long count) throws IOException { - return new String(read(count), StandardCharsets.US_ASCII); - } - - private byte[] readByteArray(long count) throws IOException { - return read(count); - } - - private byte[] readByteArray() throws IOException { - long len = readVarint(); - return readByteArray(len); - } - - private long readVarint() throws IOException { - long v = 0; - int b = in.readUnsignedByte(); - int sizeMask = b & Section.PORTABLE_RAW_SIZE_MARK_MASK; - switch (sizeMask) { - case Section.PORTABLE_RAW_SIZE_MARK_BYTE: - v = b >>> 2; - break; - case Section.PORTABLE_RAW_SIZE_MARK_WORD: - v = readRest(b, 1) >>> 2; - break; - case Section.PORTABLE_RAW_SIZE_MARK_DWORD: - v = readRest(b, 3) >>> 2; - break; - case Section.PORTABLE_RAW_SIZE_MARK_INT64: - v = readRest(b, 7) >>> 2; - break; - default: - throw new IllegalStateException(); - } - return v; - } - - // this should be in LittleEndianDataInputStream because it has little - // endian logic - private long readRest(final int firstByte, final int bytes) throws IOException { - long result = firstByte; - for (int i = 1; i < bytes + 1; i++) { - result = result + (((long) in.readUnsignedByte()) << (8 * i)); - } - return result; - } - -} diff --git a/app/src/main/java/com/m2049r/levin/util/LevinWriter.java b/app/src/main/java/com/m2049r/levin/util/LevinWriter.java deleted file mode 100644 index ad2fe32..0000000 --- a/app/src/main/java/com/m2049r/levin/util/LevinWriter.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.levin.util; - -import com.m2049r.levin.data.Section; - -import java.io.DataOutput; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Map; - -// a simplified Levin Writer WITHOUT support for arrays - -public class LevinWriter { - private DataOutput out; - - public LevinWriter(DataOutput out) { - this.out = out; - } - - public void writePayload(Section section) throws IOException { - out.writeInt(Section.PORTABLE_STORAGE_SIGNATUREA); - out.writeInt(Section.PORTABLE_STORAGE_SIGNATUREB); - out.writeByte(Section.PORTABLE_STORAGE_FORMAT_VER); - putSection(section); - } - - private void writeSection(Section section) throws IOException { - out.writeByte(Section.SERIALIZE_TYPE_OBJECT); - putSection(section); - } - - private void putSection(Section section) throws IOException { - writeVarint(section.size()); - for (Map.Entry kv : section.entrySet()) { - byte[] key = kv.getKey().getBytes(StandardCharsets.US_ASCII); - out.writeByte(key.length); - out.write(key); - write(kv.getValue()); - } - } - - private void writeVarint(long i) throws IOException { - if (i <= 63) { - out.writeByte(((int) i << 2) | Section.PORTABLE_RAW_SIZE_MARK_BYTE); - } else if (i <= 16383) { - out.writeShort(((int) i << 2) | Section.PORTABLE_RAW_SIZE_MARK_WORD); - } else if (i <= 1073741823) { - out.writeInt(((int) i << 2) | Section.PORTABLE_RAW_SIZE_MARK_DWORD); - } else { - if (i > 4611686018427387903L) - throw new IllegalArgumentException(); - out.writeLong((i << 2) | Section.PORTABLE_RAW_SIZE_MARK_INT64); - } - } - - private void write(Object object) throws IOException { - if (object instanceof byte[]) { - byte[] value = (byte[]) object; - out.writeByte(Section.SERIALIZE_TYPE_STRING); - writeVarint(value.length); - out.write(value); - } else if (object instanceof String) { - byte[] value = ((String) object) - .getBytes(StandardCharsets.US_ASCII); - out.writeByte(Section.SERIALIZE_TYPE_STRING); - writeVarint(value.length); - out.write(value); - } else if (object instanceof Integer) { - out.writeByte(Section.SERIALIZE_TYPE_UINT32); - out.writeInt((int) object); - } else if (object instanceof Long) { - out.writeByte(Section.SERIALIZE_TYPE_UINT64); - out.writeLong((long) object); - } else if (object instanceof Byte) { - out.writeByte(Section.SERIALIZE_TYPE_UINT8); - out.writeByte((byte) object); - } else if (object instanceof Section) { - writeSection((Section) object); - } else { - throw new IllegalArgumentException(); - } - } -} diff --git a/app/src/main/java/com/m2049r/levin/util/LittleEndianDataInputStream.java b/app/src/main/java/com/m2049r/levin/util/LittleEndianDataInputStream.java deleted file mode 100644 index 3924eeb..0000000 --- a/app/src/main/java/com/m2049r/levin/util/LittleEndianDataInputStream.java +++ /dev/null @@ -1,564 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.levin.util; - -import java.io.DataInput; -import java.io.EOFException; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UTFDataFormatException; - -/** - * A little endian java.io.DataInputStream (without readLine()) - */ - -public class LittleEndianDataInputStream extends FilterInputStream implements - DataInput { - - /** - * Creates a DataInputStream that uses the specified underlying InputStream. - * - * @param in the specified input stream - */ - public LittleEndianDataInputStream(InputStream in) { - super(in); - } - - @Deprecated - public final String readLine() { - throw new UnsupportedOperationException(); - } - - /** - * Reads some number of bytes from the contained input stream and stores - * them into the buffer array b. The number of bytes actually - * read is returned as an integer. This method blocks until input data is - * available, end of file is detected, or an exception is thrown. - * - *

- * If b is null, a NullPointerException is thrown. - * If the length of b is zero, then no bytes are read and - * 0 is returned; otherwise, there is an attempt to read at - * least one byte. If no byte is available because the stream is at end of - * file, the value -1 is returned; otherwise, at least one byte - * is read and stored into b. - * - *

- * The first byte read is stored into element b[0], the next - * one into b[1], and so on. The number of bytes read is, at - * most, equal to the length of b. Let k be the - * number of bytes actually read; these bytes will be stored in elements - * b[0] through b[k-1], leaving elements - * b[k] through b[b.length-1] unaffected. - * - *

- * The read(b) method has the same effect as:

- * - *
-     * read(b, 0, b.length)
-     * 
- * - *
- * - * @param b the buffer into which the data is read. - * @return the total number of bytes read into the buffer, or - * -1 if there is no more data because the end of the - * stream has been reached. - * @throws IOException if the first byte cannot be read for any reason other than - * end of file, the stream has been closed and the underlying - * input stream does not support reading after close, or - * another I/O error occurs. - * @see FilterInputStream#in - * @see InputStream#read(byte[], int, int) - */ - public final int read(byte b[]) throws IOException { - return in.read(b, 0, b.length); - } - - /** - * Reads up to len bytes of data from the contained input - * stream into an array of bytes. An attempt is made to read as many as - * len bytes, but a smaller number may be read, possibly zero. - * The number of bytes actually read is returned as an integer. - * - *

- * This method blocks until input data is available, end of file is - * detected, or an exception is thrown. - * - *

- * If len is zero, then no bytes are read and 0 is - * returned; otherwise, there is an attempt to read at least one byte. If no - * byte is available because the stream is at end of file, the value - * -1 is returned; otherwise, at least one byte is read and - * stored into b. - * - *

- * The first byte read is stored into element b[off], the next - * one into b[off+1], and so on. The number of bytes read is, - * at most, equal to len. Let k be the number of bytes - * actually read; these bytes will be stored in elements b[off] - * through b[off+k-1], leaving elements - * b[off+k] through - * b[off+len-1] unaffected. - * - *

- * In every case, elements b[0] through b[off] and - * elements b[off+len] through b[b.length-1] are - * unaffected. - * - * @param b the buffer into which the data is read. - * @param off the start offset in the destination array b - * @param len the maximum number of bytes read. - * @return the total number of bytes read into the buffer, or - * -1 if there is no more data because the end of the - * stream has been reached. - * @throws NullPointerException If b is null. - * @throws IndexOutOfBoundsException If off is negative, len is - * negative, or len is greater than - * b.length - off - * @throws IOException if the first byte cannot be read for any reason other than - * end of file, the stream has been closed and the underlying - * input stream does not support reading after close, or - * another I/O error occurs. - * @see FilterInputStream#in - * @see InputStream#read(byte[], int, int) - */ - public final int read(byte b[], int off, int len) throws IOException { - return in.read(b, off, len); - } - - /** - * See the general contract of the readFully method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @param b the buffer into which the data is read. - * @throws EOFException if this input stream reaches the end before reading all - * the bytes. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @see FilterInputStream#in - */ - public final void readFully(byte b[]) throws IOException { - readFully(b, 0, b.length); - } - - /** - * See the general contract of the readFully method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @param b the buffer into which the data is read. - * @param off the start offset of the data. - * @param len the number of bytes to read. - * @throws EOFException if this input stream reaches the end before reading all - * the bytes. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @see FilterInputStream#in - */ - public final void readFully(byte b[], int off, int len) throws IOException { - if (len < 0) - throw new IndexOutOfBoundsException(); - int n = 0; - while (n < len) { - int count = in.read(b, off + n, len - n); - if (count < 0) - throw new EOFException(); - n += count; - } - } - - /** - * See the general contract of the skipBytes method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @param n the number of bytes to be skipped. - * @return the actual number of bytes skipped. - * @throws IOException if the contained input stream does not support seek, or - * the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - */ - public final int skipBytes(int n) throws IOException { - int total = 0; - int cur = 0; - - while ((total < n) && ((cur = (int) in.skip(n - total)) > 0)) { - total += cur; - } - - return total; - } - - /** - * See the general contract of the readBoolean method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @return the boolean value read. - * @throws EOFException if this input stream has reached the end. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @see FilterInputStream#in - */ - public final boolean readBoolean() throws IOException { - int ch = in.read(); - if (ch < 0) - throw new EOFException(); - return (ch != 0); - } - - /** - * See the general contract of the readByte method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @return the next byte of this input stream as a signed 8-bit - * byte. - * @throws EOFException if this input stream has reached the end. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @see FilterInputStream#in - */ - public final byte readByte() throws IOException { - int ch = in.read(); - if (ch < 0) - throw new EOFException(); - return (byte) (ch); - } - - /** - * See the general contract of the readUnsignedByte method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @return the next byte of this input stream, interpreted as an unsigned - * 8-bit number. - * @throws EOFException if this input stream has reached the end. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @see FilterInputStream#in - */ - public final int readUnsignedByte() throws IOException { - int ch = in.read(); - if (ch < 0) - throw new EOFException(); - return ch; - } - - /** - * See the general contract of the readShort method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @return the next two bytes of this input stream, interpreted as a signed - * 16-bit number. - * @throws EOFException if this input stream reaches the end before reading two - * bytes. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @see FilterInputStream#in - */ - public final short readShort() throws IOException { - int ch1 = in.read(); - int ch2 = in.read(); - if ((ch1 | ch2) < 0) - throw new EOFException(); - return (short) ((ch1 << 0) + (ch2 << 8)); - } - - /** - * See the general contract of the readUnsignedShort method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @return the next two bytes of this input stream, interpreted as an - * unsigned 16-bit integer. - * @throws EOFException if this input stream reaches the end before reading two - * bytes. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @see FilterInputStream#in - */ - public final int readUnsignedShort() throws IOException { - int ch1 = in.read(); - int ch2 = in.read(); - if ((ch1 | ch2) < 0) - throw new EOFException(); - return (ch1 << 0) + (ch2 << 8); - } - - /** - * See the general contract of the readChar method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @return the next two bytes of this input stream, interpreted as a - * char. - * @throws EOFException if this input stream reaches the end before reading two - * bytes. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @see FilterInputStream#in - */ - public final char readChar() throws IOException { - int ch1 = in.read(); - int ch2 = in.read(); - if ((ch1 | ch2) < 0) - throw new EOFException(); - return (char) ((ch1 << 0) + (ch2 << 8)); - } - - /** - * See the general contract of the readInt method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @return the next four bytes of this input stream, interpreted as an - * int. - * @throws EOFException if this input stream reaches the end before reading four - * bytes. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @see FilterInputStream#in - */ - public final int readInt() throws IOException { - int ch1 = in.read(); - int ch2 = in.read(); - int ch3 = in.read(); - int ch4 = in.read(); - if ((ch1 | ch2 | ch3 | ch4) < 0) - throw new EOFException(); - return ((ch1 << 0) + (ch2 << 8) + (ch3 << 16) + (ch4 << 24)); - } - - private byte readBuffer[] = new byte[8]; - - /** - * See the general contract of the readLong method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @return the next eight bytes of this input stream, interpreted as a - * long. - * @throws EOFException if this input stream reaches the end before reading eight - * bytes. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @see FilterInputStream#in - */ - public final long readLong() throws IOException { - readFully(readBuffer, 0, 8); - return (((long) readBuffer[7] << 56) - + ((long) (readBuffer[6] & 255) << 48) - + ((long) (readBuffer[5] & 255) << 40) - + ((long) (readBuffer[4] & 255) << 32) - + ((long) (readBuffer[3] & 255) << 24) - + ((readBuffer[2] & 255) << 16) + ((readBuffer[1] & 255) << 8) + ((readBuffer[0] & 255) << 0)); - } - - /** - * See the general contract of the readFloat method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @return the next four bytes of this input stream, interpreted as a - * float. - * @throws EOFException if this input stream reaches the end before reading four - * bytes. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @see java.io.DataInputStream#readInt() - * @see Float#intBitsToFloat(int) - */ - public final float readFloat() throws IOException { - return Float.intBitsToFloat(readInt()); - } - - /** - * See the general contract of the readDouble method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @return the next eight bytes of this input stream, interpreted as a - * double. - * @throws EOFException if this input stream reaches the end before reading eight - * bytes. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @see java.io.DataInputStream#readLong() - * @see Double#longBitsToDouble(long) - */ - public final double readDouble() throws IOException { - return Double.longBitsToDouble(readLong()); - } - - /** - * See the general contract of the readUTF method of - * DataInput. - *

- * Bytes for this operation are read from the contained input stream. - * - * @return a Unicode string. - * @throws EOFException if this input stream reaches the end before reading all - * the bytes. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @throws UTFDataFormatException if the bytes do not represent a valid modified UTF-8 - * encoding of a string. - * @see java.io.DataInputStream#readUTF(DataInput) - */ - public final String readUTF() throws IOException { - return readUTF(this); - } - - /** - * working arrays initialized on demand by readUTF - */ - private byte bytearr[] = new byte[80]; - private char chararr[] = new char[80]; - - /** - * Reads from the stream in a representation of a Unicode - * character string encoded in modified UTF-8 format; this - * string of characters is then returned as a String. The - * details of the modified UTF-8 representation are exactly the same as for - * the readUTF method of DataInput. - * - * @param in a data input stream. - * @return a Unicode string. - * @throws EOFException if the input stream reaches the end before all the bytes. - * @throws IOException the stream has been closed and the contained input stream - * does not support reading after close, or another I/O error - * occurs. - * @throws UTFDataFormatException if the bytes do not represent a valid modified UTF-8 - * encoding of a Unicode string. - * @see java.io.DataInputStream#readUnsignedShort() - */ - public final static String readUTF(DataInput in) throws IOException { - int utflen = in.readUnsignedShort(); - byte[] bytearr = null; - char[] chararr = null; - if (in instanceof LittleEndianDataInputStream) { - LittleEndianDataInputStream dis = (LittleEndianDataInputStream) in; - if (dis.bytearr.length < utflen) { - dis.bytearr = new byte[utflen * 2]; - dis.chararr = new char[utflen * 2]; - } - chararr = dis.chararr; - bytearr = dis.bytearr; - } else { - bytearr = new byte[utflen]; - chararr = new char[utflen]; - } - - int c, char2, char3; - int count = 0; - int chararr_count = 0; - - in.readFully(bytearr, 0, utflen); - - while (count < utflen) { - c = (int) bytearr[count] & 0xff; - if (c > 127) - break; - count++; - chararr[chararr_count++] = (char) c; - } - - while (count < utflen) { - c = (int) bytearr[count] & 0xff; - switch (c >> 4) { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - /* 0xxxxxxx */ - count++; - chararr[chararr_count++] = (char) c; - break; - case 12: - case 13: - /* 110x xxxx 10xx xxxx */ - count += 2; - if (count > utflen) - throw new UTFDataFormatException( - "malformed input: partial character at end"); - char2 = (int) bytearr[count - 1]; - if ((char2 & 0xC0) != 0x80) - throw new UTFDataFormatException( - "malformed input around byte " + count); - chararr[chararr_count++] = (char) (((c & 0x1F) << 6) | (char2 & 0x3F)); - break; - case 14: - /* 1110 xxxx 10xx xxxx 10xx xxxx */ - count += 3; - if (count > utflen) - throw new UTFDataFormatException( - "malformed input: partial character at end"); - char2 = (int) bytearr[count - 2]; - char3 = (int) bytearr[count - 1]; - if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) - throw new UTFDataFormatException( - "malformed input around byte " + (count - 1)); - chararr[chararr_count++] = (char) (((c & 0x0F) << 12) - | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0)); - break; - default: - /* 10xx xxxx, 1111 xxxx */ - throw new UTFDataFormatException("malformed input around byte " - + count); - } - } - // The number of chars produced may be less than utflen - return new String(chararr, 0, chararr_count); - } -} diff --git a/app/src/main/java/com/m2049r/levin/util/LittleEndianDataOutputStream.java b/app/src/main/java/com/m2049r/levin/util/LittleEndianDataOutputStream.java deleted file mode 100644 index fbf7e0c..0000000 --- a/app/src/main/java/com/m2049r/levin/util/LittleEndianDataOutputStream.java +++ /dev/null @@ -1,403 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.levin.util; - -import java.io.DataOutput; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UTFDataFormatException; - -/** - * A little endian java.io.DataOutputStream - */ - -public class LittleEndianDataOutputStream extends FilterOutputStream implements - DataOutput { - - /** - * The number of bytes written to the data output stream so far. If this - * counter overflows, it will be wrapped to Integer.MAX_VALUE. - */ - protected int written; - - /** - * Creates a new data output stream to write data to the specified - * underlying output stream. The counter written is set to - * zero. - * - * @param out the underlying output stream, to be saved for later use. - * @see FilterOutputStream#out - */ - public LittleEndianDataOutputStream(OutputStream out) { - super(out); - } - - /** - * Increases the written counter by the specified value until it reaches - * Integer.MAX_VALUE. - */ - private void incCount(int value) { - int temp = written + value; - if (temp < 0) { - temp = Integer.MAX_VALUE; - } - written = temp; - } - - /** - * Writes the specified byte (the low eight bits of the argument - * b) to the underlying output stream. If no exception is - * thrown, the counter written is incremented by 1 - * . - *

- * Implements the write method of OutputStream. - * - * @param b the byte to be written. - * @throws IOException if an I/O error occurs. - * @see FilterOutputStream#out - */ - public synchronized void write(int b) throws IOException { - out.write(b); - incCount(1); - } - - /** - * Writes len bytes from the specified byte array starting at - * offset off to the underlying output stream. If no exception - * is thrown, the counter written is incremented by - * len. - * - * @param b the data. - * @param off the start offset in the data. - * @param len the number of bytes to write. - * @throws IOException if an I/O error occurs. - * @see FilterOutputStream#out - */ - public synchronized void write(byte b[], int off, int len) - throws IOException { - out.write(b, off, len); - incCount(len); - } - - /** - * Flushes this data output stream. This forces any buffered output bytes to - * be written out to the stream. - *

- * The flush method of DataOutputStream calls the - * flush method of its underlying output stream. - * - * @throws IOException if an I/O error occurs. - * @see FilterOutputStream#out - * @see OutputStream#flush() - */ - public void flush() throws IOException { - out.flush(); - } - - /** - * Writes a boolean to the underlying output stream as a 1-byte - * value. The value true is written out as the value - * (byte)1; the value false is written out as the - * value (byte)0. If no exception is thrown, the counter - * written is incremented by 1. - * - * @param v a boolean value to be written. - * @throws IOException if an I/O error occurs. - * @see FilterOutputStream#out - */ - public final void writeBoolean(boolean v) throws IOException { - out.write(v ? 1 : 0); - incCount(1); - } - - /** - * Writes out a byte to the underlying output stream as a - * 1-byte value. If no exception is thrown, the counter written - * is incremented by 1. - * - * @param v a byte value to be written. - * @throws IOException if an I/O error occurs. - * @see FilterOutputStream#out - */ - public final void writeByte(int v) throws IOException { - out.write(v); - incCount(1); - } - - /** - * Writes a short to the underlying output stream as two bytes, - * low byte first. If no exception is thrown, the counter - * written is incremented by 2. - * - * @param v a short to be written. - * @throws IOException if an I/O error occurs. - * @see FilterOutputStream#out - */ - public final void writeShort(int v) throws IOException { - out.write((v >>> 0) & 0xFF); - out.write((v >>> 8) & 0xFF); - incCount(2); - } - - /** - * Writes a char to the underlying output stream as a 2-byte - * value, low byte first. If no exception is thrown, the counter - * written is incremented by 2. - * - * @param v a char value to be written. - * @throws IOException if an I/O error occurs. - * @see FilterOutputStream#out - */ - public final void writeChar(int v) throws IOException { - out.write((v >>> 0) & 0xFF); - out.write((v >>> 8) & 0xFF); - incCount(2); - } - - /** - * Writes an int to the underlying output stream as four bytes, - * low byte first. If no exception is thrown, the counter - * written is incremented by 4. - * - * @param v an int to be written. - * @throws IOException if an I/O error occurs. - * @see FilterOutputStream#out - */ - public final void writeInt(int v) throws IOException { - out.write((v >>> 0) & 0xFF); - out.write((v >>> 8) & 0xFF); - out.write((v >>> 16) & 0xFF); - out.write((v >>> 24) & 0xFF); - incCount(4); - } - - private byte writeBuffer[] = new byte[8]; - - /** - * Writes a long to the underlying output stream as eight - * bytes, low byte first. In no exception is thrown, the counter - * written is incremented by 8. - * - * @param v a long to be written. - * @throws IOException if an I/O error occurs. - * @see FilterOutputStream#out - */ - public final void writeLong(long v) throws IOException { - writeBuffer[7] = (byte) (v >>> 56); - writeBuffer[6] = (byte) (v >>> 48); - writeBuffer[5] = (byte) (v >>> 40); - writeBuffer[4] = (byte) (v >>> 32); - writeBuffer[3] = (byte) (v >>> 24); - writeBuffer[2] = (byte) (v >>> 16); - writeBuffer[1] = (byte) (v >>> 8); - writeBuffer[0] = (byte) (v >>> 0); - out.write(writeBuffer, 0, 8); - incCount(8); - } - - /** - * Converts the float argument to an int using the - * floatToIntBits method in class Float, and then - * writes that int value to the underlying output stream as a - * 4-byte quantity, low byte first. If no exception is thrown, the counter - * written is incremented by 4. - * - * @param v a float value to be written. - * @throws IOException if an I/O error occurs. - * @see FilterOutputStream#out - * @see Float#floatToIntBits(float) - */ - public final void writeFloat(float v) throws IOException { - writeInt(Float.floatToIntBits(v)); - } - - /** - * Converts the double argument to a long using the - * doubleToLongBits method in class Double, and - * then writes that long value to the underlying output stream - * as an 8-byte quantity, low byte first. If no exception is thrown, the - * counter written is incremented by 8. - * - * @param v a double value to be written. - * @throws IOException if an I/O error occurs. - * @see FilterOutputStream#out - * @see Double#doubleToLongBits(double) - */ - public final void writeDouble(double v) throws IOException { - writeLong(Double.doubleToLongBits(v)); - } - - /** - * Writes out the string to the underlying output stream as a sequence of - * bytes. Each character in the string is written out, in sequence, by - * discarding its high eight bits. If no exception is thrown, the counter - * written is incremented by the length of s. - * - * @param s a string of bytes to be written. - * @throws IOException if an I/O error occurs. - * @see FilterOutputStream#out - */ - public final void writeBytes(String s) throws IOException { - int len = s.length(); - for (int i = 0; i < len; i++) { - out.write((byte) s.charAt(i)); - } - incCount(len); - } - - /** - * Writes a string to the underlying output stream as a sequence of - * characters. Each character is written to the data output stream as if by - * the writeChar method. If no exception is thrown, the counter - * written is incremented by twice the length of s - * . - * - * @param s a String value to be written. - * @throws IOException if an I/O error occurs. - * @see java.io.DataOutputStream#writeChar(int) - * @see FilterOutputStream#out - */ - public final void writeChars(String s) throws IOException { - int len = s.length(); - for (int i = 0; i < len; i++) { - int v = s.charAt(i); - out.write((v >>> 0) & 0xFF); - out.write((v >>> 8) & 0xFF); - } - incCount(len * 2); - } - - /** - * Writes a string to the underlying output stream using modified UTF-8 encoding in a - * machine-independent manner. - *

- * First, two bytes are written to the output stream as if by the - * writeShort method giving the number of bytes to follow. This - * value is the number of bytes actually written out, not the length of the - * string. Following the length, each character of the string is output, in - * sequence, using the modified UTF-8 encoding for the character. If no - * exception is thrown, the counter written is incremented by - * the total number of bytes written to the output stream. This will be at - * least two plus the length of str, and at most two plus - * thrice the length of str. - * - * @param str a string to be written. - * @throws IOException if an I/O error occurs. - */ - public final void writeUTF(String str) throws IOException { - writeUTF(str, this); - } - - /** - * bytearr is initialized on demand by writeUTF - */ - private byte[] bytearr = null; - - /** - * Writes a string to the specified DataOutput using modified UTF-8 encoding in a - * machine-independent manner. - *

- * First, two bytes are written to out as if by the writeShort - * method giving the number of bytes to follow. This value is the number of - * bytes actually written out, not the length of the string. Following the - * length, each character of the string is output, in sequence, using the - * modified UTF-8 encoding for the character. If no exception is thrown, the - * counter written is incremented by the total number of bytes - * written to the output stream. This will be at least two plus the length - * of str, and at most two plus thrice the length of - * str. - * - * @param str a string to be written. - * @param out destination to write to - * @return The number of bytes written out. - * @throws IOException if an I/O error occurs. - */ - static int writeUTF(String str, DataOutput out) throws IOException { - int strlen = str.length(); - int utflen = 0; - int c, count = 0; - - /* use charAt instead of copying String to char array */ - for (int i = 0; i < strlen; i++) { - c = str.charAt(i); - if ((c >= 0x0001) && (c <= 0x007F)) { - utflen++; - } else if (c > 0x07FF) { - utflen += 3; - } else { - utflen += 2; - } - } - - if (utflen > 65535) - throw new UTFDataFormatException("encoded string too long: " - + utflen + " bytes"); - - byte[] bytearr = null; - if (out instanceof LittleEndianDataOutputStream) { - LittleEndianDataOutputStream dos = (LittleEndianDataOutputStream) out; - if (dos.bytearr == null || (dos.bytearr.length < (utflen + 2))) - dos.bytearr = new byte[(utflen * 2) + 2]; - bytearr = dos.bytearr; - } else { - bytearr = new byte[utflen + 2]; - } - - bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF); - bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF); - - int i = 0; - for (i = 0; i < strlen; i++) { - c = str.charAt(i); - if (!((c >= 0x0001) && (c <= 0x007F))) - break; - bytearr[count++] = (byte) c; - } - - for (; i < strlen; i++) { - c = str.charAt(i); - if ((c >= 0x0001) && (c <= 0x007F)) { - bytearr[count++] = (byte) c; - - } else if (c > 0x07FF) { - bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F)); - bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F)); - bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); - } else { - bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F)); - bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); - } - } - out.write(bytearr, 0, utflen + 2); - return utflen + 2; - } - - /** - * Returns the current value of the counter written, the number - * of bytes written to this data output stream so far. If the counter - * overflows, it will be wrapped to Integer.MAX_VALUE. - * - * @return the value of the written field. - * @see java.io.DataOutputStream#written - */ - public final int size() { - return written; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/adapter/TransactionInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/adapter/TransactionInfoAdapter.java index ce6c17f..99aab83 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/adapter/TransactionInfoAdapter.java +++ b/app/src/main/java/com/m2049r/xmrwallet/adapter/TransactionInfoAdapter.java @@ -18,154 +18,33 @@ package com.m2049r.xmrwallet.adapter; import static com.m2049r.xmrwallet.util.DateHelper.DATETIME_FORMATTER; -import android.content.Context; import android.text.Html; import android.text.Spanned; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.core.content.ContextCompat; -import androidx.leanback.widget.DiffCallback; -import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.progressindicator.CircularProgressIndicator; import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.data.Crypto; import com.m2049r.xmrwallet.data.UserNotes; import com.m2049r.xmrwallet.model.TransactionInfo; -import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.ThemeHelper; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; -import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.Objects; import java.util.TimeZone; -import timber.log.Timber; - public class TransactionInfoAdapter extends RecyclerView.Adapter { private List localDataSet; private TxInfoAdapterListener listener = null; - /** - * Provide a reference to the type of views that you are using - * (custom ViewHolder). - */ - public static class ViewHolder extends RecyclerView.ViewHolder { - private final int outboundColour; - private final int inboundColour; - private final int pendingColour; - private final int failedColour; - private TxInfoAdapterListener listener = null; - private TextView amountTextView = null; - public ViewHolder(TxInfoAdapterListener listener, View view) { - super(view); - inboundColour = ThemeHelper.getThemedColor(view.getContext(), R.attr.positiveColor); - outboundColour = ThemeHelper.getThemedColor(view.getContext(), R.attr.negativeColor); - pendingColour = ThemeHelper.getThemedColor(view.getContext(), R.attr.neutralColor); - failedColour = ThemeHelper.getThemedColor(view.getContext(), R.attr.neutralColor); - this.listener = listener; - Calendar cal = Calendar.getInstance(); - TimeZone tz = cal.getTimeZone(); //get the local time zone. - DATETIME_FORMATTER.setTimeZone(tz); - } - - public void bind(TransactionInfo txInfo) { - String displayAmount = Helper.getDisplayAmount(txInfo.amount, Helper.DISPLAY_DIGITS_INFO); - - TextView confirmationsTextView = ((TextView)itemView.findViewById(R.id.tvConfirmations)); - CircularProgressIndicator confirmationsProgressBar = ((CircularProgressIndicator)itemView.findViewById(R.id.pbConfirmations)); - confirmationsProgressBar.setMax(TransactionInfo.CONFIRMATION); - this.amountTextView = ((TextView)itemView.findViewById(R.id.tx_amount)); - ((TextView)itemView.findViewById(R.id.tx_failed)).setVisibility(View.GONE); - if(txInfo.isFailed) { - ((TextView)itemView.findViewById(R.id.tx_amount)).setText(itemView.getContext().getString(R.string.tx_list_amount_negative, displayAmount)); - ((TextView)itemView.findViewById(R.id.tx_failed)).setVisibility(View.VISIBLE); - setTxColour(failedColour); - confirmationsTextView.setVisibility(View.GONE); - confirmationsProgressBar.setVisibility(View.GONE); - } else if(txInfo.isPending) { - setTxColour(pendingColour); - confirmationsProgressBar.setVisibility(View.GONE); - confirmationsProgressBar.setIndeterminate(true); - confirmationsProgressBar.setVisibility(View.VISIBLE); - confirmationsTextView.setVisibility(View.GONE); - } else if (txInfo.direction == TransactionInfo.Direction.Direction_In) { - setTxColour(inboundColour); - if (!txInfo.isConfirmed()) { - confirmationsProgressBar.setVisibility(View.VISIBLE); - final int confirmations = (int) txInfo.confirmations; - confirmationsProgressBar.setProgressCompat(confirmations, true); - final String confCount = Integer.toString(confirmations); - confirmationsTextView.setText(confCount); - if (confCount.length() == 1) // we only have space for character in the progress circle - confirmationsTextView.setVisibility(View.VISIBLE); - else - confirmationsTextView.setVisibility(View.GONE); - } else { - confirmationsProgressBar.setVisibility(View.GONE); - confirmationsTextView.setVisibility(View.GONE); - } - } else { - setTxColour(outboundColour); - confirmationsProgressBar.setVisibility(View.GONE); - confirmationsTextView.setVisibility(View.GONE); - } - - if (txInfo.direction == TransactionInfo.Direction.Direction_Out) { - ((TextView)itemView.findViewById(R.id.tx_amount)).setText(itemView.getContext().getString(R.string.tx_list_amount_negative, displayAmount)); - } else { - ((TextView)itemView.findViewById(R.id.tx_amount)).setText(itemView.getContext().getString(R.string.tx_list_amount_positive, displayAmount)); - } - - TextView paymentIdTextView = ((TextView)itemView.findViewById(R.id.tx_paymentid)); - String tag = null; - String info = ""; - UserNotes userNotes = new UserNotes(txInfo.notes); - if ((txInfo.addressIndex != 0) && (txInfo.direction == TransactionInfo.Direction.Direction_In)) - tag = txInfo.getDisplayLabel(); - if ((userNotes.note.isEmpty())) { - if (!txInfo.paymentId.equals("0000000000000000")) { - info = txInfo.paymentId; - } - } else { - info = userNotes.note; - } - if (tag == null) { - paymentIdTextView.setText(info); - } else { - Spanned label = Html.fromHtml(itemView.getContext().getString(R.string.tx_details_notes, - Integer.toHexString(ThemeHelper.getThemedColor(itemView.getContext(), R.attr.positiveColor) & 0xFFFFFF), - Integer.toHexString(ThemeHelper.getThemedColor(itemView.getContext(), android.R.attr.colorBackground) & 0xFFFFFF), - tag, info.isEmpty() ? "" : ("  " + info))); - paymentIdTextView.setText(label); - } - ((TextView)itemView.findViewById(R.id.tx_datetime)).setText(getDateTime(txInfo.timestamp)); - itemView.setOnClickListener(view -> { - listener.onClickTransaction(txInfo); - }); - } - - private void setTxColour(int clr) { - amountTextView.setTextColor(clr); - } - - private String getDateTime(long time) { - return DATETIME_FORMATTER.format(new Date(time * 1000)); - } - } - /** * Initialize the dataset of the Adapter. */ @@ -205,5 +84,114 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter { + listener.onClickTransaction(txInfo); + }); + } + + private void setTxColour(int clr) { + amountTextView.setTextColor(clr); + } + + private String getDateTime(long time) { + return DATETIME_FORMATTER.format(new Date(time * 1000)); + } + } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java b/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java deleted file mode 100644 index 3a5cecb..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.data; - -import android.net.Uri; - -import com.m2049r.xmrwallet.util.OpenAliasHelper; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import lombok.ToString; -import timber.log.Timber; - -@ToString -public class BarcodeData { - public enum Security { - NORMAL, - OA_NO_DNSSEC, - OA_DNSSEC - } - - final public Crypto asset; - final public List ambiguousAssets; - final public String address; - final public String addressName; - final public String amount; - final public String description; - final public Security security; - - public BarcodeData(List assets, String address) { - if (assets.isEmpty()) - throw new IllegalArgumentException("no assets specified"); - this.addressName = null; - this.description = null; - this.amount = null; - this.security = Security.NORMAL; - this.address = address; - if (assets.size() == 1) { - this.asset = assets.get(0); - this.ambiguousAssets = null; - } else { - this.asset = null; - this.ambiguousAssets = assets; - } - } - - public BarcodeData(Crypto asset, String address, String description, String amount) { - this(asset, address, null, description, amount, Security.NORMAL); - } - - public BarcodeData(Crypto asset, String address, String addressName, String description, String amount, Security security) { - this.ambiguousAssets = null; - this.asset = asset; - this.address = address; - this.addressName = addressName; - this.description = description; - this.amount = amount; - this.security = security; - } - - public Uri getUri() { - return Uri.parse(getUriString()); - } - - public String getUriString() { - if (asset != Crypto.XMR) throw new IllegalStateException("We can only do XMR stuff!"); - StringBuilder sb = new StringBuilder(); - sb.append(Crypto.XMR.getUriScheme()) - .append(':') - .append(address); - boolean first = true; - if ((description != null) && !description.isEmpty()) { - sb.append(first ? "?" : "&"); - first = false; - sb.append(Crypto.XMR.getUriMessage()).append('=').append(Uri.encode(description)); - } - if ((amount != null) && !amount.isEmpty()) { - sb.append(first ? "?" : "&"); - sb.append(Crypto.XMR.getUriAmount()).append('=').append(amount); - } - return sb.toString(); - } - - static private BarcodeData parseNaked(String address) { - List possibleAssets = new ArrayList<>(); - for (Crypto crypto : Crypto.values()) { - if (crypto.validate(address)) { - possibleAssets.add(crypto); - } - } - if (possibleAssets.isEmpty()) - return null; - return new BarcodeData(possibleAssets, address); - } - - static public BarcodeData parseUri(String uriString) { - Timber.d("parseBitUri=%s", uriString); - - URI uri; - try { - uri = new URI(uriString); - } catch (URISyntaxException ex) { - return null; - } - if (!uri.isOpaque()) return null; - final String scheme = uri.getScheme(); - Crypto crypto = Crypto.withScheme(scheme); - if (crypto == null) return null; - - String[] parts = uri.getRawSchemeSpecificPart().split("[?]"); - if ((parts.length <= 0) || (parts.length > 2)) { - Timber.d("invalid number of parts %d", parts.length); - return null; - } - - Map parms = new HashMap<>(); - if (parts.length == 2) { - String[] args = parts[1].split("&"); - for (String arg : args) { - String[] namevalue = arg.split("="); - if (namevalue.length == 0) { - continue; - } - parms.put(Uri.decode(namevalue[0]).toLowerCase(), - namevalue.length > 1 ? Uri.decode(namevalue[1]) : ""); - } - } - - String addressName = parms.get(crypto.getUriLabel()); - String description = parms.get(crypto.getUriMessage()); - String address = parts[0]; // no need to decode as there can be no special characters - if (address.isEmpty()) { - Timber.d("no address"); - return null; - } - if (!crypto.validate(address)) { - Timber.d("%s address (%s) invalid", crypto, address); - return null; - } - String amount = parms.get(crypto.getUriAmount()); - if ((amount != null) && (!amount.isEmpty())) { - try { - Double.parseDouble(amount); - } catch (NumberFormatException ex) { - Timber.d(ex.getLocalizedMessage()); - return null; // we have an amount but its not a number! - } - } - return new BarcodeData(crypto, address, addressName, description, amount, Security.NORMAL); - } - - - static public BarcodeData fromString(String qrCode) { - BarcodeData bcData = parseUri(qrCode); - if (bcData == null) { - // maybe it's naked? - bcData = parseNaked(qrCode); - } - if (bcData == null) { - // check for OpenAlias - bcData = parseOpenAlias(qrCode, false); - } - return bcData; - } - - static public BarcodeData parseOpenAlias(String oaString, boolean dnssec) { - Timber.d("parseOpenAlias=%s", oaString); - if (oaString == null) return null; - - Map oaAttrs = OpenAliasHelper.parse(oaString); - if (oaAttrs == null) return null; - - String oaAsset = oaAttrs.get(OpenAliasHelper.OA1_ASSET); - if (oaAsset == null) return null; - - String address = oaAttrs.get(OpenAliasHelper.OA1_ADDRESS); - if (address == null) return null; - - Crypto crypto = Crypto.withSymbol(oaAsset); - if (crypto == null) { - Timber.i("Unsupported OpenAlias asset %s", oaAsset); - return null; - } - if (!crypto.validate(address)) { - Timber.d("%s address invalid", crypto); - return null; - } - - String description = oaAttrs.get(OpenAliasHelper.OA1_DESCRIPTION); - if (description == null) { - description = oaAttrs.get(OpenAliasHelper.OA1_NAME); - } - String amount = oaAttrs.get(OpenAliasHelper.OA1_AMOUNT); - String addressName = oaAttrs.get(OpenAliasHelper.OA1_NAME); - - if (amount != null) { - try { - Double.parseDouble(amount); - } catch (NumberFormatException ex) { - Timber.d(ex.getLocalizedMessage()); - return null; // we have an amount but its not a number! - } - } - - Security sec = dnssec ? BarcodeData.Security.OA_DNSSEC : BarcodeData.Security.OA_NO_DNSSEC; - - return new BarcodeData(crypto, address, addressName, description, amount, sec); - } - - public boolean isAmbiguous() { - return ambiguousAssets != null; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/Crypto.java b/app/src/main/java/com/m2049r/xmrwallet/data/Crypto.java deleted file mode 100644 index 45b49e6..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/data/Crypto.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.m2049r.xmrwallet.data; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.util.validator.BitcoinAddressType; -import com.m2049r.xmrwallet.util.validator.BitcoinAddressValidator; -import com.m2049r.xmrwallet.util.validator.EthAddressValidator; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public enum Crypto { - XMR("XMR", true, "monero:tx_amount:recipient_name:tx_description", 0, R.drawable.ic_monero, R.drawable.ic_monero_bw, Wallet::isAddressValid), - BTC("BTC", true, "bitcoin:amount:label:message", 0, R.drawable.ic_xmrto_btc, R.drawable.ic_xmrto_btc_off, address -> { - return BitcoinAddressValidator.validate(address, BitcoinAddressType.BTC); - }), - DASH("DASH", true, "dash:amount:label:message", 0, R.drawable.ic_xmrto_dash, R.drawable.ic_xmrto_dash_off, address -> { - return BitcoinAddressValidator.validate(address, BitcoinAddressType.DASH); - }), - DOGE("DOGE", true, "dogecoin:amount:label:message", 0, R.drawable.ic_xmrto_doge, R.drawable.ic_xmrto_doge_off, address -> { - return BitcoinAddressValidator.validate(address, BitcoinAddressType.DOGE); - }), - ETH("ETH", false, "ethereum:amount:label:message", 0, R.drawable.ic_xmrto_eth, R.drawable.ic_xmrto_eth_off, EthAddressValidator::validate), - LTC("LTC", true, "litecoin:amount:label:message", 0, R.drawable.ic_xmrto_ltc, R.drawable.ic_xmrto_ltc_off, address -> { - return BitcoinAddressValidator.validate(address, BitcoinAddressType.LTC); - }); - - @Getter - @NonNull - private final String symbol; - @Getter - private final boolean casefull; - @NonNull - private final String uriSpec; - @Getter - private final int buttonId; - @Getter - private final int iconEnabledId; - @Getter - private final int iconDisabledId; - @NonNull - private final Validator validator; - - @Nullable - public static Crypto withScheme(@NonNull String scheme) { - for (Crypto crypto : values()) { - if (crypto.getUriScheme().equals(scheme)) return crypto; - } - return null; - } - - @Nullable - public static Crypto withSymbol(@NonNull String symbol) { - final String upperSymbol = symbol.toUpperCase(); - for (Crypto crypto : values()) { - if (crypto.symbol.equals(upperSymbol)) return crypto; - } - return null; - } - - interface Validator { - boolean validate(String address); - } - - // TODO maybe cache these segments - String getUriScheme() { - return uriSpec.split(":")[0]; - } - - String getUriAmount() { - return uriSpec.split(":")[1]; - } - - String getUriLabel() { - return uriSpec.split(":")[2]; - } - - String getUriMessage() { - return uriSpec.split(":")[3]; - } - - boolean validate(String address) { - return validator.validate(address); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/Node.java b/app/src/main/java/com/m2049r/xmrwallet/data/Node.java index 617975e..e0eca15 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/data/Node.java +++ b/app/src/main/java/com/m2049r/xmrwallet/data/Node.java @@ -35,69 +35,21 @@ public class Node { static public final String MAINNET = "mainnet"; static public final String STAGENET = "stagenet"; static public final String TESTNET = "testnet"; - - static class Address { - final private InetAddress inet; - final private String onion; - - public boolean isOnion() { - return onion != null; - } - - public String getHostName() { - if (inet != null) { - return inet.getHostName(); - } else { - return onion; - } - } - - public String getHostAddress() { - if (inet != null) { - return inet.getHostAddress(); - } else { - return onion; - } - } - - private Address(InetAddress address, String onion) { - this.inet = address; - this.onion = onion; - } - - static Address of(InetAddress address) { - return new Address(address, null); - } - - static Address of(String host) throws UnknownHostException { - if (OnionHelper.isOnionHost(host)) { - return new Address(null, host); - } else { - return new Address(InetAddress.getByName(host), null); - } - } - - @Override - public int hashCode() { - return getHostAddress().hashCode(); - } - - @Override - public boolean equals(Object other) { - return (other instanceof Address) && (getHostAddress().equals(((Address) other).getHostAddress())); - } - } - - @Getter - private String name = null; + static private int DEFAULT_LEVIN_PORT = 0; + static private int DEFAULT_RPC_PORT = 0; @Getter final private NetworkType networkType; - Address hostAddress; @Getter - private String host; + @Setter + private final boolean selected = false; + Address hostAddress; @Getter @Setter int rpcPort = 0; + @Getter + private String name = null; + @Getter + private String host; private int levinPort = 0; @Getter @Setter @@ -108,37 +60,6 @@ public class Node { @Getter @Setter private boolean favourite = false; - @Getter - @Setter - private final boolean selected = false; - - @Override - public int hashCode() { - return hostAddress.hashCode(); - } - - // Nodes are equal if they are the same host address:port & are on the same network - @Override - public boolean equals(Object other) { - if (!(other instanceof Node)) return false; - final Node anotherNode = (Node) other; - return (hostAddress.equals(anotherNode.hostAddress) - && (rpcPort == anotherNode.rpcPort) - && (networkType == anotherNode.networkType)); - } - - public boolean isOnion() { - return hostAddress.isOnion(); - } - - static public Node fromString(String nodeString) { - try { - return new Node(nodeString); - } catch (IllegalArgumentException ex) { - Timber.w(ex); - return null; - } - } Node(String nodeString) { if ((nodeString == null) || nodeString.isEmpty()) @@ -223,6 +144,92 @@ public class Node { this.levinPort = getDefaultLevinPort(); } + public Node() { + this.networkType = WalletManager.getInstance().getNetworkType(); + } + + // constructor used for created nodes from retrieved peer lists + public Node(InetSocketAddress socketAddress) { + this(); + this.hostAddress = Address.of(socketAddress.getAddress()); + this.host = socketAddress.getHostString(); + this.rpcPort = 0; // unknown + this.levinPort = socketAddress.getPort(); + this.username = ""; + this.password = ""; + } + + public Node(Node anotherNode) { + networkType = anotherNode.networkType; + overwriteWith(anotherNode); + } + + static public Node fromString(String nodeString) { + try { + return new Node(nodeString); + } catch (IllegalArgumentException ex) { + Timber.w(ex); + return null; + } + } + + // every node knows its network, but they are all the same + static public int getDefaultLevinPort() { + if (DEFAULT_LEVIN_PORT > 0) return DEFAULT_LEVIN_PORT; + switch (WalletManager.getInstance().getNetworkType()) { + case NetworkType_Mainnet: + DEFAULT_LEVIN_PORT = 18080; + break; + case NetworkType_Testnet: + DEFAULT_LEVIN_PORT = 28080; + break; + case NetworkType_Stagenet: + DEFAULT_LEVIN_PORT = 38080; + break; + default: + throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType()); + } + return DEFAULT_LEVIN_PORT; + } + + // every node knows its network, but they are all the same + static public int getDefaultRpcPort() { + if (DEFAULT_RPC_PORT > 0) return DEFAULT_RPC_PORT; + switch (WalletManager.getInstance().getNetworkType()) { + case NetworkType_Mainnet: + DEFAULT_RPC_PORT = 18081; + break; + case NetworkType_Testnet: + DEFAULT_RPC_PORT = 28081; + break; + case NetworkType_Stagenet: + DEFAULT_RPC_PORT = 38081; + break; + default: + throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType()); + } + return DEFAULT_RPC_PORT; + } + + @Override + public int hashCode() { + return hostAddress.hashCode(); + } + + // Nodes are equal if they are the same host address:port & are on the same network + @Override + public boolean equals(Object other) { + if (!(other instanceof Node)) return false; + final Node anotherNode = (Node) other; + return (hostAddress.equals(anotherNode.hostAddress) + && (rpcPort == anotherNode.rpcPort) + && (networkType == anotherNode.networkType)); + } + + public boolean isOnion() { + return hostAddress.isOnion(); + } + public String toNodeString() { return toString(); } @@ -255,21 +262,6 @@ public class Node { return sb.toString(); } - public Node() { - this.networkType = WalletManager.getInstance().getNetworkType(); - } - - // constructor used for created nodes from retrieved peer lists - public Node(InetSocketAddress socketAddress) { - this(); - this.hostAddress = Address.of(socketAddress.getAddress()); - this.host = socketAddress.getHostString(); - this.rpcPort = 0; // unknown - this.levinPort = socketAddress.getPort(); - this.username = ""; - this.password = ""; - } - public String getAddress() { return getHostAddress() + ":" + rpcPort; } @@ -309,11 +301,6 @@ public class Node { favourite = !favourite; } - public Node(Node anotherNode) { - networkType = anotherNode.networkType; - overwriteWith(anotherNode); - } - public void overwriteWith(Node anotherNode) { if (networkType != anotherNode.networkType) throw new IllegalStateException("network types do not match"); @@ -327,45 +314,55 @@ public class Node { favourite = anotherNode.favourite; } - static private int DEFAULT_LEVIN_PORT = 0; + static class Address { + final private InetAddress inet; + final private String onion; - // every node knows its network, but they are all the same - static public int getDefaultLevinPort() { - if (DEFAULT_LEVIN_PORT > 0) return DEFAULT_LEVIN_PORT; - switch (WalletManager.getInstance().getNetworkType()) { - case NetworkType_Mainnet: - DEFAULT_LEVIN_PORT = 18080; - break; - case NetworkType_Testnet: - DEFAULT_LEVIN_PORT = 28080; - break; - case NetworkType_Stagenet: - DEFAULT_LEVIN_PORT = 38080; - break; - default: - throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType()); + private Address(InetAddress address, String onion) { + this.inet = address; + this.onion = onion; } - return DEFAULT_LEVIN_PORT; - } - static private int DEFAULT_RPC_PORT = 0; - - // every node knows its network, but they are all the same - static public int getDefaultRpcPort() { - if (DEFAULT_RPC_PORT > 0) return DEFAULT_RPC_PORT; - switch (WalletManager.getInstance().getNetworkType()) { - case NetworkType_Mainnet: - DEFAULT_RPC_PORT = 18081; - break; - case NetworkType_Testnet: - DEFAULT_RPC_PORT = 28081; - break; - case NetworkType_Stagenet: - DEFAULT_RPC_PORT = 38081; - break; - default: - throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType()); + static Address of(InetAddress address) { + return new Address(address, null); + } + + static Address of(String host) throws UnknownHostException { + if (OnionHelper.isOnionHost(host)) { + return new Address(null, host); + } else { + return new Address(InetAddress.getByName(host), null); + } + } + + public boolean isOnion() { + return onion != null; + } + + public String getHostName() { + if (inet != null) { + return inet.getHostName(); + } else { + return onion; + } + } + + public String getHostAddress() { + if (inet != null) { + return inet.getHostAddress(); + } else { + return onion; + } + } + + @Override + public int hashCode() { + return getHostAddress().hashCode(); + } + + @Override + public boolean equals(Object other) { + return (other instanceof Address) && (getHostAddress().equals(((Address) other).getHostAddress())); } - return DEFAULT_RPC_PORT; } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java b/app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java deleted file mode 100644 index 0a305ec..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.data; - -import android.content.Context; -import android.text.Html; -import android.text.Spanned; -import android.widget.TextView; - -import com.m2049r.levin.scanner.LevinPeer; -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.util.NetCipherHelper; -import com.m2049r.xmrwallet.util.NetCipherHelper.Request; -import com.m2049r.xmrwallet.util.NodePinger; -import com.m2049r.xmrwallet.util.ThemeHelper; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.Calendar; -import java.util.Comparator; - -import lombok.Getter; -import lombok.Setter; -import okhttp3.HttpUrl; -import okhttp3.Response; -import okhttp3.ResponseBody; -import timber.log.Timber; - -public class NodeInfo extends Node { - final static public int MIN_MAJOR_VERSION = 14; - final static public String RPC_VERSION = "2.0"; - - @Getter - private long height = 0; - @Getter - private long timestamp = 0; - @Getter - private int majorVersion = 0; - @Getter - private double responseTime = Double.MAX_VALUE; - @Getter - private int responseCode = 0; - @Getter - private boolean tested = false; - @Getter - @Setter - private final boolean selecting = false; - - public void clear() { - height = 0; - majorVersion = 0; - responseTime = Double.MAX_VALUE; - responseCode = 0; - timestamp = 0; - tested = false; - } - - static public NodeInfo fromString(String nodeString) { - try { - return new NodeInfo(nodeString); - } catch (IllegalArgumentException ex) { - return null; - } - } - - public NodeInfo(NodeInfo anotherNode) { - super(anotherNode); - overwriteWith(anotherNode); - } - - private SocketAddress levinSocketAddress = null; - - synchronized public SocketAddress getLevinSocketAddress() { - if (levinSocketAddress == null) { - // use default peer port if not set - very few peers use nonstandard port - levinSocketAddress = new InetSocketAddress(hostAddress.getHostAddress(), getDefaultLevinPort()); - } - return levinSocketAddress; - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object other) { - return super.equals(other); - } - - public NodeInfo(String nodeString) { - super(nodeString); - } - - public NodeInfo(LevinPeer levinPeer) { - super(levinPeer.getSocketAddress()); - } - - public NodeInfo(InetSocketAddress address) { - super(address); - } - - public NodeInfo() { - super(); - } - - public boolean isSuccessful() { - return (responseCode >= 200) && (responseCode < 300); - } - - public boolean isUnauthorized() { - return responseCode == HttpURLConnection.HTTP_UNAUTHORIZED; - } - - public boolean isValid() { - return isSuccessful() && (majorVersion >= MIN_MAJOR_VERSION) && (responseTime < Double.MAX_VALUE); - } - - static public Comparator BestNodeComparator = (o1, o2) -> { - if (o1.isValid()) { - if (o2.isValid()) { // both are valid - // higher node wins - int heightDiff = (int) (o2.height - o1.height); - if (heightDiff != 0) - return heightDiff; - // if they are equal, faster node wins - return (int) Math.signum(o1.responseTime - o2.responseTime); - } else { - return -1; - } - } else { - return 1; - } - }; - - public void overwriteWith(NodeInfo anotherNode) { - super.overwriteWith(anotherNode); - height = anotherNode.height; - timestamp = anotherNode.timestamp; - majorVersion = anotherNode.majorVersion; - responseTime = anotherNode.responseTime; - responseCode = anotherNode.responseCode; - } - - public String toNodeString() { - return super.toString(); - } - - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(super.toString()); - sb.append("?rc=").append(responseCode); - sb.append("?v=").append(majorVersion); - sb.append("&h=").append(height); - sb.append("&ts=").append(timestamp); - if (responseTime < Double.MAX_VALUE) { - sb.append("&t=").append(responseTime).append("ms"); - } - return sb.toString(); - } - - private static final int HTTP_TIMEOUT = 1000; //ms - public static final double PING_GOOD = HTTP_TIMEOUT / 3.0; //ms - public static final double PING_MEDIUM = 2 * PING_GOOD; //ms - public static final double PING_BAD = HTTP_TIMEOUT; - - public boolean testRpcService() { - return testRpcService(rpcPort); - } - - public boolean testRpcService(NodePinger.Listener listener) { - boolean result = testRpcService(rpcPort); - if (listener != null) - listener.publish(this); - return result; - } - - private Request rpcServiceRequest(int port) { - final HttpUrl url = new HttpUrl.Builder() - .scheme("http") - .host(getHost()) - .port(port) - .addPathSegment("json_rpc") - .build(); - final String json = "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getlastblockheader\"}"; - return new Request(url, json, getUsername(), getPassword()); - } - - private boolean testRpcService(int port) { - Timber.d("Testing %s", toNodeString()); - clear(); - if (hostAddress.isOnion() && !NetCipherHelper.isTor()) { - tested = true; // sortof - responseCode = 418; // I'm a teapot - or I need an Onion - who knows - return false; // autofail - } - try { - long ta = System.nanoTime(); - try (Response response = rpcServiceRequest(port).execute()) { - Timber.d("%s: %s", response.code(), response.request().url()); - responseTime = (System.nanoTime() - ta) / 1000000.0; - responseCode = response.code(); - if (response.isSuccessful()) { - ResponseBody respBody = response.body(); // closed through Response object - if ((respBody != null) && (respBody.contentLength() < 2000)) { // sanity check - final JSONObject json = new JSONObject(respBody.string()); - String rpcVersion = json.getString("jsonrpc"); - if (!RPC_VERSION.equals(rpcVersion)) - return false; - final JSONObject result = json.getJSONObject("result"); - if (!result.has("credits")) // introduced in monero v0.15.0 - return false; - final JSONObject header = result.getJSONObject("block_header"); - height = header.getLong("height"); - timestamp = header.getLong("timestamp"); - majorVersion = header.getInt("major_version"); - return true; // success - } - } - } - } catch (IOException | JSONException ex) { - Timber.d("EX: %s", ex.getMessage()); //TODO: do something here (show error?) - } finally { - tested = true; - } - return false; - } - - static final private int[] TEST_PORTS = {18089}; // check only opt-in port - - public boolean findRpcService() { - // if already have an rpcPort, use that - if (rpcPort > 0) return testRpcService(rpcPort); - // otherwise try to find one - for (int port : TEST_PORTS) { - if (testRpcService(port)) { // found a service - this.rpcPort = port; - return true; - } - } - return false; - } - - static public final int STALE_NODE_HOURS = 2; - - public void showInfo(TextView view, String info, boolean isError) { - final Context ctx = view.getContext(); - final Spanned text = Html.fromHtml(ctx.getString(R.string.status, - Integer.toHexString(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor) & 0xFFFFFF), - Integer.toHexString(ThemeHelper.getThemedColor(ctx, android.R.attr.colorBackground) & 0xFFFFFF), - (hostAddress.isOnion() ? " .onion  " : ""), " " + info)); - view.setText(text); - if (isError) - view.setTextColor(ThemeHelper.getThemedColor(ctx, R.attr.colorError)); - else - view.setTextColor(ThemeHelper.getThemedColor(ctx, android.R.attr.textColorSecondary)); - } - - public void showInfo(TextView view) { - if (!isTested()) { - showInfo(view, "", false); - return; - } - final Context ctx = view.getContext(); - final long now = Calendar.getInstance().getTimeInMillis() / 1000; - final long secs = (now - timestamp); - final long mins = secs / 60; - final long hours = mins / 60; - final long days = hours / 24; - String info; - if (mins < 2) { - info = ctx.getString(R.string.node_updated_now, secs); - } else if (hours < 2) { - info = ctx.getString(R.string.node_updated_mins, mins); - } else if (days < 2) { - info = ctx.getString(R.string.node_updated_hours, hours); - } else { - info = ctx.getString(R.string.node_updated_days, days); - } - showInfo(view, info, hours >= STALE_NODE_HOURS); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/Subaddress.java b/app/src/main/java/com/m2049r/xmrwallet/data/Subaddress.java index 582ce87..1755507 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/data/Subaddress.java +++ b/app/src/main/java/com/m2049r/xmrwallet/data/Subaddress.java @@ -28,6 +28,7 @@ import lombok.ToString; @ToString @EqualsAndHashCode public class Subaddress implements Comparable { + public static final Pattern DEFAULT_LABEL_FORMATTER = Pattern.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}:[0-9]{2}:[0-9]{2}$"); @Getter final private int accountIndex; @Getter @@ -52,8 +53,6 @@ public class Subaddress implements Comparable { return address.substring(0, 8) + "…" + address.substring(address.length() - 8); } - public static final Pattern DEFAULT_LABEL_FORMATTER = Pattern.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}:[0-9]{2}:[0-9]{2}$"); - public String getDisplayLabel() { if (label.isEmpty() || (DEFAULT_LABEL_FORMATTER.matcher(label).matches())) return ("#" + addressIndex); diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/TxData.java b/app/src/main/java/com/m2049r/xmrwallet/data/TxData.java index 144948c..0cc0a63 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/data/TxData.java +++ b/app/src/main/java/com/m2049r/xmrwallet/data/TxData.java @@ -26,6 +26,22 @@ import com.m2049r.xmrwallet.util.Helper; // https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents public class TxData implements Parcelable { + // this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public TxData createFromParcel(Parcel in) { + return new TxData(in); + } + + public TxData[] newArray(int size) { + return new TxData[size]; + } + }; + private String dstAddr; + private long amount; + private int mixin; + private PendingTransaction.Priority priority; + private UserNotes userNotes; + public TxData() { } @@ -46,30 +62,26 @@ public class TxData implements Parcelable { this.priority = priority; } + protected TxData(Parcel in) { + dstAddr = in.readString(); + amount = in.readLong(); + mixin = in.readInt(); + priority = PendingTransaction.Priority.fromInteger(in.readInt()); + + } + public String getDestinationAddress() { return dstAddr; } - public long getAmount() { - return amount; - } - - public double getAmountAsDouble() { - return 1.0 * amount / Helper.ONE_XMR; - } - - public int getMixin() { - return mixin; - } - - public PendingTransaction.Priority getPriority() { - return priority; - } - public void setDestinationAddress(String dstAddr) { this.dstAddr = dstAddr; } + public long getAmount() { + return amount; + } + public void setAmount(long amount) { this.amount = amount; } @@ -78,10 +90,22 @@ public class TxData implements Parcelable { this.amount = Wallet.getAmountFromDouble(amount); } + public double getAmountAsDouble() { + return 1.0 * amount / Helper.ONE_XMR; + } + + public int getMixin() { + return mixin; + } + public void setMixin(int mixin) { this.mixin = mixin; } + public PendingTransaction.Priority getPriority() { + return priority; + } + public void setPriority(PendingTransaction.Priority priority) { this.priority = priority; } @@ -94,13 +118,6 @@ public class TxData implements Parcelable { this.userNotes = userNotes; } - private String dstAddr; - private long amount; - private int mixin; - private PendingTransaction.Priority priority; - - private UserNotes userNotes; - @Override public void writeToParcel(Parcel out, int flags) { out.writeString(dstAddr); @@ -109,25 +126,6 @@ public class TxData implements Parcelable { out.writeInt(priority.getValue()); } - // this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public TxData createFromParcel(Parcel in) { - return new TxData(in); - } - - public TxData[] newArray(int size) { - return new TxData[size]; - } - }; - - protected TxData(Parcel in) { - dstAddr = in.readString(); - amount = in.readLong(); - mixin = in.readInt(); - priority = PendingTransaction.Priority.fromInteger(in.readInt()); - - } - @Override public int describeContents() { return 0; diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/TxDataBtc.java b/app/src/main/java/com/m2049r/xmrwallet/data/TxDataBtc.java deleted file mode 100644 index 5ea1388..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/data/TxDataBtc.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2017 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.data; - -import android.os.Parcel; - -import androidx.annotation.NonNull; - -import lombok.Getter; -import lombok.Setter; - -public class TxDataBtc extends TxData { - @Getter - @Setter - private String btcSymbol; // the actual non-XMR thing we're sending - @Getter - @Setter - private String xmrtoOrderId; // shown in success screen - @Getter - @Setter - private String btcAddress; - @Getter - @Setter - private double btcAmount; - - public TxDataBtc() { - super(); - } - - public TxDataBtc(TxDataBtc txDataBtc) { - super(txDataBtc); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeString(btcSymbol); - out.writeString(xmrtoOrderId); - out.writeString(btcAddress); - out.writeDouble(btcAmount); - } - - // this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods - public static final Creator CREATOR = new Creator() { - public TxDataBtc createFromParcel(Parcel in) { - return new TxDataBtc(in); - } - - public TxDataBtc[] newArray(int size) { - return new TxDataBtc[size]; - } - }; - - protected TxDataBtc(Parcel in) { - super(in); - btcSymbol = in.readString(); - xmrtoOrderId = in.readString(); - btcAddress = in.readString(); - btcAmount = in.readDouble(); - } - - @NonNull - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("xmrtoOrderId:"); - sb.append(xmrtoOrderId); - sb.append(",btcSymbol:"); - sb.append(btcSymbol); - sb.append(",btcAddress:"); - sb.append(btcAddress); - sb.append(",btcAmount:"); - sb.append(btcAmount); - return sb.toString(); - } - - public boolean validateAddress(@NonNull String address) { - if ((btcSymbol == null) || (btcAddress == null)) return false; - final Crypto crypto = Crypto.withSymbol(btcSymbol); - if (crypto == null) return false; - if (crypto.isCasefull()) { // compare as-is - return address.equals(btcAddress); - } else { // normalize & compare (e.g. ETH with and without checksum capitals - return address.equalsIgnoreCase(btcAddress); - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/InformationBottomSheetDialog.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/InformationBottomSheetDialog.java index 66db6ec..f557a95 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/InformationBottomSheetDialog.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/InformationBottomSheetDialog.java @@ -4,8 +4,6 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; -import android.widget.EditText; import android.widget.ImageButton; import android.widget.TextView; @@ -14,13 +12,8 @@ import androidx.annotation.Nullable; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.util.Constants; import com.m2049r.xmrwallet.util.Helper; -import java.io.File; - public class InformationBottomSheetDialog extends BottomSheetDialogFragment { public boolean showCopyButton = false; public String information = ""; @@ -34,7 +27,7 @@ public class InformationBottomSheetDialog extends BottomSheetDialogFragment { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); ImageButton copyInformationImageButton = view.findViewById(R.id.copy_information_imagebutton); - if(showCopyButton) { + if (showCopyButton) { copyInformationImageButton.setVisibility(View.VISIBLE); } else { copyInformationImageButton.setVisibility(View.INVISIBLE); diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/PasswordBottomSheetDialog.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/PasswordBottomSheetDialog.java index bff9d22..efb8e85 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/PasswordBottomSheetDialog.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/PasswordBottomSheetDialog.java @@ -7,20 +7,13 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; -import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.service.BalanceService; -import com.m2049r.xmrwallet.service.TxService; import com.m2049r.xmrwallet.util.Constants; import com.m2049r.xmrwallet.util.Helper; @@ -51,7 +44,7 @@ public class PasswordBottomSheetDialog extends BottomSheetDialogFragment { unlockWalletButton.setOnClickListener(view1 -> { String password = passwordEditText.getText().toString(); boolean success = checkPassword(walletFile, password); - if(success) { + if (success) { listener.onPasswordSuccess(password); dismiss(); } else { @@ -66,6 +59,7 @@ public class PasswordBottomSheetDialog extends BottomSheetDialogFragment { public interface PasswordListener { void onPasswordSuccess(String password); + void onPasswordFail(); } } \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/SendBottomSheetDialog.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/SendBottomSheetDialog.java index a2bc805..454f95d 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/SendBottomSheetDialog.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/SendBottomSheetDialog.java @@ -2,20 +2,8 @@ package com.m2049r.xmrwallet.fragment.dialog; import android.app.Activity; import android.content.Context; -import android.content.pm.PackageManager; import android.os.AsyncTask; import android.os.Bundle; -import com.google.android.material.bottomsheet.BottomSheetDialogFragment; -import com.google.zxing.client.android.Intents; -import com.journeyapps.barcodescanner.ScanContract; -import com.journeyapps.barcodescanner.ScanOptions; -import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.model.PendingTransaction; -import com.m2049r.xmrwallet.model.Wallet; -import com.m2049r.xmrwallet.service.BalanceService; -import com.m2049r.xmrwallet.service.TxService; -import com.m2049r.xmrwallet.util.Helper; - import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -32,31 +20,38 @@ import androidx.annotation.Nullable; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.zxing.client.android.Intents; +import com.journeyapps.barcodescanner.ScanContract; +import com.journeyapps.barcodescanner.ScanOptions; +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.model.PendingTransaction; +import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.service.BalanceService; +import com.m2049r.xmrwallet.service.TxService; +import com.m2049r.xmrwallet.util.Helper; + import java.util.List; public class SendBottomSheetDialog extends BottomSheetDialogFragment { - private final ActivityResultLauncher barcodeLauncher = registerForActivityResult(new ScanContract(), - result -> { - if(result.getContents() != null) { - pasteAddress(result.getContents()); - } - }); - - private final ActivityResultLauncher cameraPermissionsLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), + private final MutableLiveData _sendingMax = new MutableLiveData<>(false); + public LiveData sendingMax = _sendingMax; private final ActivityResultLauncher cameraPermissionsLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { - if(granted) { + if (granted) { onScan(); } else { Toast.makeText(getActivity(), getString(R.string.no_camera_permission), Toast.LENGTH_SHORT).show(); } }); - - private MutableLiveData _sendingMax = new MutableLiveData<>(false); - public LiveData sendingMax = _sendingMax; - private MutableLiveData _pendingTransaction = new MutableLiveData<>(null); + private final MutableLiveData _pendingTransaction = new MutableLiveData<>(null); public LiveData pendingTransaction = _pendingTransaction; - private EditText addressEditText; + private final ActivityResultLauncher barcodeLauncher = registerForActivityResult(new ScanContract(), + result -> { + if (result.getContents() != null) { + pasteAddress(result.getContents()); + } + }); private EditText amountEditText; private TextView sendAllTextView; private TextView feeTextView; @@ -90,9 +85,9 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { pasteAddressImageButton.setOnClickListener(view1 -> { Context ctx = getContext(); - if(ctx != null) { + if (ctx != null) { String clipboard = Helper.getClipBoardText(ctx); - if(clipboard != null) { + if (clipboard != null) { pasteAddress(clipboard); } } @@ -115,7 +110,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { if (validAddress && (!amount.isEmpty() || sendAll)) { long amountRaw = Wallet.getAmountFromString(amount); long balance = BalanceService.getInstance().getUnlockedBalanceRaw(); - if((amountRaw >= balance || amountRaw <= 0) && !sendAll) { + if ((amountRaw >= balance || amountRaw <= 0) && !sendAll) { Toast.makeText(getActivity(), getString(R.string.send_amount_invalid), Toast.LENGTH_SHORT).show(); return; } @@ -131,7 +126,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { sendButton.setOnClickListener(view1 -> { PendingTransaction pendingTx = pendingTransaction.getValue(); - if(pendingTx != null) { + if (pendingTx != null) { Toast.makeText(getActivity(), getString(R.string.sending_tx), Toast.LENGTH_SHORT).show(); sendButton.setEnabled(false); sendTx(pendingTx); @@ -139,7 +134,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { }); sendingMax.observe(getViewLifecycleOwner(), sendingMax -> { - if(pendingTransaction.getValue() == null) { + if (pendingTransaction.getValue() == null) { if (sendingMax) { amountEditText.setVisibility(View.INVISIBLE); sendAllTextView.setVisibility(View.VISIBLE); @@ -155,7 +150,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { pendingTransaction.observe(getViewLifecycleOwner(), pendingTx -> { showConfirmationLayout(pendingTx != null); - if(pendingTx != null) { + if (pendingTx != null) { String address = addressEditText.getText().toString(); addressTextView.setText(getString(R.string.tx_address_text, address)); amountTextView.setText(getString(R.string.tx_amount_text, Helper.getDisplayAmount(pendingTx.getAmount()))); @@ -179,7 +174,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { AsyncTask.execute(() -> { boolean success = TxService.getInstance().sendTx(pendingTx); Activity activity = getActivity(); - if(activity != null) { + if (activity != null) { activity.runOnUiThread(() -> { if (success) { Toast.makeText(getActivity(), getString(R.string.sent_tx), Toast.LENGTH_SHORT).show(); @@ -196,11 +191,11 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { private void createTx(String address, String amount, boolean sendAll, PendingTransaction.Priority feePriority) { AsyncTask.execute(() -> { PendingTransaction pendingTx = TxService.getInstance().createTx(address, amount, sendAll, feePriority); - if(pendingTx != null) { + if (pendingTx != null) { _pendingTransaction.postValue(pendingTx); } else { Activity activity = getActivity(); - if(activity != null) { + if (activity != null) { activity.runOnUiThread(() -> { createButton.setEnabled(true); Toast.makeText(getActivity(), getString(R.string.error_creating_tx), Toast.LENGTH_SHORT).show(); @@ -211,7 +206,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { } private void showConfirmationLayout(boolean show) { - if(show) { + if (show) { sendButton.setVisibility(View.VISIBLE); addressEditText.setVisibility(View.GONE); amountEditText.setVisibility(View.GONE); @@ -241,10 +236,12 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { private void pasteAddress(String address) { String modifiedAddress = address.replace("monero:", "").split("\\?")[0]; boolean isValid = Wallet.isAddressValid(modifiedAddress); - if(isValid) { + if (isValid) { addressEditText.setText(modifiedAddress); } else { Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show(); } } + + } \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeFragment.java index 4e4f06a..08f5a4a 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeFragment.java @@ -5,25 +5,20 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; -import android.widget.EditText; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; -import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.bottomsheet.BottomSheetDialog; import com.m2049r.xmrwallet.MainActivity; import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.adapter.TransactionInfoAdapter; @@ -32,20 +27,16 @@ import com.m2049r.xmrwallet.fragment.dialog.SendBottomSheetDialog; import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.service.AddressService; import com.m2049r.xmrwallet.service.BalanceService; import com.m2049r.xmrwallet.service.BlockchainService; import com.m2049r.xmrwallet.service.HistoryService; -import com.m2049r.xmrwallet.service.PrefService; -import com.m2049r.xmrwallet.service.TxService; -import com.m2049r.xmrwallet.util.Constants; import java.util.Collections; public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxInfoAdapterListener { - private HomeViewModel mViewModel; long startHeight = 0; + private HomeViewModel mViewModel; @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @@ -56,7 +47,7 @@ public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxI @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - MainActivity mainActivity = (MainActivity)getActivity(); + MainActivity mainActivity = (MainActivity) getActivity(); mViewModel = new ViewModelProvider(this).get(HomeViewModel.class); bindObservers(view); bindListeners(view); @@ -96,7 +87,7 @@ public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxI HistoryService historyService = HistoryService.getInstance(); BlockchainService blockchainService = BlockchainService.getInstance(); - if(balanceService != null) { + if (balanceService != null) { balanceService.balance.observe(getViewLifecycleOwner(), balance -> { unlockedBalanceTextView.setText(getString(R.string.wallet_balance_text, Wallet.getDisplayAmount(balance))); }); @@ -112,7 +103,7 @@ public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxI } ProgressBar progressBar = view.findViewById(R.id.sync_progress_bar); - if(blockchainService != null) { + if (blockchainService != null) { blockchainService.height.observe(getViewLifecycleOwner(), height -> { Wallet wallet = WalletManager.getInstance().getWallet(); if (!wallet.isSynchronized()) { @@ -135,13 +126,13 @@ public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxI TransactionInfoAdapter adapter = new TransactionInfoAdapter(this); txHistoryRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); txHistoryRecyclerView.setAdapter(adapter); - if(historyService != null) { + if (historyService != null) { historyService.history.observe(getViewLifecycleOwner(), history -> { if (history.isEmpty()) { txHistoryRecyclerView.setVisibility(View.GONE); } else { Collections.sort(history); - if(history.size() > 100) { + if (history.size() > 100) { adapter.submitList(history.subList(0, 99)); } else { adapter.submitList(history); diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeViewModel.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeViewModel.java index 4af425c..62b502a 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeViewModel.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeViewModel.java @@ -1,21 +1,7 @@ package com.m2049r.xmrwallet.fragment.home; -import android.graphics.Bitmap; - import androidx.lifecycle.ViewModel; -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.WriterException; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.qrcode.QRCodeWriter; -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; - -import java.util.HashMap; -import java.util.Map; - -import timber.log.Timber; - public class HomeViewModel extends ViewModel { } \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingFragment.java index 98f8f82..e58fcc9 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingFragment.java @@ -17,7 +17,6 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; -import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; @@ -55,7 +54,7 @@ public class OnboardingFragment extends Fragment { createWalletButton.setOnClickListener(view1 -> { String walletPassword = walletPasswordEditText.getText().toString(); - if(!walletPassword.isEmpty()) { + if (!walletPassword.isEmpty()) { PrefService.getInstance().edit().putBoolean(Constants.PREF_USES_PASSWORD, true).apply(); } String walletSeed = walletSeedEditText.getText().toString().trim(); @@ -63,14 +62,14 @@ public class OnboardingFragment extends Fragment { long restoreHeight = -1; File walletFile = new File(getActivity().getApplicationInfo().dataDir, Constants.WALLET_NAME); Wallet wallet = null; - if(walletSeed.isEmpty()) { + if (walletSeed.isEmpty()) { wallet = WalletManager.getInstance().createWallet(walletFile, walletPassword, Constants.MNEMONIC_LANGUAGE, restoreHeight); } else { - if(!checkMnemonic(walletSeed)) { + if (!checkMnemonic(walletSeed)) { Toast.makeText(getContext(), getString(R.string.invalid_mnemonic_code), Toast.LENGTH_SHORT).show(); return; } - if(!restoreHeightText.isEmpty()) { + if (!restoreHeightText.isEmpty()) { restoreHeight = Long.parseLong(restoreHeightText); } wallet = WalletManager.getInstance().recoveryWallet(walletFile, walletPassword, walletSeed, "", restoreHeight); @@ -78,21 +77,24 @@ public class OnboardingFragment extends Fragment { boolean ok = wallet.getStatus().isOk(); walletFile.delete(); // cache is broken for some reason when recovering wallets. delete the file here. this happens in monerujo too. - if(ok) { - ((MainActivity)getActivity()).init(walletFile, walletPassword); + if (ok) { + ((MainActivity) getActivity()).init(walletFile, walletPassword); getActivity().onBackPressed(); } }); walletSeedEditText.addTextChangedListener(new TextWatcher() { @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } + @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + } @Override public void afterTextChanged(Editable editable) { String text = editable.toString(); - if(text.isEmpty()) { + if (text.isEmpty()) { createWalletButton.setText(R.string.create_wallet); } else { createWalletButton.setText(R.string.menu_restore); @@ -101,7 +103,7 @@ public class OnboardingFragment extends Fragment { }); mViewModel.showMoreOptions.observe(getViewLifecycleOwner(), show -> { - if(show) { + if (show) { moreOptionsChevronImageView.setImageResource(R.drawable.ic_keyboard_arrow_up); walletSeedEditText.setVisibility(View.VISIBLE); walletRestoreHeightEditText.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingViewModel.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingViewModel.java index cd43b3b..fc2a4ed 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingViewModel.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingViewModel.java @@ -5,7 +5,7 @@ import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; public class OnboardingViewModel extends ViewModel { - private MutableLiveData _showMoreOptions = new MutableLiveData<>(false); + private final MutableLiveData _showMoreOptions = new MutableLiveData<>(false); public LiveData showMoreOptions = _showMoreOptions; public void onMoreOptionsClicked() { diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsFragment.java index ec3c704..1be59df 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/settings/SettingsFragment.java @@ -5,8 +5,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; -import android.widget.CompoundButton; -import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -18,9 +16,7 @@ import androidx.lifecycle.ViewModelProvider; import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.fragment.dialog.InformationBottomSheetDialog; import com.m2049r.xmrwallet.fragment.dialog.PasswordBottomSheetDialog; -import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.service.BlockchainService; import com.m2049r.xmrwallet.service.PrefService; import com.m2049r.xmrwallet.util.Constants; import com.m2049r.xmrwallet.util.DayNightMode; @@ -46,7 +42,7 @@ public class SettingsFragment extends Fragment implements PasswordBottomSheetDia nightModeSwitch.setChecked(NightmodeHelper.getPreferredNightmode() == DayNightMode.NIGHT); nightModeSwitch.setOnCheckedChangeListener((compoundButton, b) -> { - if(b) { + if (b) { NightmodeHelper.setAndSavePreferredNightmode(DayNightMode.NIGHT); } else { NightmodeHelper.setAndSavePreferredNightmode(DayNightMode.DAY); @@ -65,7 +61,7 @@ public class SettingsFragment extends Fragment implements PasswordBottomSheetDia displaySeedButton.setOnClickListener(view1 -> { boolean usesPassword = PrefService.getInstance().getBoolean(Constants.PREF_USES_PASSWORD, false); - if(usesPassword) { + if (usesPassword) { PasswordBottomSheetDialog passwordDialog = new PasswordBottomSheetDialog(); passwordDialog.listener = this; passwordDialog.show(getActivity().getSupportFragmentManager(), "password_dialog"); diff --git a/app/src/main/java/com/m2049r/xmrwallet/ledger/Instruction.java b/app/src/main/java/com/m2049r/xmrwallet/ledger/Instruction.java deleted file mode 100644 index b96d33d..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/ledger/Instruction.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.ledger; - -public enum Instruction { - - INS_NONE(0x00), - INS_RESET(0x02), - INS_GET_KEY(0x20), - INS_DISPLAY_ADDRESS(0x21), - INS_PUT_KEY(0x22), - INS_GET_CHACHA8_PREKEY(0x24), - INS_VERIFY_KEY(0x26), - INS_MANAGE_SEEDWORDS(0x28), - - INS_SECRET_KEY_TO_PUBLIC_KEY(0x30), - INS_GEN_KEY_DERIVATION(0x32), - INS_DERIVATION_TO_SCALAR(0x34), - INS_DERIVE_PUBLIC_KEY(0x36), - INS_DERIVE_SECRET_KEY(0x38), - INS_GEN_KEY_IMAGE(0x3A), - - INS_SECRET_KEY_ADD(0x3C), - INS_SECRET_KEY_SUB(0x3E), - INS_GENERATE_KEYPAIR(0x40), - INS_SECRET_SCAL_MUL_KEY(0x42), - INS_SECRET_SCAL_MUL_BASE(0x44), - - INS_DERIVE_SUBADDRESS_PUBLIC_KEY(0x46), - INS_GET_SUBADDRESS(0x48), - INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY(0x4A), - INS_GET_SUBADDRESS_SECRET_KEY(0x4C), - - INS_OPEN_TX(0x70), - INS_SET_SIGNATURE_MODE(0x72), - INS_GET_ADDITIONAL_KEY(0x74), - INS_STEALTH(0x76), - INS_GEN_COMMITMENT_MASK(0x77), - INS_BLIND(0x78), - INS_UNBLIND(0x7A), - INS_GEN_TXOUT_KEYS(0x7B), - INS_VALIDATE(0x7C), - INS_PREFIX_HASH(0x7D), - INS_MLSAG(0x7E), - INS_CLOSE_TX(0x80), - - INS_GET_TX_PROOF(0xA0), - - INS_GET_RESPONSE(0xC0), - - INS_UNDEFINED(0xFF); - - public static Instruction fromByte(byte n) { - switch (n & 0xFF) { - case 0x00: - return INS_NONE; - case 0x02: - return INS_RESET; - - case 0x20: - return INS_GET_KEY; - case 0x22: - return INS_PUT_KEY; - case 0x24: - return INS_GET_CHACHA8_PREKEY; - case 0x26: - return INS_VERIFY_KEY; - - case 0x30: - return INS_SECRET_KEY_TO_PUBLIC_KEY; - case 0x32: - return INS_GEN_KEY_DERIVATION; - case 0x34: - return INS_DERIVATION_TO_SCALAR; - case 0x36: - return INS_DERIVE_PUBLIC_KEY; - case 0x38: - return INS_DERIVE_SECRET_KEY; - case 0x3A: - return INS_GEN_KEY_IMAGE; - case 0x3C: - return INS_SECRET_KEY_ADD; - case 0x3E: - return INS_SECRET_KEY_SUB; - case 0x40: - return INS_GENERATE_KEYPAIR; - case 0x42: - return INS_SECRET_SCAL_MUL_KEY; - case 0x44: - return INS_SECRET_SCAL_MUL_BASE; - - case 0x46: - return INS_DERIVE_SUBADDRESS_PUBLIC_KEY; - case 0x48: - return INS_GET_SUBADDRESS; - case 0x4A: - return INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY; - case 0x4C: - return INS_GET_SUBADDRESS_SECRET_KEY; - - case 0x70: - return INS_OPEN_TX; - case 0x72: - return INS_SET_SIGNATURE_MODE; - case 0x74: - return INS_GET_ADDITIONAL_KEY; - case 0x76: - return INS_STEALTH; - case 0x78: - return INS_BLIND; - case 0x7A: - return INS_UNBLIND; - case 0x7C: - return INS_VALIDATE; - case 0x7E: - return INS_MLSAG; - case 0x80: - return INS_CLOSE_TX; - - case 0xc0: - return INS_GET_RESPONSE; - - default: - return INS_UNDEFINED; - } - } - - public int getValue() { - return value; - } - - public byte getByteValue() { - return (byte) (value & 0xFF); - } - - private final int value; - - Instruction(int value) { - this.value = value; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/ledger/Ledger.java b/app/src/main/java/com/m2049r/xmrwallet/ledger/Ledger.java deleted file mode 100644 index 0932af3..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/ledger/Ledger.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - ******************************************************************************* - * BTChip Bitcoin Hardware Wallet Java API - * (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************** - */ - -package com.m2049r.xmrwallet.ledger; - -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; - -import com.btchip.BTChipException; -import com.btchip.comm.BTChipTransport; -import com.btchip.comm.android.BTChipTransportAndroidHID; -import com.m2049r.xmrwallet.BuildConfig; -import com.m2049r.xmrwallet.model.WalletManager; -import com.m2049r.xmrwallet.util.Helper; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import timber.log.Timber; - -public class Ledger { - static final public boolean ENABLED = true; - // 5:20 is same as wallet2.cpp::restore() - static public final int LOOKAHEAD_ACCOUNTS = 5; - static public final int LOOKAHEAD_SUBADDRESSES = 20; - static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES; - - private static final byte PROTOCOL_VERSION = 0x03; - public static final int SW_OK = 0x9000; - public static final int SW_INS_NOT_SUPPORTED = 0x6D00; - public static final int[] OK = {SW_OK}; - public static final int MINIMUM_LEDGER_VERSION = (1 << 16) + (8 << 8) + (0); // 1.6.0 - - public static UsbDevice findDevice(UsbManager usbManager) { - if (!ENABLED) return null; - return BTChipTransportAndroidHID.getDevice(usbManager); - } - - static private Ledger Instance = null; - - static public String connect(UsbManager usbManager, UsbDevice usbDevice) throws IOException { - if (Instance != null) { - disconnect(); - } - Instance = new Ledger(usbManager, usbDevice); - return Name(); - } - - static public void disconnect() { - // this is not synchronized so as to close immediately - if (Instance != null) { - Instance.close(); - Instance = null; - } - } - - static public boolean isConnected() { - //TODO synchronize with connect/disconnect? - return Instance != null; - } - - static public String Name() { - if (Instance != null) { - return Instance.name; - } else { - return null; - } - } - - static public byte[] Exchange(byte[] apdu) { - if (Instance != null) { - Timber.d("INS: %s", Instruction.fromByte(apdu[1])); - return Instance.exchangeRaw(apdu); - } else { - return null; - } - } - - static public boolean check() { - if (Instance == null) return false; - byte[] moneroVersion = WalletManager.moneroVersion().getBytes(StandardCharsets.US_ASCII); - - try { - byte[] resp = Instance.exchangeApduNoOpt(Instruction.INS_RESET, moneroVersion, OK); - int deviceVersion = (resp[0] << 16) + (resp[1] << 8) + (resp[2]); - if (deviceVersion < MINIMUM_LEDGER_VERSION) - return false; - } catch (BTChipException ex) { // comm error - probably wrong app started on device - return false; - } - return true; - } - - final private BTChipTransport transport; - final private String name; - private int lastSW = 0; - - private Ledger(UsbManager usbManager, UsbDevice usbDevice) throws IOException { - final BTChipTransport transport = BTChipTransportAndroidHID.open(usbManager, usbDevice); - Timber.d("transport opened = %s", transport.toString()); - transport.setDebug(BuildConfig.DEBUG); - this.transport = transport; - this.name = usbDevice.getManufacturerName() + " " + usbDevice.getProductName(); - initKey(); - } - - synchronized private void close() { - initKey(); // don't leak key after we disconnect - transport.close(); - Timber.d("transport closed"); - lastSW = 0; - } - - synchronized private byte[] exchangeRaw(byte[] apdu) { - if (transport == null) - throw new IllegalStateException("No transport (probably closed previously)"); - Timber.d("exchangeRaw %02x", apdu[1]); - Instruction ins = Instruction.fromByte(apdu[1]); - if (listener != null) listener.onInstructionSend(ins, apdu); - sniffOut(ins, apdu); - byte[] data = transport.exchange(apdu); - if (listener != null) listener.onInstructionReceive(ins, data); - sniffIn(data); - return data; - } - - private byte[] exchange(byte[] apdu) throws BTChipException { - byte[] response = exchangeRaw(apdu); - if (response.length < 2) { - throw new BTChipException("Truncated response"); - } - lastSW = ((response[response.length - 2] & 0xff) << 8) | - response[response.length - 1] & 0xff; - byte[] result = new byte[response.length - 2]; - System.arraycopy(response, 0, result, 0, response.length - 2); - return result; - } - - private byte[] exchangeCheck(byte[] apdu, int[] acceptedSW) throws BTChipException { - byte[] response = exchange(apdu); - if (acceptedSW == null) { - return response; - } - for (int SW : acceptedSW) { - if (lastSW == SW) { - return response; - } - } - throw new BTChipException("Invalid status", lastSW); - } - - private byte[] exchangeApduNoOpt(Instruction instruction, byte[] data, int[] acceptedSW) - throws BTChipException { - byte[] apdu = new byte[data.length + 6]; - apdu[0] = PROTOCOL_VERSION; - apdu[1] = instruction.getByteValue(); - apdu[2] = 0; // p1 - apdu[3] = 0; // p2 - apdu[4] = (byte) (data.length + 1); // +1 because the opt byte is part of the data - apdu[5] = 0; // opt - System.arraycopy(data, 0, apdu, 6, data.length); - return exchangeCheck(apdu, acceptedSW); - } - - public interface Listener { - void onInstructionSend(Instruction ins, byte[] apdu); - - void onInstructionReceive(Instruction ins, byte[] data); - } - - Listener listener; - - static public void setListener(Listener listener) { - if (Instance != null) { - Instance.listener = listener; - } - } - - static public void unsetListener(Listener listener) { - if ((Instance != null) && (Instance.listener == listener)) - Instance.listener = null; - } - - // very stupid hack to extract the view key - // without messing around with monero core code - // NB: as all the ledger comm can be sniffed off the USB cable - there is no security issue here - private boolean snoopKey = false; - private byte[] key; - - private void initKey() { - key = Helper.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000"); - } - - static public String Key() { - if (Instance != null) { - return Helper.bytesToHex(Instance.key).toLowerCase(); - } else { - return null; - } - } - - private void sniffOut(Instruction ins, byte[] apdu) { - if (ins == Instruction.INS_GET_KEY) { - snoopKey = (apdu[2] == 2); - } - } - - private void sniffIn(byte[] data) { - // stupid hack to extract the view key - // without messing around with monero core code - if (snoopKey) { - if (data.length == 34) { // 32 key + result code 9000 - long sw = ((data[data.length - 2] & 0xff) << 8) | - (data[data.length - 1] & 0xff); - Timber.e("WS %d", sw); - if (sw == SW_OK) { - System.arraycopy(data, 0, key, 0, 32); - } - } - snoopKey = false; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/NetworkType.java b/app/src/main/java/com/m2049r/xmrwallet/model/NetworkType.java index 4cac90d..57e5382 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/NetworkType.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/NetworkType.java @@ -21,6 +21,12 @@ public enum NetworkType { NetworkType_Testnet(1), NetworkType_Stagenet(2); + private final int value; + + NetworkType(int value) { + this.value = value; + } + public static NetworkType fromInteger(int n) { switch (n) { case 0: @@ -36,10 +42,4 @@ public enum NetworkType { public int getValue() { return value; } - - private final int value; - - NetworkType(int value) { - this.value = value; - } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/PendingTransaction.java b/app/src/main/java/com/m2049r/xmrwallet/model/PendingTransaction.java index 5353c5b..174e3e2 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/PendingTransaction.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/PendingTransaction.java @@ -27,46 +27,6 @@ public class PendingTransaction { this.handle = handle; } - public enum Status { - Status_Ok, - Status_Error, - Status_Critical - } - - public enum Priority { - Priority_Default(0), - Priority_Low(1), - Priority_Medium(2), - Priority_High(3), - Priority_Last(4); - - public static Priority fromInteger(int n) { - switch (n) { - case 0: - return Priority_Default; - case 1: - return Priority_Low; - case 2: - return Priority_Medium; - case 3: - return Priority_High; - } - return null; - } - - public int getValue() { - return value; - } - - private final int value; - - Priority(int value) { - this.value = value; - } - - - } - public Status getStatus() { return Status.values()[getStatusJ()]; } @@ -95,4 +55,44 @@ public class PendingTransaction { public native long getTxCount(); + public enum Status { + Status_Ok, + Status_Error, + Status_Critical + } + + public enum Priority { + Priority_Default(0), + Priority_Low(1), + Priority_Medium(2), + Priority_High(3), + Priority_Last(4); + + private final int value; + + Priority(int value) { + this.value = value; + } + + public static Priority fromInteger(int n) { + switch (n) { + case 0: + return Priority_Default; + case 1: + return Priority_Low; + case 2: + return Priority_Medium; + case 3: + return Priority_High; + } + return null; + } + + public int getValue() { + return value; + } + + + } + } diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java index ffb4f12..28b2c0a 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java @@ -30,6 +30,12 @@ public class TransactionHistory { private final long handle; int accountIndex; + private List transactions = new ArrayList<>(); + + public TransactionHistory(long handle, int accountIndex) { + this.handle = handle; + this.accountIndex = accountIndex; + } public void setAccountFor(Wallet wallet) { if (accountIndex != wallet.getAccountIndex()) { @@ -38,29 +44,22 @@ public class TransactionHistory { } } - public TransactionHistory(long handle, int accountIndex) { - this.handle = handle; - this.accountIndex = accountIndex; - } - private void loadNotes(Wallet wallet) { for (TransactionInfo info : transactions) { info.notes = wallet.getUserNote(info.hash); } } - public native int getCount(); // over all accounts/subaddresses - //private native long getTransactionByIndexJ(int i); //private native long getTransactionByIdJ(String id); + public native int getCount(); // over all accounts/subaddresses + public List getAll() { return transactions; } - private List transactions = new ArrayList<>(); - void refreshWithNotes(Wallet wallet) { refresh(); loadNotes(wallet); diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java index 4b904e1..6ae49a9 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java @@ -30,26 +30,15 @@ import lombok.RequiredArgsConstructor; // this is a POJO for the TransactionInfoAdapter public class TransactionInfo implements Parcelable, Comparable { public static final int CONFIRMATION = 10; // blocks - - @RequiredArgsConstructor - public enum Direction { - Direction_In(0), - Direction_Out(1); - - public static Direction fromInteger(int n) { - switch (n) { - case 0: - return Direction_In; - case 1: - return Direction_Out; - } - return null; + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public TransactionInfo createFromParcel(Parcel in) { + return new TransactionInfo(in); } - @Getter - private final int value; - } - + public TransactionInfo[] newArray(int size) { + return new TransactionInfo[size]; + } + }; public Direction direction; public boolean isPending; public boolean isFailed; @@ -100,6 +89,26 @@ public class TransactionInfo implements Parcelable, Comparable this.transfers = transfers; } + private TransactionInfo(Parcel in) { + direction = Direction.fromInteger(in.readInt()); + isPending = in.readByte() != 0; + isFailed = in.readByte() != 0; + amount = in.readLong(); + fee = in.readLong(); + blockheight = in.readLong(); + hash = in.readString(); + timestamp = in.readLong(); + paymentId = in.readString(); + accountIndex = in.readInt(); + addressIndex = in.readInt(); + confirmations = in.readLong(); + subaddressLabel = in.readString(); + transfers = in.readArrayList(Transfer.class.getClassLoader()); + txKey = in.readString(); + notes = in.readString(); + address = in.readString(); + } + public boolean isConfirmed() { return confirmations >= CONFIRMATION; } @@ -136,36 +145,6 @@ public class TransactionInfo implements Parcelable, Comparable out.writeString(address); } - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public TransactionInfo createFromParcel(Parcel in) { - return new TransactionInfo(in); - } - - public TransactionInfo[] newArray(int size) { - return new TransactionInfo[size]; - } - }; - - private TransactionInfo(Parcel in) { - direction = Direction.fromInteger(in.readInt()); - isPending = in.readByte() != 0; - isFailed = in.readByte() != 0; - amount = in.readLong(); - fee = in.readLong(); - blockheight = in.readLong(); - hash = in.readString(); - timestamp = in.readLong(); - paymentId = in.readString(); - accountIndex = in.readInt(); - addressIndex = in.readInt(); - confirmations = in.readLong(); - subaddressLabel = in.readString(); - transfers = in.readArrayList(Transfer.class.getClassLoader()); - txKey = in.readString(); - notes = in.readString(); - address = in.readString(); - } - @Override public int describeContents() { return 0; @@ -183,4 +162,23 @@ public class TransactionInfo implements Parcelable, Comparable return this.hash.compareTo(another.hash); } } + + @RequiredArgsConstructor + public enum Direction { + Direction_In(0), + Direction_Out(1); + + @Getter + private final int value; + + public static Direction fromInteger(int n) { + switch (n) { + case 0: + return Direction_In; + case 1: + return Direction_Out; + } + return null; + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/Transfer.java b/app/src/main/java/com/m2049r/xmrwallet/model/Transfer.java index 27ce6a0..9cfcfef 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/Transfer.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/Transfer.java @@ -20,20 +20,6 @@ import android.os.Parcel; import android.os.Parcelable; public class Transfer implements Parcelable { - public long amount; - public String address; - - public Transfer(long amount, String address) { - this.amount = amount; - this.address = address; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeLong(amount); - out.writeString(address); - } - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public Transfer createFromParcel(Parcel in) { return new Transfer(in); @@ -43,12 +29,25 @@ public class Transfer implements Parcelable { return new Transfer[size]; } }; + public long amount; + public String address; + + public Transfer(long amount, String address) { + this.amount = amount; + this.address = address; + } private Transfer(Parcel in) { amount = in.readLong(); address = in.readString(); } + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(amount); + out.writeString(address); + } + @Override public int describeContents() { return 0; diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java b/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java index bc705f2..99b7dfa 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java @@ -33,53 +33,47 @@ import timber.log.Timber; public class Wallet { final static public long SWEEP_ALL = Long.MAX_VALUE; + private static final String NEW_ACCOUNT_NAME = "Untitled account"; // src/wallet/wallet2.cpp:941 static { System.loadLibrary("monerujo"); } - static public class Status { - Status(int status, String errorString) { - this.status = StatusEnum.values()[status]; - this.errorString = errorString; - } + boolean synced = false; + private int accountIndex = 0; + private long handle = 0; + private long listenerHandle = 0; + private PendingTransaction pendingTransaction = null; + private TransactionHistory history = null; - final private StatusEnum status; - final private String errorString; - @Nullable - private ConnectionStatus connectionStatus; // optional - - public StatusEnum getStatus() { - return status; - } - - public String getErrorString() { - return errorString; - } - - public void setConnectionStatus(@Nullable ConnectionStatus connectionStatus) { - this.connectionStatus = connectionStatus; - } - - @Nullable - public ConnectionStatus getConnectionStatus() { - return connectionStatus; - } - - public boolean isOk() { - return (getStatus() == StatusEnum.Status_Ok) - && ((getConnectionStatus() == null) || - (getConnectionStatus() == ConnectionStatus.ConnectionStatus_Connected)); - } - - @Override - @NonNull - public String toString() { - return "Wallet.Status: " + status + "/" + errorString + "/" + connectionStatus; - } + Wallet(long handle) { + this.handle = handle; } - private int accountIndex = 0; + Wallet(long handle, int accountIndex) { + this.handle = handle; + this.accountIndex = accountIndex; + } + + public static native String getDisplayAmount(long amount); + + public static native long getAmountFromString(String amount); + + public static native long getAmountFromDouble(double amount); + + public static native String generatePaymentId(); + + public static native boolean isPaymentIdValid(String payment_id); + + public static boolean isAddressValid(String address) { + return isAddressValid(address, WalletManager.getInstance().getNetworkType().getValue()); + } + + public static native boolean isAddressValid(String address, int networkType); + + public static native String getPaymentIdFromAddress(String address, int networkType); + + public static native long getMaximumAllowedAmount(); public int getAccountIndex() { return accountIndex; @@ -95,40 +89,6 @@ public class Wallet { return new File(getPath()).getName(); } - private long handle = 0; - private long listenerHandle = 0; - - Wallet(long handle) { - this.handle = handle; - } - - Wallet(long handle, int accountIndex) { - this.handle = handle; - this.accountIndex = accountIndex; - } - - @RequiredArgsConstructor - @Getter - public enum Device { - Device_Undefined(0, 0), - Device_Software(50, 200), - Device_Ledger(5, 20); - private final int accountLookahead; - private final int subaddressLookahead; - } - - public enum StatusEnum { - Status_Ok, - Status_Error, - Status_Critical - } - - public enum ConnectionStatus { - ConnectionStatus_Disconnected, - ConnectionStatus_Connected, - ConnectionStatus_WrongVersion - } - public native String getSeed(String offset); public native String getSeedLanguage(); @@ -153,6 +113,9 @@ public class Wallet { return getAddress(accountIndex); } +//TODO virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0; +//TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0; + public String getAddress(int accountIndex) { return getAddressJ(accountIndex, 0); } @@ -193,19 +156,25 @@ public class Wallet { public native int nettype(); -//TODO virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0; -//TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0; +// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0; +// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0; public native String getIntegratedAddress(String payment_id); public native String getSecretViewKey(); + // virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0; +// virtual bool connectToDaemon() = 0; + public native String getSecretSpendKey(); public boolean store() { return store(""); } +//TODO virtual void setTrustedDaemon(bool arg) = 0; +//TODO virtual bool trustedDaemon() const = 0; + public native synchronized boolean store(String path); public boolean close() { @@ -225,15 +194,9 @@ public class Wallet { private native boolean initJ(String daemon_address, long upper_transaction_size_limit, String daemon_username, String daemon_password, String proxy); -// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0; -// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0; - - public native void setRestoreHeight(long height); - public native long getRestoreHeight(); - // virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0; -// virtual bool connectToDaemon() = 0; + public native void setRestoreHeight(long height); public ConnectionStatus getConnectionStatus() { int s = getConnectionStatusJ(); @@ -242,9 +205,6 @@ public class Wallet { private native int getConnectionStatusJ(); -//TODO virtual void setTrustedDaemon(bool arg) = 0; -//TODO virtual bool trustedDaemon() const = 0; - public native boolean setProxy(String address); public long getBalance() { @@ -273,8 +233,6 @@ public class Wallet { public native long getDaemonBlockChainTargetHeight(); - boolean synced = false; - public boolean isSynchronized() { return synced; } @@ -283,26 +241,6 @@ public class Wallet { this.synced = true; } - public static native String getDisplayAmount(long amount); - - public static native long getAmountFromString(String amount); - - public static native long getAmountFromDouble(double amount); - - public static native String generatePaymentId(); - - public static native boolean isPaymentIdValid(String payment_id); - - public static boolean isAddressValid(String address) { - return isAddressValid(address, WalletManager.getInstance().getNetworkType().getValue()); - } - - public static native boolean isAddressValid(String address, int networkType); - - public static native String getPaymentIdFromAddress(String address, int networkType); - - public static native long getMaximumAllowedAmount(); - public native void startRefresh(); public native void pauseRefresh(); @@ -318,16 +256,13 @@ public class Wallet { rescanBlockchainAsyncJ(); } -//TODO virtual void setAutoRefreshInterval(int millis) = 0; -//TODO virtual int autoRefreshInterval() const = 0; - - - private PendingTransaction pendingTransaction = null; - public PendingTransaction getPendingTransaction() { return pendingTransaction; } +//TODO virtual void setAutoRefreshInterval(int millis) = 0; +//TODO virtual int autoRefreshInterval() const = 0; + public void disposePendingTransaction() { if (pendingTransaction != null) { disposeTransaction(pendingTransaction); @@ -366,7 +301,6 @@ public class Wallet { int mixin_count, int priority, int accountIndex); - public PendingTransaction createSweepUnmixableTransaction() { disposePendingTransaction(); long txHandle = createSweepUnmixableTransactionJ(); @@ -376,19 +310,8 @@ public class Wallet { private native long createSweepUnmixableTransactionJ(); -//virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0; -//virtual bool submitTransaction(const std::string &fileName) = 0; - public native void disposeTransaction(PendingTransaction pendingTransaction); -//virtual bool exportKeyImages(const std::string &filename) = 0; -//virtual bool importKeyImages(const std::string &filename) = 0; - - -//virtual TransactionHistory * history() const = 0; - - private TransactionHistory history = null; - public TransactionHistory getHistory() { if (history == null) { history = new TransactionHistory(getHistoryJ(), accountIndex); @@ -396,15 +319,21 @@ public class Wallet { return history; } +//virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0; +//virtual bool submitTransaction(const std::string &fileName) = 0; + private native long getHistoryJ(); +//virtual bool exportKeyImages(const std::string &filename) = 0; +//virtual bool importKeyImages(const std::string &filename) = 0; + + +//virtual TransactionHistory * history() const = 0; + public void refreshHistory() { getHistory().refreshWithNotes(this); } -//virtual AddressBook * addressBook() const = 0; -//virtual void setListener(WalletListener *) = 0; - private native long setListenerJ(WalletListener listener); public void setListener(WalletListener listener) { @@ -413,6 +342,9 @@ public class Wallet { public native int getDefaultMixin(); +//virtual AddressBook * addressBook() const = 0; +//virtual void setListener(WalletListener *) = 0; + public native void setDefaultMixin(int mixin); public native boolean setUserNote(String txid, String note); @@ -421,14 +353,6 @@ public class Wallet { public native String getTxKey(String txid); -//virtual std::string signMessage(const std::string &message) = 0; -//virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0; - -//virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &tvAmount, std::string &tx_description, std::string &recipient_name, std::vector &unknown_parameters, std::string &error) = 0; -//virtual bool rescanSpent() = 0; - - private static final String NEW_ACCOUNT_NAME = "Untitled account"; // src/wallet/wallet2.cpp:941 - public void addAccount() { addAccount(NEW_ACCOUNT_NAME); } @@ -439,6 +363,16 @@ public class Wallet { return getAccountLabel(accountIndex); } +//virtual std::string signMessage(const std::string &message) = 0; +//virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0; + +//virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &tvAmount, std::string &tx_description, std::string &recipient_name, std::vector &unknown_parameters, std::string &error) = 0; +//virtual bool rescanSpent() = 0; + + public void setAccountLabel(String label) { + setAccountLabel(accountIndex, label); + } + public String getAccountLabel(int accountIndex) { String label = getSubaddressLabel(accountIndex, 0); if (label.equals(NEW_ACCOUNT_NAME)) { @@ -456,10 +390,6 @@ public class Wallet { public native String getSubaddressLabel(int accountIndex, int addressIndex); - public void setAccountLabel(String label) { - setAccountLabel(accountIndex, label); - } - public void setAccountLabel(int accountIndex, String label) { setSubaddressLabel(accountIndex, 0, label); } @@ -504,4 +434,66 @@ public class Wallet { private native int getDeviceTypeJ(); + @RequiredArgsConstructor + @Getter + public enum Device { + Device_Undefined(0, 0), + Device_Software(50, 200), + Device_Ledger(5, 20); + private final int accountLookahead; + private final int subaddressLookahead; + } + + public enum StatusEnum { + Status_Ok, + Status_Error, + Status_Critical + } + + public enum ConnectionStatus { + ConnectionStatus_Disconnected, + ConnectionStatus_Connected, + ConnectionStatus_WrongVersion + } + + static public class Status { + final private StatusEnum status; + final private String errorString; + @Nullable + private ConnectionStatus connectionStatus; // optional + Status(int status, String errorString) { + this.status = StatusEnum.values()[status]; + this.errorString = errorString; + } + + public StatusEnum getStatus() { + return status; + } + + public String getErrorString() { + return errorString; + } + + @Nullable + public ConnectionStatus getConnectionStatus() { + return connectionStatus; + } + + public void setConnectionStatus(@Nullable ConnectionStatus connectionStatus) { + this.connectionStatus = connectionStatus; + } + + public boolean isOk() { + return (getStatus() == StatusEnum.Status_Ok) + && ((getConnectionStatus() == null) || + (getConnectionStatus() == ConnectionStatus.ConnectionStatus_Connected)); + } + + @Override + @NonNull + public String toString() { + return "Wallet.Status: " + status + "/" + errorString + "/" + connectionStatus; + } + } + } diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java index bd28f14..99cf262 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java @@ -17,7 +17,6 @@ package com.m2049r.xmrwallet.model; import com.m2049r.xmrwallet.data.Node; -import com.m2049r.xmrwallet.ledger.Ledger; import com.m2049r.xmrwallet.util.RestoreHeight; import java.io.File; @@ -31,12 +30,26 @@ import timber.log.Timber; public class WalletManager { + //TODO: maybe put these in an enum like in monero core - but why? + static public int LOGLEVEL_SILENT = -1; + static public int LOGLEVEL_WARN = 0; + static public int LOGLEVEL_INFO = 1; + static public int LOGLEVEL_DEBUG = 2; + static public int LOGLEVEL_TRACE = 3; + static public int LOGLEVEL_MAX = 4; + // no need to keep a reference to the REAL WalletManager (we get it every tvTime we need it) + private static WalletManager Instance = null; + static { System.loadLibrary("monerujo"); } - // no need to keep a reference to the REAL WalletManager (we get it every tvTime we need it) - private static WalletManager Instance = null; + private final NetworkType networkType = NetworkType.NetworkType_Mainnet; + private Wallet managedWallet = null; + private String daemonAddress = null; + private String daemonUsername = ""; + private String daemonPassword = ""; + private String proxy = ""; public static synchronized WalletManager getInstance() { if (WalletManager.Instance == null) { @@ -46,10 +59,6 @@ public class WalletManager { return WalletManager.Instance; } - public String addressPrefix() { - return addressPrefix(getNetworkType()); - } - static public String addressPrefix(NetworkType networkType) { switch (networkType) { case NetworkType_Testnet: @@ -63,7 +72,23 @@ public class WalletManager { } } - private Wallet managedWallet = null; + static public native void initLogger(String argv0, String defaultLogBaseName); + + static public native void setLogLevel(int level); + + static public native void logDebug(String category, String message); + + static public native void logInfo(String category, String message); + + static public native void logWarning(String category, String message); + + static public native void logError(String category, String message); + + static public native String moneroVersion(); + + public String addressPrefix() { + return addressPrefix(getNetworkType()); + } public Wallet getWallet() { return managedWallet; @@ -108,6 +133,8 @@ public class WalletManager { return wallet; } + //public native List findWallets(String path); // this does not work - some error in boost + private native long createWalletJ(String path, String password, String language, int networkType); public Wallet openAccount(String path, int accountIndex, String password) { @@ -117,6 +144,8 @@ public class WalletManager { return wallet; } +//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0; + public Wallet openWallet(String path, String password) { long walletHandle = openWalletJ(path, password, getNetworkType().getValue()); Wallet wallet = new Wallet(walletHandle); @@ -163,7 +192,7 @@ public class WalletManager { String deviceName) { long walletHandle = createWalletFromDeviceJ(aFile.getAbsolutePath(), password, getNetworkType().getValue(), deviceName, restoreHeight, - Ledger.SUBADDRESS_LOOKAHEAD); + "5:20"); Wallet wallet = new Wallet(walletHandle); manageWallet(wallet); return wallet; @@ -175,7 +204,6 @@ public class WalletManager { long restoreHeight, String subaddressLookahead); - public native boolean closeJ(Wallet wallet); public boolean close(Wallet wallet) { @@ -208,25 +236,6 @@ public class WalletManager { private native int queryWalletDeviceJ(String keys_file_name, String password); - //public native List findWallets(String path); // this does not work - some error in boost - - public class WalletInfo implements Comparable { - @Getter - final private File path; - @Getter - final private String name; - - public WalletInfo(File wallet) { - path = wallet.getParentFile(); - name = wallet.getName(); - } - - @Override - public int compareTo(WalletInfo another) { - return name.toLowerCase().compareTo(another.name.toLowerCase()); - } - } - public List findWallets(File path) { List wallets = new ArrayList<>(); Timber.d("Scanning: %s", path.getAbsolutePath()); @@ -243,11 +252,6 @@ public class WalletManager { return wallets; } -//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0; - - private String daemonAddress = null; - private final NetworkType networkType = NetworkType.NetworkType_Mainnet; - public NetworkType getNetworkType() { return networkType; } @@ -279,20 +283,18 @@ public class WalletManager { private native void setDaemonAddressJ(String address); - private String daemonUsername = ""; - public String getDaemonUsername() { return daemonUsername; } - private String daemonPassword = ""; - public String getDaemonPassword() { return daemonPassword; } public native int getDaemonVersion(); +//TODO static std::tuple checkUpdates(const std::string &software, const std::string &subdir); + public native long getBlockchainHeight(); public native long getBlockchainTargetHeight(); @@ -311,8 +313,6 @@ public class WalletManager { public native String resolveOpenAlias(String address, boolean dnssec_valid); - private String proxy = ""; - public String getProxy() { return proxy; } @@ -324,27 +324,20 @@ public class WalletManager { public native boolean setProxyJ(String address); -//TODO static std::tuple checkUpdates(const std::string &software, const std::string &subdir); + public class WalletInfo implements Comparable { + @Getter + final private File path; + @Getter + final private String name; - static public native void initLogger(String argv0, String defaultLogBaseName); + public WalletInfo(File wallet) { + path = wallet.getParentFile(); + name = wallet.getName(); + } - //TODO: maybe put these in an enum like in monero core - but why? - static public int LOGLEVEL_SILENT = -1; - static public int LOGLEVEL_WARN = 0; - static public int LOGLEVEL_INFO = 1; - static public int LOGLEVEL_DEBUG = 2; - static public int LOGLEVEL_TRACE = 3; - static public int LOGLEVEL_MAX = 4; - - static public native void setLogLevel(int level); - - static public native void logDebug(String category, String message); - - static public native void logInfo(String category, String message); - - static public native void logWarning(String category, String message); - - static public native void logError(String category, String message); - - static public native String moneroVersion(); + @Override + public int compareTo(WalletInfo another) { + return name.toLowerCase().compareTo(another.name.toLowerCase()); + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/AddressService.java b/app/src/main/java/com/m2049r/xmrwallet/service/AddressService.java index bc3d86d..afc699e 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/AddressService.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/AddressService.java @@ -5,16 +5,8 @@ import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; -import java.util.ArrayList; -import java.util.HashMap; - public class AddressService extends ServiceBase { public static AddressService instance = null; - - public static AddressService getInstance() { - return instance; - } - private int latestAddressIndex = 1; public AddressService(MoneroHandlerThread thread) { @@ -22,9 +14,13 @@ public class AddressService extends ServiceBase { instance = this; } + public static AddressService getInstance() { + return instance; + } + public void refreshAddresses() { for (TransactionInfo info : HistoryService.getInstance().getHistory()) { - if(info.addressIndex >= latestAddressIndex) { + if (info.addressIndex >= latestAddressIndex) { latestAddressIndex = info.addressIndex + 1; } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/BalanceService.java b/app/src/main/java/com/m2049r/xmrwallet/service/BalanceService.java index ff68ea4..33cdaeb 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/BalanceService.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/BalanceService.java @@ -3,26 +3,23 @@ package com.m2049r.xmrwallet.service; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; -import com.m2049r.xmrwallet.MainActivity; import com.m2049r.xmrwallet.model.WalletManager; public class BalanceService extends ServiceBase { public static BalanceService instance = null; - - public static BalanceService getInstance() { - return instance; - } - private final MutableLiveData _balance = new MutableLiveData<>(0L); - public LiveData balance = _balance; private final MutableLiveData _lockedBalance = new MutableLiveData<>(0L); + public LiveData balance = _balance; public LiveData lockedBalance = _lockedBalance; - public BalanceService(MoneroHandlerThread thread) { super(thread); instance = this; } + public static BalanceService getInstance() { + return instance; + } + public void refreshBalance() { _balance.postValue(getUnlockedBalanceRaw()); _lockedBalance.postValue(getLockedBalanceRaw()); diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/BlockchainService.java b/app/src/main/java/com/m2049r/xmrwallet/service/BlockchainService.java index a2fb312..ccf8680 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/BlockchainService.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/BlockchainService.java @@ -3,26 +3,23 @@ package com.m2049r.xmrwallet.service; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; -import com.m2049r.xmrwallet.MainActivity; import com.m2049r.xmrwallet.model.WalletManager; public class BlockchainService extends ServiceBase { public static BlockchainService instance = null; - - public static BlockchainService getInstance() { - return instance; - } - - private long daemonHeight = 0; - private long lastDaemonHeightUpdateTimeMs = 0; private final MutableLiveData _currentHeight = new MutableLiveData<>(0L); public LiveData height = _currentHeight; - + private long daemonHeight = 0; + private long lastDaemonHeightUpdateTimeMs = 0; public BlockchainService(MoneroHandlerThread thread) { super(thread); instance = this; } + public static BlockchainService getInstance() { + return instance; + } + public void refreshBlockchain() { _currentHeight.postValue(getCurrentHeight()); } @@ -37,11 +34,11 @@ public class BlockchainService extends ServiceBase { public void setDaemonHeight(long height) { long t = System.currentTimeMillis(); - if(height > 0) { + if (height > 0) { daemonHeight = height; lastDaemonHeightUpdateTimeMs = t; } else { - if(t - lastDaemonHeightUpdateTimeMs > 120000) { + if (t - lastDaemonHeightUpdateTimeMs > 120000) { daemonHeight = WalletManager.getInstance().getWallet().getDaemonBlockChainHeight(); lastDaemonHeightUpdateTimeMs = t; } diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/HistoryService.java b/app/src/main/java/com/m2049r/xmrwallet/service/HistoryService.java index abbd645..dff4ce0 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/HistoryService.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/HistoryService.java @@ -2,25 +2,25 @@ package com.m2049r.xmrwallet.service; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; + import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.WalletManager; + import java.util.List; public class HistoryService extends ServiceBase { public static HistoryService instance = null; - - public static HistoryService getInstance() { - return instance; - } - private final MutableLiveData> _history = new MutableLiveData<>(); public LiveData> history = _history; - public HistoryService(MoneroHandlerThread thread) { super(thread); instance = this; } + public static HistoryService getInstance() { + return instance; + } + public void refreshHistory() { _history.postValue(getHistory()); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java b/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java index e26807f..5613cd4 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java @@ -28,8 +28,6 @@ import com.m2049r.xmrwallet.model.WalletListener; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.util.Constants; -import java.io.File; - /** * Handy class for starting a new thread that has a looper. The looper can then be @@ -37,10 +35,11 @@ import java.io.File; * The started Thread has a stck size of STACK_SIZE (=5MB) */ public class MoneroHandlerThread extends Thread implements WalletListener { - private Listener listener = null; // from src/cryptonote_config.h static public final long THREAD_STACK_SIZE = 5 * 1024 * 1024; - private Wallet wallet; + int triesLeft = 5; + private Listener listener = null; + private final Wallet wallet; public MoneroHandlerThread(String name, Listener listener, Wallet wallet) { super(null, null, name, THREAD_STACK_SIZE); @@ -57,7 +56,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener { @Override public void run() { boolean usesTor = PrefService.getInstance().getBoolean(Constants.PREF_USES_TOR, false); - if(usesTor) { + if (usesTor) { String proxy = "127.0.0.1:9050"; WalletManager.getInstance().setProxy(proxy); WalletManager.getInstance().setDaemon(Node.fromString(DefaultNodes.boldsuck.getUri())); @@ -93,13 +92,11 @@ public class MoneroHandlerThread extends Thread implements WalletListener { refresh(); } - int triesLeft = 5; - @Override public void refreshed() { Wallet.ConnectionStatus status = wallet.getFullStatus().getConnectionStatus(); - if(status == Wallet.ConnectionStatus.ConnectionStatus_Disconnected || status == null) { - if(triesLeft > 0) { + if (status == Wallet.ConnectionStatus.ConnectionStatus_Disconnected || status == null) { + if (triesLeft > 0) { wallet.startRefresh(); triesLeft--; } else { @@ -129,6 +126,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener { public interface Listener { void onRefresh(); + void onConnectionFail(); } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/PrefService.java b/app/src/main/java/com/m2049r/xmrwallet/service/PrefService.java index 10495c6..b1cdfa3 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/PrefService.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/PrefService.java @@ -3,18 +3,17 @@ package com.m2049r.xmrwallet.service; import android.content.Context; import android.content.SharedPreferences; -import com.m2049r.xmrwallet.MainActivity; import com.m2049r.xmrwallet.MoneroApplication; public class PrefService extends ServiceBase { public static SharedPreferences instance = null; - public static SharedPreferences getInstance() { - return instance; - } - public PrefService(MoneroApplication application) { super(null); instance = application.getSharedPreferences(application.getApplicationInfo().packageName, Context.MODE_PRIVATE); } + + public static SharedPreferences getInstance() { + return instance; + } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/TxService.java b/app/src/main/java/com/m2049r/xmrwallet/service/TxService.java index 4ff72ab..f414587 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/TxService.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/TxService.java @@ -1,21 +1,19 @@ package com.m2049r.xmrwallet.service; -import com.m2049r.xmrwallet.MainActivity; -import com.m2049r.xmrwallet.livedata.SingleLiveEvent; import com.m2049r.xmrwallet.model.PendingTransaction; public class TxService extends ServiceBase { public static TxService instance = null; - public static TxService getInstance() { - return instance; - } - public TxService(MoneroHandlerThread thread) { super(thread); instance = this; } + public static TxService getInstance() { + return instance; + } + public PendingTransaction createTx(String address, String amount, boolean sendAll, PendingTransaction.Priority feePriority) { return this.getThread().createTx(address, amount, sendAll, feePriority); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/CrazyPassEncoder.java b/app/src/main/java/com/m2049r/xmrwallet/util/CrazyPassEncoder.java deleted file mode 100644 index 37f5329..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/CrazyPassEncoder.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.util; - -import java.math.BigInteger; - -public class CrazyPassEncoder { - static final String BASE = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; - static final int PW_CHARS = 52; - - // this takes a 32 byte buffer and converts it to 52 alphnumeric characters - // separated by blanks every 4 characters = 13 groups of 4 - // always (padding by Xs if need be - static public String encode(byte[] data) { - if (data.length != 32) throw new IllegalArgumentException("data[] is not 32 bytes long"); - BigInteger rest = new BigInteger(1, data); - BigInteger remainder; - final StringBuilder result = new StringBuilder(); - final BigInteger base = BigInteger.valueOf(BASE.length()); - int i = 0; - do { - if ((i > 0) && (i % 4 == 0)) result.append(' '); - i++; - remainder = rest.remainder(base); - rest = rest.divide(base); - result.append(BASE.charAt(remainder.intValue())); - } while (!BigInteger.ZERO.equals(rest)); - // pad it - while (i < PW_CHARS) { - if ((i > 0) && (i % 4 == 0)) result.append(' '); - result.append('2'); - i++; - } - return result.toString(); - } - - static public String reformat(String password) { - // maybe this is a CrAzYpass without blanks? or lowercase letters - String noBlanks = password.toUpperCase().replaceAll(" ", ""); - if (noBlanks.length() == PW_CHARS) { // looks like a CrAzYpass - // insert blanks every 4 characters - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < PW_CHARS; i++) { - if ((i > 0) && (i % 4 == 0)) sb.append(' '); - char c = noBlanks.charAt(i); - if (BASE.indexOf(c) < 0) return null; // invalid character found - sb.append(c); - } - return sb.toString(); - } else { - return null; // not a CrAzYpass - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/DayNightMode.java b/app/src/main/java/com/m2049r/xmrwallet/util/DayNightMode.java index 6e8cdff..44f8ace 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/DayNightMode.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/DayNightMode.java @@ -31,10 +31,6 @@ public enum DayNightMode { this.nightMode = nightMode; } - public int getNightMode() { - return nightMode; - } - static public DayNightMode getValue(int nightMode) { for (DayNightMode mode : DayNightMode.values()) { if (mode.nightMode == nightMode) @@ -42,4 +38,8 @@ public enum DayNightMode { } return UNKNOWN; } + + public int getNightMode() { + return nightMode; + } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java index e753ce7..f8909f3 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java @@ -30,10 +30,6 @@ import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.VectorDrawable; -import android.os.AsyncTask; -import android.os.StrictMode; -import android.system.ErrnoException; -import android.system.Os; import android.view.View; import android.view.WindowManager; import android.view.animation.Animation; @@ -42,12 +38,9 @@ import android.view.inputmethod.InputMethodManager; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; -import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; -import com.m2049r.xmrwallet.BuildConfig; import com.m2049r.xmrwallet.R; -import com.m2049r.xmrwallet.data.Crypto; import com.m2049r.xmrwallet.model.WalletManager; import java.io.File; @@ -67,17 +60,18 @@ import timber.log.Timber; public class Helper { static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass"; - static public final String BASE_CRYPTO = Crypto.XMR.getSymbol(); static public final int XMR_DECIMALS = 12; static public final long ONE_XMR = Math.round(Math.pow(10, Helper.XMR_DECIMALS)); static public final boolean SHOW_EXCHANGERATES = true; - static public boolean ALLOW_SHIFT = false; - + static public final int PERMISSIONS_REQUEST_CAMERA = 7; + static final int HTTP_TIMEOUT = 5000; static private final String WALLET_DIR = "wallets"; static private final String MONERO_DIR = "monero"; - + private final static char[] HexArray = "0123456789ABCDEF".toCharArray(); + static public boolean ALLOW_SHIFT = false; static public int DISPLAY_DIGITS_INFO = 5; + static private Animation ShakeAnimation; static public File getWalletRoot(Context context) { return getStorage(context, WALLET_DIR); @@ -97,8 +91,6 @@ public class Helper { return dir; } - static public final int PERMISSIONS_REQUEST_CAMERA = 7; - static public boolean getCameraPermission(Activity context) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { if (context.checkSelfPermission(Manifest.permission.CAMERA) @@ -227,8 +219,6 @@ public class Helper { return bitmap; } - static final int HTTP_TIMEOUT = 5000; - static public String getUrl(String httpsUrl) { HttpsURLConnection urlConnection = null; try { @@ -261,7 +251,7 @@ public class Helper { } static public void clipBoardCopy(Context context, String label, String text) { - if(context != null) { + if (context != null) { ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText(label, text); clipboardManager.setPrimaryClip(clip); @@ -284,8 +274,6 @@ public class Helper { return null; } - static private Animation ShakeAnimation; - static public Animation getShakeAnimation(Context context) { if (ShakeAnimation == null) { synchronized (Helper.class) { @@ -297,8 +285,6 @@ public class Helper { return ShakeAnimation; } - private final static char[] HexArray = "0123456789ABCDEF".toCharArray(); - public static String bytesToHex(byte[] data) { if ((data != null) && (data.length > 0)) return String.format("%0" + (data.length * 2) + "X", new BigInteger(1, data)); diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java index 984c171..683c037 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java @@ -82,16 +82,6 @@ public class KeyStoreHelper { } } - static public class BrokenPasswordStoreException extends Exception { - BrokenPasswordStoreException() { - super(); - } - - BrokenPasswordStoreException(Throwable cause) { - super(cause); - } - } - /** * Creates a public and private key and stores it using the Android Key * Store, so that only this application will be able to access the keys. @@ -267,4 +257,14 @@ public class KeyStoreHelper { String WALLET_PASS_PREFS_NAME = "wallet"; String WALLET_PASS_KEY_PREFIX = "walletKey-"; } + + static public class BrokenPasswordStoreException extends Exception { + BrokenPasswordStoreException() { + super(); + } + + BrokenPasswordStoreException(Throwable cause) { + super(cause); + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/LegacyStorageHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/LegacyStorageHelper.java index 20ea61c..5d17d19 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/LegacyStorageHelper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/LegacyStorageHelper.java @@ -24,6 +24,8 @@ import timber.log.Timber; @RequiredArgsConstructor public class LegacyStorageHelper { + private static final Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$"); + private static final String MIGRATED_KEY = "migrated_legacy_storage"; final private File srcDir; final private File dstDir; @@ -49,6 +51,70 @@ public class LegacyStorageHelper { } } + private static boolean isExternalStorageWritable() { + String state = Environment.getExternalStorageState(); + return Environment.MEDIA_MOUNTED.equals(state); + } + + private static File getWalletRoot() { + if (!isExternalStorageWritable()) + throw new IllegalStateException(); + + // wallet folder for legacy (pre-Q) installations + final String FLAVOR_SUFFIX = + (BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR) + + (BuildConfig.DEBUG ? "-debug" : ""); + final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX; + + File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR); + if (!dir.exists() || !dir.isDirectory()) + throw new IllegalStateException(); + return dir; + } + + private static boolean hasReadPermission(Context context) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + return context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_DENIED; + } else { + return true; + } + } + + private static String getUniqueName(File root, String name) { + if (!(new File(root, name + ".keys")).exists()) // does not exist => it's ok to use + return name; + + File[] wallets = root.listFiles( + (dir, filename) -> { + Matcher m = WALLET_PATTERN.matcher(filename); + if (m.find()) + return m.group(1).equals(name); + else return false; + }); + if (wallets.length == 0) return name + " (1)"; + int maxIndex = 0; + for (File wallet : wallets) { + try { + final Matcher m = WALLET_PATTERN.matcher(wallet.getName()); + if (!m.find()) + throw new IllegalStateException("this must match as it did before"); + final int index = Integer.parseInt(m.group(2)); + if (index > maxIndex) maxIndex = index; + } catch (NumberFormatException ex) { + // this cannot happen & we can ignore it if it does + } + } + return name + " (" + (maxIndex + 1) + ")"; + } + + public static boolean isStorageMigrated(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MIGRATED_KEY, false); + } + + public static void setStorageMigrated(Context context) { + PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(MIGRATED_KEY, true).apply(); + } + public void migrate() { String addressPrefix = WalletManager.getInstance().addressPrefix(); File[] wallets = srcDir.listFiles((dir, filename) -> filename.endsWith(".keys")); @@ -99,72 +165,4 @@ public class LegacyStorageHelper { inChannel.transferTo(0, inChannel.size(), outChannel); } } - - private static boolean isExternalStorageWritable() { - String state = Environment.getExternalStorageState(); - return Environment.MEDIA_MOUNTED.equals(state); - } - - private static File getWalletRoot() { - if (!isExternalStorageWritable()) - throw new IllegalStateException(); - - // wallet folder for legacy (pre-Q) installations - final String FLAVOR_SUFFIX = - (BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR) - + (BuildConfig.DEBUG ? "-debug" : ""); - final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX; - - File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR); - if (!dir.exists() || !dir.isDirectory()) - throw new IllegalStateException(); - return dir; - } - - private static boolean hasReadPermission(Context context) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { - return context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_DENIED; - } else { - return true; - } - } - - private static final Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$"); - - private static String getUniqueName(File root, String name) { - if (!(new File(root, name + ".keys")).exists()) // does not exist => it's ok to use - return name; - - File[] wallets = root.listFiles( - (dir, filename) -> { - Matcher m = WALLET_PATTERN.matcher(filename); - if (m.find()) - return m.group(1).equals(name); - else return false; - }); - if (wallets.length == 0) return name + " (1)"; - int maxIndex = 0; - for (File wallet : wallets) { - try { - final Matcher m = WALLET_PATTERN.matcher(wallet.getName()); - if (!m.find()) - throw new IllegalStateException("this must match as it did before"); - final int index = Integer.parseInt(m.group(2)); - if (index > maxIndex) maxIndex = index; - } catch (NumberFormatException ex) { - // this cannot happen & we can ignore it if it does - } - } - return name + " (" + (maxIndex + 1) + ")"; - } - - private static final String MIGRATED_KEY = "migrated_legacy_storage"; - - public static boolean isStorageMigrated(Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MIGRATED_KEY, false); - } - - public static void setStorageMigrated(Context context) { - PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(MIGRATED_KEY, true).apply(); - } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/MoneroThreadPoolExecutor.java b/app/src/main/java/com/m2049r/xmrwallet/util/MoneroThreadPoolExecutor.java index 27bb64c..f383942 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/MoneroThreadPoolExecutor.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/MoneroThreadPoolExecutor.java @@ -28,11 +28,11 @@ import java.util.concurrent.atomic.AtomicInteger; public class MoneroThreadPoolExecutor { + public static final Executor MONERO_THREAD_POOL_EXECUTOR; private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE_SECONDS = 30; - private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); @@ -40,12 +40,9 @@ public class MoneroThreadPoolExecutor { return new Thread(null, r, "MoneroTask #" + mCount.getAndIncrement(), MoneroHandlerThread.THREAD_STACK_SIZE); } }; - private static final BlockingQueue sPoolWorkQueue = new LinkedBlockingQueue<>(128); - public static final Executor MONERO_THREAD_POOL_EXECUTOR; - static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/NetCipherHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/NetCipherHelper.java deleted file mode 100644 index 08dc864..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/NetCipherHelper.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright (c) 2021 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.util; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; - -import com.burgstaller.okhttp.AuthenticationCacheInterceptor; -import com.burgstaller.okhttp.CachingAuthenticatorDecorator; -import com.burgstaller.okhttp.digest.CachingAuthenticator; -import com.burgstaller.okhttp.digest.Credentials; -import com.burgstaller.okhttp.digest.DigestAuthenticator; - -import org.json.JSONObject; - -import java.io.IOException; -import java.net.Proxy; -import java.util.ArrayList; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -import info.guardianproject.netcipher.client.StrongOkHttpClientBuilder; -import info.guardianproject.netcipher.proxy.OrbotHelper; -import info.guardianproject.netcipher.proxy.SignatureUtils; -import info.guardianproject.netcipher.proxy.StatusCallback; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.HttpUrl; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.RequestBody; -import okhttp3.Response; -import timber.log.Timber; - -@RequiredArgsConstructor -public class NetCipherHelper implements StatusCallback { - public static final String USER_AGENT = "Monerujo/1.0"; - public static final int HTTP_TIMEOUT = 1000; //ms - public static final int TOR_TIMEOUT_CONNECT = 5000; //ms - public static final int TOR_TIMEOUT = 2000; //ms - - public interface OnStatusChangedListener { - void connected(); - - void disconnected(); - - void notInstalled(); - - void notEnabled(); - } - - final private Context context; - final private OrbotHelper orbot; - - @SuppressLint("StaticFieldLeak") - private static NetCipherHelper Instance; - - public static void createInstance(Context context) { - if (Instance == null) { - synchronized (NetCipherHelper.class) { - if (Instance == null) { - final Context applicationContext = context.getApplicationContext(); - Instance = new NetCipherHelper(applicationContext, OrbotHelper.get(context).statusTimeout(5000)); - } - } - } - } - - public static NetCipherHelper getInstance() { - if (Instance == null) throw new IllegalStateException("NetCipherHelper is null"); - return Instance; - } - - private OkHttpClient client; - - private void createTorClient(Intent statusIntent) { - String orbotStatus = statusIntent.getStringExtra(OrbotHelper.EXTRA_STATUS); - if (orbotStatus == null) throw new IllegalStateException("status is null"); - if (!orbotStatus.equals(OrbotHelper.STATUS_ON)) - throw new IllegalStateException("Orbot is not ON"); - try { - final OkHttpClient.Builder okBuilder = new OkHttpClient.Builder() - .connectTimeout(TOR_TIMEOUT_CONNECT, TimeUnit.MILLISECONDS) - .writeTimeout(TOR_TIMEOUT, TimeUnit.MILLISECONDS) - .readTimeout(TOR_TIMEOUT, TimeUnit.MILLISECONDS); - client = new StrongOkHttpClientBuilder(context) - .withSocksProxy() - .applyTo(okBuilder, statusIntent) - .build(); - Helper.ALLOW_SHIFT = false; // no shifting with Tor - } catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - private void createClearnetClient() { - try { - client = new OkHttpClient.Builder() - .connectTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS) - .writeTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS) - .readTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS) - .build(); - Helper.ALLOW_SHIFT = true; - } catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - private OnStatusChangedListener onStatusChangedListener; - - public static void deregister() { - getInstance().onStatusChangedListener = null; - } - - public static void register(OnStatusChangedListener listener) { - final NetCipherHelper me = getInstance(); - me.onStatusChangedListener = listener; - - // NOT_INSTALLED is dealt with through the callbacks - me.orbot.removeStatusCallback(me) // make sure we are registered just once - .addStatusCallback(me); - - // deal with org.torproject.android.intent.action.STATUS = STARTS_DISABLED - me.context.registerReceiver(orbotStatusReceiver, new IntentFilter(OrbotHelper.ACTION_STATUS)); - - me.startTor(); - } - - // for StatusCallback - public enum Status { - STARTING, - ENABLED, - STOPPING, - DISABLED, - NOT_INSTALLED, - NOT_ENABLED, - UNKNOWN - } - - private Status status = Status.UNKNOWN; - - @Override - public void onStarting() { - Timber.d("onStarting"); - status = Status.STARTING; - } - - @Override - public void onEnabled(Intent statusIntent) { - Timber.d("onEnabled"); - if (getTorPref() != Status.ENABLED) return; // do we want Tor? - createTorClient(statusIntent); - status = Status.ENABLED; - if (onStatusChangedListener != null) { - new Thread(() -> onStatusChangedListener.connected()).start(); - } - } - - @Override - public void onStopping() { - Timber.d("onStopping"); - status = Status.STOPPING; - } - - @Override - public void onDisabled() { - Timber.d("onDisabled"); - createClearnetClient(); - status = Status.DISABLED; - if (onStatusChangedListener != null) { - new Thread(() -> onStatusChangedListener.disconnected()).start(); - } - } - - @Override - public void onStatusTimeout() { - Timber.d("onStatusTimeout"); - createClearnetClient(); - // (timeout does not not change the status) - if (onStatusChangedListener != null) { - new Thread(() -> onStatusChangedListener.disconnected()).start(); - } - orbotInit = false; // do init() next time we try to open Tor - } - - @Override - public void onNotYetInstalled() { - Timber.d("onNotYetInstalled"); - // never mind then - orbot.removeStatusCallback(this); - createClearnetClient(); - status = Status.NOT_INSTALLED; - if (onStatusChangedListener != null) { - new Thread(() -> onStatusChangedListener.notInstalled()).start(); - } - } - - // user has not enabled background Orbot starts - public void onNotEnabled() { - Timber.d("onNotEnabled"); - // keep the callback in case they turn it on manually - setTorPref(Status.DISABLED); - createClearnetClient(); - status = Status.NOT_ENABLED; - if (onStatusChangedListener != null) { - new Thread(() -> onStatusChangedListener.notEnabled()).start(); - } - } - - static public Status getStatus() { - return getInstance().status; - } - - public void toggle() { - switch (getStatus()) { - case ENABLED: - onDisabled(); - setTorPref(Status.DISABLED); - break; - case DISABLED: - setTorPref(Status.ENABLED); - startTor(); - break; - } - } - - private boolean orbotInit = false; - - private void startTor() { - if (!isOrbotInstalled()) { - onNotYetInstalled(); - } else if (getTorPref() == Status.DISABLED) { - onDisabled(); - } else if (!orbotInit) { - orbotInit = orbot.init(); - } else { - orbot.requestStart(context); - } - } - - // extracted from OrbotHelper - private boolean isOrbotInstalled() { - ArrayList hashes = new ArrayList<>(); - // Tor Project signing key - hashes.add("A4:54:B8:7A:18:47:A8:9E:D7:F5:E7:0F:BA:6B:BA:96:F3:EF:29:C2:6E:09:81:20:4F:E3:47:BF:23:1D:FD:5B"); - // f-droid.org signing key - hashes.add("A7:02:07:92:4F:61:FF:09:37:1D:54:84:14:5C:4B:EE:77:2C:55:C1:9E:EE:23:2F:57:70:E1:82:71:F7:CB:AE"); - - return null != SignatureUtils.validateBroadcastIntent(context, - OrbotHelper.getOrbotStartIntent(context), - hashes, false); - } - - - static public boolean hasClient() { - return getInstance().client != null; - } - - static public boolean isTor() { - return getStatus() == Status.ENABLED; - } - - static public String getProxy() { - if (!isTor()) return ""; - final Proxy proxy = getInstance().client.proxy(); - if (proxy == null) return ""; - return proxy.address().toString().substring(1); - } - - @ToString - static public class Request { - final HttpUrl url; - final String json; - final String username; - final String password; - - public Request(final HttpUrl url, final String json, final String username, final String password) { - this.url = url; - this.json = json; - this.username = username; - this.password = password; - } - - public Request(final HttpUrl url, final JSONObject json) { - this(url, json == null ? null : json.toString(), null, null); - } - - public Request(final HttpUrl url) { - this(url, null, null, null); - } - - public void enqueue(Callback callback) { - newCall().enqueue(callback); - } - - public Response execute() throws IOException { - return newCall().execute(); - } - - private Call newCall() { - return getClient().newCall(getRequest()); - } - - private OkHttpClient getClient() { - if (mockClient != null) return mockClient; // Unit-test mode - final OkHttpClient client = getInstance().client; - if ((username != null) && (!username.isEmpty())) { - final DigestAuthenticator authenticator = new DigestAuthenticator(new Credentials(username, password)); - final Map authCache = new ConcurrentHashMap<>(); - return client.newBuilder() - .authenticator(new CachingAuthenticatorDecorator(authenticator, authCache)) - .addInterceptor(new AuthenticationCacheInterceptor(authCache)) - .build(); - // TODO: maybe cache & reuse the client for these credentials? - } else { - return client; - } - } - - private okhttp3.Request getRequest() { - final okhttp3.Request.Builder builder = - new okhttp3.Request.Builder() - .url(url) - .header("User-Agent", USER_AGENT); - if (json != null) { - builder.post(RequestBody.create(json, MediaType.parse("application/json"))); - } else { - builder.get(); - } - return builder.build(); - } - - // for unit tests only - static public OkHttpClient mockClient = null; - } - - private static final String PREFS_NAME = "tor"; - private static final String PREFS_STATUS = "status"; - private Status currentPref = Status.UNKNOWN; - - private Status getTorPref() { - if (currentPref != Status.UNKNOWN) return currentPref; - currentPref = Status.valueOf(context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - .getString(PREFS_STATUS, "DISABLED")); - return currentPref; - } - - private void setTorPref(Status status) { - if (getTorPref() == status) return; // no change - context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - .edit() - .putString(PREFS_STATUS, status.name()) - .apply(); - currentPref = status; - } - - private static final BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - Timber.d("%s/%s", intent.getAction(), intent.getStringExtra(OrbotHelper.EXTRA_STATUS)); - if (OrbotHelper.ACTION_STATUS.equals(intent.getAction())) { - if (OrbotHelper.STATUS_STARTS_DISABLED.equals(intent.getStringExtra(OrbotHelper.EXTRA_STATUS))) { - getInstance().onNotEnabled(); - } - } - } - }; - - public void installOrbot(Activity host) { - host.startActivity(OrbotHelper.getOrbotInstallIntent(context)); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/NightmodeHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/NightmodeHelper.java index 120c389..2e7afb1 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/NightmodeHelper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/NightmodeHelper.java @@ -17,12 +17,9 @@ package com.m2049r.xmrwallet.util; import android.annotation.SuppressLint; -import android.content.Context; -import android.preference.PreferenceManager; import androidx.appcompat.app.AppCompatDelegate; -import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.service.PrefService; public class NightmodeHelper { diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/NodePinger.java b/app/src/main/java/com/m2049r/xmrwallet/util/NodePinger.java deleted file mode 100644 index e353de1..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/NodePinger.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.util; - -import com.m2049r.xmrwallet.data.NodeInfo; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import timber.log.Timber; - -public class NodePinger { - static final public int NUM_THREADS = 10; - static final public long MAX_TIME = 5L; // seconds - - public interface Listener { - void publish(NodeInfo node); - } - - static public void execute(Collection nodes, final Listener listener) { - final ExecutorService exeService = Executors.newFixedThreadPool(NUM_THREADS); - List> taskList = new ArrayList<>(); - for (NodeInfo node : nodes) { - taskList.add(() -> node.testRpcService(listener)); - } - - try { - exeService.invokeAll(taskList, MAX_TIME, TimeUnit.SECONDS); - } catch (InterruptedException ex) { - Timber.w(ex); - } - exeService.shutdownNow(); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/OpenAliasHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/OpenAliasHelper.java deleted file mode 100644 index d648fec..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/OpenAliasHelper.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (c) 2018 m2049r - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Specs from https://openalias.org/ - -package com.m2049r.xmrwallet.util; - -import android.os.AsyncTask; - -import com.m2049r.xmrwallet.data.BarcodeData; -import com.m2049r.xmrwallet.data.Crypto; - -import org.jitsi.dnssec.validator.ValidatingResolver; -import org.xbill.DNS.DClass; -import org.xbill.DNS.Flags; -import org.xbill.DNS.Message; -import org.xbill.DNS.Name; -import org.xbill.DNS.RRset; -import org.xbill.DNS.Rcode; -import org.xbill.DNS.Record; -import org.xbill.DNS.Section; -import org.xbill.DNS.SimpleResolver; -import org.xbill.DNS.TXTRecord; -import org.xbill.DNS.Type; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; - -import timber.log.Timber; - -public class OpenAliasHelper { - public static final String OA1_SCHEME = "oa1:"; - public static final String OA1_ASSET = "asset"; - public static final String OA1_ADDRESS = "recipient_address"; - public static final String OA1_NAME = "recipient_name"; - public static final String OA1_DESCRIPTION = "tx_description"; - public static final String OA1_AMOUNT = "tx_amount"; - - public static final int DNS_LOOKUP_TIMEOUT = 2500; // ms - - public static void resolve(String name, OnResolvedListener resolvedListener) { - new DnsTxtResolver(resolvedListener).execute(name); - } - - public static Map parse(String oaString) { - return new OpenAliasParser(oaString).parse(); - } - - public interface OnResolvedListener { - void onResolved(Map dataMap); - - void onFailure(); - } - - private static class DnsTxtResolver extends AsyncTask { - List txts = new ArrayList<>(); - boolean dnssec = false; - - private final OnResolvedListener resolvedListener; - - private DnsTxtResolver(OnResolvedListener resolvedListener) { - this.resolvedListener = resolvedListener; - } - - // trust anchor of the root zone - // http://data.iana.org/root-anchors/root-anchors.xml - final String ROOT = - ". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5\n" + - ". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D"; - final String[] DNSSEC_SERVERS = { - "4.2.2.1", // Level3 - "4.2.2.2", // Level3 - "4.2.2.6", // Level3 - "1.1.1.1", // cloudflare - "9.9.9.9", // quad9 - "8.8.4.4", // google - "8.8.8.8" // google - }; - - @Override - protected Boolean doInBackground(String... args) { - //main(); - if (args.length != 1) return false; - String name = args[0]; - if ((name == null) || (name.isEmpty())) - return false; //pointless trying to lookup nothing - Timber.d("Resolving %s", name); - try { - SimpleResolver sr = new SimpleResolver(DNSSEC_SERVERS[new Random().nextInt(DNSSEC_SERVERS.length)]); - ValidatingResolver vr = new ValidatingResolver(sr); - vr.setTimeout(0, DNS_LOOKUP_TIMEOUT); - vr.loadTrustAnchors(new ByteArrayInputStream(ROOT.getBytes(StandardCharsets.US_ASCII))); - Record qr = Record.newRecord(Name.fromConstantString(name + "."), Type.TXT, DClass.IN); - Message response = vr.send(Message.newQuery(qr)); - final int rcode = response.getRcode(); - if (rcode != Rcode.NOERROR) { - Timber.i("Rcode: %s", Rcode.string(rcode)); - for (RRset set : response.getSectionRRsets(Section.ADDITIONAL)) { - if (set.getName().equals(Name.root) && set.getType() == Type.TXT - && set.getDClass() == ValidatingResolver.VALIDATION_REASON_QCLASS) { - Timber.i("Reason: %s", ((TXTRecord) set.first()).getStrings().get(0)); - } - } - return false; - } else { - dnssec = response.getHeader().getFlag(Flags.AD); - for (Record record : response.getSectionArray(Section.ANSWER)) { - if (record.getType() == Type.TXT) { - txts.addAll(((TXTRecord) record).getStrings()); - } - } - } - } catch (IOException | IllegalArgumentException ex) { - return false; - } - return true; - } - - @Override - public void onPostExecute(Boolean success) { - if (resolvedListener != null) - if (success) { - Map dataMap = new HashMap<>(); - for (String txt : txts) { - BarcodeData bc = BarcodeData.parseOpenAlias(txt, dnssec); - if (bc != null) { - if (!dataMap.containsKey(bc.asset)) { - dataMap.put(bc.asset, bc); - } - } - } - resolvedListener.onResolved(dataMap); - } else { - resolvedListener.onFailure(); - } - } - } - - private static class OpenAliasParser { - int currentPos = 0; - final String oaString; - StringBuilder sb = new StringBuilder(); - - OpenAliasParser(String oaString) { - this.oaString = oaString; - } - - Map parse() { - if ((oaString == null) || !oaString.startsWith(OA1_SCHEME)) return null; - if (oaString.charAt(oaString.length() - 1) != ';') return null; - - Map oaAttributes = new HashMap<>(); - - final int assetEnd = oaString.indexOf(' '); - if (assetEnd > 20) return null; // random sanity check - String asset = oaString.substring(OA1_SCHEME.length(), assetEnd); - oaAttributes.put(OA1_ASSET, asset); - - boolean inQuote = false; - boolean inKey = true; - String key = null; - for (currentPos = assetEnd; currentPos < oaString.length() - 1; currentPos++) { - char c = currentChar(); - if (inKey) { - if ((sb.length() == 0) && Character.isWhitespace(c)) continue; - if ((c == '\\') || (c == ';')) return null; - if (c == '=') { - key = sb.toString(); - if (oaAttributes.containsKey(key)) return null; // no duplicate keys allowed - sb.setLength(0); - inKey = false; - } else { - sb.append(c); - } - continue; - } - - // now we are in the value - if ((sb.length() == 0) && (c == '"')) { - inQuote = true; - continue; - } - if ((!inQuote || ((sb.length() > 0) && (c == '"'))) && (nextChar() == ';')) { - if (!inQuote) appendCurrentEscapedChar(); - oaAttributes.put(key, sb.toString()); - sb.setLength(0); - currentPos++; // skip the next ; - inQuote = false; - inKey = true; - key = null; - continue; - } - appendCurrentEscapedChar(); - } - if (inQuote) return null; - - if (key != null) { - oaAttributes.put(key, sb.toString()); - } - - return oaAttributes; - } - - char currentChar() { - return oaString.charAt(currentPos); - } - - char nextChar() throws IndexOutOfBoundsException { - int pos = currentPos; - char c = oaString.charAt(pos); - if (c == '\\') { - pos++; - } - return oaString.charAt(pos + 1); - } - - void appendCurrentEscapedChar() throws IndexOutOfBoundsException { - char c = oaString.charAt(currentPos); - if (c == '\\') { - c = oaString.charAt(++currentPos); - } - sb.append(c); - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/RestoreHeight.java b/app/src/main/java/com/m2049r/xmrwallet/util/RestoreHeight.java index 3f9f0f3..573609a 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/RestoreHeight.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/RestoreHeight.java @@ -29,18 +29,6 @@ public class RestoreHeight { static final int DIFFICULTY_TARGET = 120; // seconds static private RestoreHeight Singleton = null; - - static public RestoreHeight getInstance() { - if (Singleton == null) { - synchronized (RestoreHeight.class) { - if (Singleton == null) { - Singleton = new RestoreHeight(); - } - } - } - return Singleton; - } - private final Map blockheight = new HashMap<>(); RestoreHeight() { @@ -141,6 +129,17 @@ public class RestoreHeight { blockheight.put("2022-03-01", 2569711L); } + static public RestoreHeight getInstance() { + if (Singleton == null) { + synchronized (RestoreHeight.class) { + if (Singleton == null) { + Singleton = new RestoreHeight(); + } + } + } + return Singleton; + } + public long getHeight(String date) { SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd"); parser.setTimeZone(TimeZone.getTimeZone("UTC")); diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/ZipBackup.java b/app/src/main/java/com/m2049r/xmrwallet/util/ZipBackup.java deleted file mode 100644 index cfdc705..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/ZipBackup.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.m2049r.xmrwallet.util; - -import android.content.Context; -import android.net.Uri; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class ZipBackup { - final private Context context; - final private String walletName; - - private ZipOutputStream zip; - - public void writeTo(Uri zipUri) throws IOException { - if (zip != null) - throw new IllegalStateException("zip already initialized"); - try { - zip = new ZipOutputStream(context.getContentResolver().openOutputStream(zipUri)); - - final File walletRoot = Helper.getWalletRoot(context); - addFile(new File(walletRoot, walletName + ".keys")); - addFile(new File(walletRoot, walletName)); - - zip.close(); - } finally { - if (zip != null) zip.close(); - } - } - - private void addFile(File file) throws IOException { - if (!file.exists()) return; // ignore missing files (e.g. the cache file might not exist) - ZipEntry entry = new ZipEntry(file.getName()); - zip.putNextEntry(entry); - writeFile(file); - zip.closeEntry(); - } - - private void writeFile(File source) throws IOException { - try (InputStream is = new FileInputStream(source)) { - byte[] buffer = new byte[8192]; - int length; - while ((length = is.read(buffer)) > 0) { - zip.write(buffer, 0, length); - } - } - } - - private static final SimpleDateFormat DATETIME_FORMATTER = - new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss"); - - public String getBackupName() { - return walletName + " " + DATETIME_FORMATTER.format(new Date()); - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/ZipRestore.java b/app/src/main/java/com/m2049r/xmrwallet/util/ZipRestore.java deleted file mode 100644 index 1b9148b..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/ZipRestore.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.m2049r.xmrwallet.util; - -import android.content.Context; -import android.net.Uri; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import lombok.RequiredArgsConstructor; -import timber.log.Timber; - -@RequiredArgsConstructor -public class ZipRestore { - final private Context context; - final private Uri zipUri; - - private File walletRoot; - - private ZipInputStream zip; - - public boolean restore() throws IOException { - walletRoot = Helper.getWalletRoot(context); - String walletName = testArchive(); - if (walletName == null) return false; - - walletName = getUniqueName(walletName); - - if (zip != null) - throw new IllegalStateException("zip already initialized"); - try { - zip = new ZipInputStream(context.getContentResolver().openInputStream(zipUri)); - for (ZipEntry entry = zip.getNextEntry(); entry != null; zip.closeEntry(), entry = zip.getNextEntry()) { - File destination; - final String name = entry.getName(); - if (name.endsWith(".keys")) { - destination = new File(walletRoot, walletName + ".keys"); - } else if (name.endsWith(".address.txt")) { - destination = new File(walletRoot, walletName + ".address.txt"); - } else { - destination = new File(walletRoot, walletName); - } - writeFile(destination); - } - } finally { - if (zip != null) zip.close(); - } - return true; - } - - private void writeFile(File destination) throws IOException { - try (OutputStream os = new FileOutputStream(destination)) { - byte[] buffer = new byte[8192]; - int length; - while ((length = zip.read(buffer)) > 0) { - os.write(buffer, 0, length); - } - } - } - - // test the archive to contain files we expect & return the name of the contained wallet or null - private String testArchive() { - String walletName = null; - boolean keys = false; - ZipInputStream zipStream = null; - try { - zipStream = new ZipInputStream(context.getContentResolver().openInputStream(zipUri)); - for (ZipEntry entry = zipStream.getNextEntry(); entry != null; - zipStream.closeEntry(), entry = zipStream.getNextEntry()) { - if (entry.isDirectory()) - return null; - final String name = entry.getName(); - if ((new File(name)).getParentFile() != null) - return null; - if (walletName == null) { - if (name.endsWith(".keys")) { - walletName = name.substring(0, name.length() - ".keys".length()); - keys = true; // we have they keys - } else if (name.endsWith(".address.txt")) { - walletName = name.substring(0, name.length() - ".address.txt".length()); - } else { - walletName = name; - } - } else { // we have a wallet name - if (name.endsWith(".keys")) { - if (!name.equals(walletName + ".keys")) return null; - keys = true; // we have they keys - } else if (name.endsWith(".address.txt")) { - if (!name.equals(walletName + ".address.txt")) return null; - } else if (!name.equals(walletName)) return null; - } - } - } catch (IOException ex) { - return null; - } finally { - try { - if (zipStream != null) zipStream.close(); - } catch (IOException ex) { - Timber.w(ex); - } - } - // we need the keys at least - if (keys) return walletName; - else return null; - } - - final static Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$"); - - private String getUniqueName(String name) { - if (!(new File(walletRoot, name + ".keys")).exists()) // does not exist => it's ok to use - return name; - - File[] wallets = walletRoot.listFiles( - (dir, filename) -> { - Matcher m = WALLET_PATTERN.matcher(filename); - if (m.find()) - return m.group(1).equals(name); - else return false; - }); - if (wallets.length == 0) return name + " (1)"; - int maxIndex = 0; - for (File wallet : wallets) { - try { - final Matcher m = WALLET_PATTERN.matcher(wallet.getName()); - m.find(); - final int index = Integer.parseInt(m.group(2)); - if (index > maxIndex) maxIndex = index; - } catch (NumberFormatException ex) { - // this cannot happen & we can ignore it if it does - } - } - return name + " (" + (maxIndex + 1) + ")"; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/ledger/ECsecp256k1.java b/app/src/main/java/com/m2049r/xmrwallet/util/ledger/ECsecp256k1.java deleted file mode 100644 index 1aef005..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/ledger/ECsecp256k1.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Based on - * https://stackoverflow.com/a/19943894 - * - * Curve parameters from - * https://en.bitcoin.it/wiki/Secp256k1 - * - * Copyright (c) 2019 m2049r - * Copyright (c) 2013 ChiaraHsieh - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.util.ledger; - -import java.math.BigInteger; -import java.security.spec.ECPoint; - -public class ECsecp256k1 { - static private final BigInteger TWO = new BigInteger("2"); - static private final BigInteger THREE = new BigInteger("3"); - static public final BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16); - static public final BigInteger a = new BigInteger("0000000000000000000000000000000000000000000000000000000000000000", 16); - static public final BigInteger b = new BigInteger("0000000000000000000000000000000000000000000000000000000000000007", 16); - static public final BigInteger n = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16); - static public final ECPoint G = new ECPoint( - new BigInteger("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16), - new BigInteger("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16)); - - public static ECPoint scalmult(BigInteger kin, ECPoint P) { - ECPoint R = ECPoint.POINT_INFINITY, S = P; - BigInteger k = kin.mod(n); // not necessary b/c that's how curves work - int length = k.bitLength(); - byte[] binarray = new byte[length]; - for (int i = 0; i <= length - 1; i++) { - binarray[i] = k.mod(TWO).byteValue(); - k = k.divide(TWO); - } - for (int i = length - 1; i >= 0; i--) { - // i should start at length-1 not -2 because the MSB of binary may not be 1 - R = doublePoint(R); - if (binarray[i] == 1) - R = addPoint(R, S); - } - return R; - } - - public static ECPoint addPoint(ECPoint r, ECPoint s) { - if (r.equals(s)) - return doublePoint(r); - else if (r.equals(ECPoint.POINT_INFINITY)) - return s; - else if (s.equals(ECPoint.POINT_INFINITY)) - return r; - BigInteger slope = (r.getAffineY().subtract(s.getAffineY())) - .multiply(r.getAffineX().subtract(s.getAffineX()).modInverse(p)); - BigInteger Xout = (slope.modPow(TWO, p).subtract(r.getAffineX())).subtract(s.getAffineX()).mod(p); - BigInteger Yout = s.getAffineY().negate().add(slope.multiply(s.getAffineX().subtract(Xout))).mod(p); - return new ECPoint(Xout, Yout); - } - - public static ECPoint doublePoint(ECPoint r) { - if (r.equals(ECPoint.POINT_INFINITY)) - return r; - BigInteger slope = (r.getAffineX().pow(2)).multiply(THREE).add(a) - .multiply((r.getAffineY().multiply(TWO)).modInverse(p)); - BigInteger Xout = slope.pow(2).subtract(r.getAffineX().multiply(TWO)).mod(p); - BigInteger Yout = (r.getAffineY().negate()).add(slope.multiply(r.getAffineX().subtract(Xout))).mod(p); - return new ECPoint(Xout, Yout); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/ledger/Monero.java b/app/src/main/java/com/m2049r/xmrwallet/util/ledger/Monero.java deleted file mode 100644 index f4a3fdd..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/ledger/Monero.java +++ /dev/null @@ -1,1866 +0,0 @@ -/* - * A quick and hacky Java implementation of most of - * https://github.com/LedgerHQ/ledger-app-monero/blob/master/tools/python/src/ledger/monero/seedconv.py - * - * Copyright (c) 2019 m2049r - * Copyright 2018 Cedric Mesnil , Ledger SAS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.util.ledger; - -import com.theromus.sha.Keccak; -import com.theromus.sha.Parameters; - -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.spec.ECPoint; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.KeySpec; -import java.text.Normalizer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.zip.CRC32; - -import javax.crypto.Mac; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; - -import timber.log.Timber; - -public class Monero { - - static public String convert(String mnemonic, String passphrase) { - String[] words = mnemonic.toLowerCase().split("\\s"); - StringBuilder normalizedMnemonic = new StringBuilder(); - int wordCount = 0; - for (String word : words) { - if (word.length() == 0) continue; - if (wordCount > 0) normalizedMnemonic.append(" "); - wordCount++; - normalizedMnemonic.append(word); - } - if ((wordCount != 12) && (wordCount != 24) && (wordCount != 18)) return null; - Monero seed = new Monero(); - try { - return seed.getMnemonic(normalizedMnemonic.toString(), passphrase); - } catch (IllegalStateException | IllegalArgumentException ex) { - return null; - } - } - - private byte[] seed; - private byte[] mkey; - private byte[] mchain; - private byte[] monero_ki; - private byte[] monero_ci; - private byte[] view_key; - private byte[] spend_key; - - private static byte[] NKFDbytes(String str) { - return Normalizer.normalize(str, Normalizer.Form.NFKD).getBytes(); - } - - private static char[] NKFDchars(String str) { - return Normalizer.normalize(str, Normalizer.Form.NFKD).toCharArray(); - } - - private static byte[] fixByteArray32(byte[] b) { - if ((b.length > 33)) throw new IllegalStateException(); - if ((b.length == 33) && (b[0] != 0)) throw new IllegalStateException(); - if (b.length == 33) - return Arrays.copyOfRange(b, 1, 33); - else - return b; - } - - private static byte[] intToBytes(int i, int bytes) { - ByteBuffer buffer = ByteBuffer.allocate(bytes); - buffer.putInt(i); - return buffer.array(); - } - - private static void reverse(byte[] b) { - for (int i = 0; i < b.length / 2; i++) { - byte temp = b[i]; - b[i] = b[b.length - i - 1]; - b[b.length - i - 1] = temp; - } - } - - private void derive(String path) - throws NoSuchAlgorithmException, InvalidKeyException { - byte[] kpar = Arrays.copyOf(mkey, 32); - byte[] cpar = Arrays.copyOf(mchain, 32); - - String[] pathSegments = path.split("/"); - if (!pathSegments[0].equals("m")) - throw new IllegalArgumentException("Path must start with 'm'"); - for (int i = 1; i < pathSegments.length; i++) { - String child = pathSegments[i]; - boolean hardened = child.charAt(child.length() - 1) == '\''; - - byte[] data = new byte[33 + 4]; - if (hardened) { - int c = Integer.parseInt(child.substring(0, child.length() - 1)); - c += 0x80000000; - data[0] = 0; - System.arraycopy(kpar, 0, data, 1, kpar.length); - System.arraycopy(intToBytes(c, 4), 0, data, 1 + kpar.length, 4); - } else { - int c = Integer.parseInt(child); - BigInteger k = new BigInteger(1, kpar); - ECPoint kG = ECsecp256k1.scalmult(k, ECsecp256k1.G); - byte[] xBytes = fixByteArray32(kG.getAffineX().toByteArray()); - byte[] Wpar = new byte[33]; - System.arraycopy(xBytes, 0, Wpar, 33 - xBytes.length, xBytes.length); - byte[] yBytes = fixByteArray32(kG.getAffineY().toByteArray()); - if ((yBytes[yBytes.length - 1] & 1) == 0) - Wpar[0] = 0x02; - else - Wpar[0] = 0x03; - System.arraycopy(Wpar, 0, data, 0, Wpar.length); - System.arraycopy(intToBytes(c, 4), 0, data, Wpar.length, 4); - } - - SecretKeySpec keySpec = new SecretKeySpec(cpar, "HmacSHA512"); - Mac mac = Mac.getInstance("HmacSHA512"); - mac.init(keySpec); - byte[] I = mac.doFinal(data); - BigInteger Il = new BigInteger(1, Arrays.copyOfRange(I, 0, 32)); - BigInteger kparInt = new BigInteger(1, kpar); - Il = Il.add(kparInt).mod(ECsecp256k1.n); - byte[] IlBytes = fixByteArray32(Il.toByteArray()); - kpar = new byte[32]; - System.arraycopy(IlBytes, 0, kpar, 0, 32); - System.arraycopy(I, 32, cpar, 0, I.length - 32); - } - monero_ki = kpar; - monero_ci = cpar; - } - - private void makeSeed(String mnemonic, String passphrase) - throws NoSuchAlgorithmException, InvalidKeySpecException { - SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512"); - KeySpec spec = new PBEKeySpec(NKFDchars(mnemonic), NKFDbytes("mnemonic" + passphrase), 2048, 512); - seed = skf.generateSecret(spec).getEncoded(); - } - - private void makeMasterKey() - throws NoSuchAlgorithmException, InvalidKeyException { - SecretKeySpec keySpec = new SecretKeySpec( - NKFDbytes("Bitcoin seed"), - "HmacSHA512"); - Mac mac = Mac.getInstance("HmacSHA512"); - mac.init(keySpec); - byte[] result = mac.doFinal(seed); - mkey = Arrays.copyOfRange(result, 0, 32); - mchain = Arrays.copyOfRange(result, 32, 64); - } - - private void makeKeys() { - BigInteger l = new BigInteger("1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed", 16); - Keccak keccak = new Keccak(); - final byte[] b = keccak.getHash(monero_ki, Parameters.KECCAK_256); - reverse(b); - BigInteger ble = new BigInteger(1, b).mod(l); - spend_key = fixByteArray32(ble.toByteArray()); - reverse(spend_key); - byte[] a = keccak.getHash(spend_key, Parameters.KECCAK_256); - reverse(a); - BigInteger ale = new BigInteger(1, a).mod(l); - view_key = fixByteArray32(ale.toByteArray()); - reverse(view_key); - } - - private String getWords() { - if (spend_key.length != 32) throw new IllegalArgumentException(); - String[] wordList = ENGLISH_WORDS; - List words = new ArrayList<>(); - for (int i = 0; i < spend_key.length / 4; i++) { - long val = ((long) (spend_key[i * 4 + 0] & 0xff) << 0) | - ((long) (spend_key[i * 4 + 1] & 0xff) << 8) | - ((long) (spend_key[i * 4 + 2] & 0xff) << 16) | - ((long) (spend_key[i * 4 + 3] & 0xff) << 24); - long w1 = val % wordList.length; - long w2 = ((val / wordList.length) + w1) % wordList.length; - long w3 = (((val / wordList.length) / wordList.length) + w2) % wordList.length; - - words.add(wordList[(int) w1]); - words.add(wordList[(int) w2]); - words.add(wordList[(int) w3]); - } - - StringBuilder mnemonic = new StringBuilder(); - StringBuilder trimmedWords = new StringBuilder(); - - for (String word : words) { - mnemonic.append(word).append(" "); - trimmedWords.append(word.substring(0, ENGLISH_PREFIX_LENGTH)); - } - CRC32 crc32 = new CRC32(); - crc32.update(trimmedWords.toString().getBytes(StandardCharsets.UTF_8)); - long checksum = crc32.getValue(); - mnemonic.append(words.get((int) (checksum % 24))); - return mnemonic.toString(); - } - - private String getMnemonic(String ledgerMnemonic, String passphrase) { - try { - makeSeed(ledgerMnemonic, passphrase); - makeMasterKey(); - derive("m/44'/128'/0'/0/0"); - makeKeys(); - return getWords(); - } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException ex) { - Timber.e(ex); - } - return null; - } - - public static final int ENGLISH_PREFIX_LENGTH = 3; - public static final String[] ENGLISH_WORDS = { - "abbey", - "abducts", - "ability", - "ablaze", - "abnormal", - "abort", - "abrasive", - "absorb", - "abyss", - "academy", - "aces", - "aching", - "acidic", - "acoustic", - "acquire", - "across", - "actress", - "acumen", - "adapt", - "addicted", - "adept", - "adhesive", - "adjust", - "adopt", - "adrenalin", - "adult", - "adventure", - "aerial", - "afar", - "affair", - "afield", - "afloat", - "afoot", - "afraid", - "after", - "against", - "agenda", - "aggravate", - "agile", - "aglow", - "agnostic", - "agony", - "agreed", - "ahead", - "aided", - "ailments", - "aimless", - "airport", - "aisle", - "ajar", - "akin", - "alarms", - "album", - "alchemy", - "alerts", - "algebra", - "alkaline", - "alley", - "almost", - "aloof", - "alpine", - "already", - "also", - "altitude", - "alumni", - "always", - "amaze", - "ambush", - "amended", - "amidst", - "ammo", - "amnesty", - "among", - "amply", - "amused", - "anchor", - "android", - "anecdote", - "angled", - "ankle", - "annoyed", - "answers", - "antics", - "anvil", - "anxiety", - "anybody", - "apart", - "apex", - "aphid", - "aplomb", - "apology", - "apply", - "apricot", - "aptitude", - "aquarium", - "arbitrary", - "archer", - "ardent", - "arena", - "argue", - "arises", - "army", - "around", - "arrow", - "arsenic", - "artistic", - "ascend", - "ashtray", - "aside", - "asked", - "asleep", - "aspire", - "assorted", - "asylum", - "athlete", - "atlas", - "atom", - "atrium", - "attire", - "auburn", - "auctions", - "audio", - "august", - "aunt", - "austere", - "autumn", - "avatar", - "avidly", - "avoid", - "awakened", - "awesome", - "awful", - "awkward", - "awning", - "awoken", - "axes", - "axis", - "axle", - "aztec", - "azure", - "baby", - "bacon", - "badge", - "baffles", - "bagpipe", - "bailed", - "bakery", - "balding", - "bamboo", - "banjo", - "baptism", - "basin", - "batch", - "bawled", - "bays", - "because", - "beer", - "befit", - "begun", - "behind", - "being", - "below", - "bemused", - "benches", - "berries", - "bested", - "betting", - "bevel", - "beware", - "beyond", - "bias", - "bicycle", - "bids", - "bifocals", - "biggest", - "bikini", - "bimonthly", - "binocular", - "biology", - "biplane", - "birth", - "biscuit", - "bite", - "biweekly", - "blender", - "blip", - "bluntly", - "boat", - "bobsled", - "bodies", - "bogeys", - "boil", - "boldly", - "bomb", - "border", - "boss", - "both", - "bounced", - "bovine", - "bowling", - "boxes", - "boyfriend", - "broken", - "brunt", - "bubble", - "buckets", - "budget", - "buffet", - "bugs", - "building", - "bulb", - "bumper", - "bunch", - "business", - "butter", - "buying", - "buzzer", - "bygones", - "byline", - "bypass", - "cabin", - "cactus", - "cadets", - "cafe", - "cage", - "cajun", - "cake", - "calamity", - "camp", - "candy", - "casket", - "catch", - "cause", - "cavernous", - "cease", - "cedar", - "ceiling", - "cell", - "cement", - "cent", - "certain", - "chlorine", - "chrome", - "cider", - "cigar", - "cinema", - "circle", - "cistern", - "citadel", - "civilian", - "claim", - "click", - "clue", - "coal", - "cobra", - "cocoa", - "code", - "coexist", - "coffee", - "cogs", - "cohesive", - "coils", - "colony", - "comb", - "cool", - "copy", - "corrode", - "costume", - "cottage", - "cousin", - "cowl", - "criminal", - "cube", - "cucumber", - "cuddled", - "cuffs", - "cuisine", - "cunning", - "cupcake", - "custom", - "cycling", - "cylinder", - "cynical", - "dabbing", - "dads", - "daft", - "dagger", - "daily", - "damp", - "dangerous", - "dapper", - "darted", - "dash", - "dating", - "dauntless", - "dawn", - "daytime", - "dazed", - "debut", - "decay", - "dedicated", - "deepest", - "deftly", - "degrees", - "dehydrate", - "deity", - "dejected", - "delayed", - "demonstrate", - "dented", - "deodorant", - "depth", - "desk", - "devoid", - "dewdrop", - "dexterity", - "dialect", - "dice", - "diet", - "different", - "digit", - "dilute", - "dime", - "dinner", - "diode", - "diplomat", - "directed", - "distance", - "ditch", - "divers", - "dizzy", - "doctor", - "dodge", - "does", - "dogs", - "doing", - "dolphin", - "domestic", - "donuts", - "doorway", - "dormant", - "dosage", - "dotted", - "double", - "dove", - "down", - "dozen", - "dreams", - "drinks", - "drowning", - "drunk", - "drying", - "dual", - "dubbed", - "duckling", - "dude", - "duets", - "duke", - "dullness", - "dummy", - "dunes", - "duplex", - "duration", - "dusted", - "duties", - "dwarf", - "dwelt", - "dwindling", - "dying", - "dynamite", - "dyslexic", - "each", - "eagle", - "earth", - "easy", - "eating", - "eavesdrop", - "eccentric", - "echo", - "eclipse", - "economics", - "ecstatic", - "eden", - "edgy", - "edited", - "educated", - "eels", - "efficient", - "eggs", - "egotistic", - "eight", - "either", - "eject", - "elapse", - "elbow", - "eldest", - "eleven", - "elite", - "elope", - "else", - "eluded", - "emails", - "ember", - "emerge", - "emit", - "emotion", - "empty", - "emulate", - "energy", - "enforce", - "enhanced", - "enigma", - "enjoy", - "enlist", - "enmity", - "enough", - "enraged", - "ensign", - "entrance", - "envy", - "epoxy", - "equip", - "erase", - "erected", - "erosion", - "error", - "eskimos", - "espionage", - "essential", - "estate", - "etched", - "eternal", - "ethics", - "etiquette", - "evaluate", - "evenings", - "evicted", - "evolved", - "examine", - "excess", - "exhale", - "exit", - "exotic", - "exquisite", - "extra", - "exult", - "fabrics", - "factual", - "fading", - "fainted", - "faked", - "fall", - "family", - "fancy", - "farming", - "fatal", - "faulty", - "fawns", - "faxed", - "fazed", - "feast", - "february", - "federal", - "feel", - "feline", - "females", - "fences", - "ferry", - "festival", - "fetches", - "fever", - "fewest", - "fiat", - "fibula", - "fictional", - "fidget", - "fierce", - "fifteen", - "fight", - "films", - "firm", - "fishing", - "fitting", - "five", - "fixate", - "fizzle", - "fleet", - "flippant", - "flying", - "foamy", - "focus", - "foes", - "foggy", - "foiled", - "folding", - "fonts", - "foolish", - "fossil", - "fountain", - "fowls", - "foxes", - "foyer", - "framed", - "friendly", - "frown", - "fruit", - "frying", - "fudge", - "fuel", - "fugitive", - "fully", - "fuming", - "fungal", - "furnished", - "fuselage", - "future", - "fuzzy", - "gables", - "gadget", - "gags", - "gained", - "galaxy", - "gambit", - "gang", - "gasp", - "gather", - "gauze", - "gave", - "gawk", - "gaze", - "gearbox", - "gecko", - "geek", - "gels", - "gemstone", - "general", - "geometry", - "germs", - "gesture", - "getting", - "geyser", - "ghetto", - "ghost", - "giant", - "giddy", - "gifts", - "gigantic", - "gills", - "gimmick", - "ginger", - "girth", - "giving", - "glass", - "gleeful", - "glide", - "gnaw", - "gnome", - "goat", - "goblet", - "godfather", - "goes", - "goggles", - "going", - "goldfish", - "gone", - "goodbye", - "gopher", - "gorilla", - "gossip", - "gotten", - "gourmet", - "governing", - "gown", - "greater", - "grunt", - "guarded", - "guest", - "guide", - "gulp", - "gumball", - "gur", - "gusts", - "gutter", - "guys", - "gymnast", - "gypsy", - "gyrate", - "habitat", - "hacksaw", - "haggled", - "hairy", - "hamburger", - "happens", - "hashing", - "hatchet", - "haunted", - "having", - "hawk", - "haystack", - "hazard", - "hectare", - "hedgehog", - "heels", - "hefty", - "height", - "hemlock", - "hence", - "heron", - "hesitate", - "hexagon", - "hickory", - "hiding", - "highway", - "hijack", - "hiker", - "hills", - "himself", - "hinder", - "hippo", - "hire", - "history", - "hitched", - "hive", - "hoax", - "hobby", - "hockey", - "hoisting", - "hold", - "honked", - "hookup", - "hope", - "hornet", - "hospital", - "hotel", - "hounded", - "hover", - "howls", - "hubcaps", - "huddle", - "huge", - "hull", - "humid", - "hunter", - "hurried", - "husband", - "huts", - "hybrid", - "hydrogen", - "hyper", - "iceberg", - "icing", - "icon", - "identity", - "idiom", - "idled", - "idols", - "igloo", - "ignore", - "iguana", - "illness", - "imagine", - "imbalance", - "imitate", - "impel", - "inactive", - "inbound", - "incur", - "industrial", - "inexact", - "inflamed", - "ingested", - "initiate", - "injury", - "inkling", - "inline", - "inmate", - "innocent", - "inorganic", - "input", - "inquest", - "inroads", - "insult", - "intended", - "inundate", - "invoke", - "inwardly", - "ionic", - "irate", - "iris", - "irony", - "irritate", - "island", - "isolated", - "issued", - "italics", - "itches", - "items", - "itinerary", - "itself", - "ivory", - "jabbed", - "jackets", - "jaded", - "jagged", - "jailed", - "jamming", - "january", - "jargon", - "jaunt", - "javelin", - "jaws", - "jazz", - "jeans", - "jeers", - "jellyfish", - "jeopardy", - "jerseys", - "jester", - "jetting", - "jewels", - "jigsaw", - "jingle", - "jittery", - "jive", - "jobs", - "jockey", - "jogger", - "joining", - "joking", - "jolted", - "jostle", - "journal", - "joyous", - "jubilee", - "judge", - "juggled", - "juicy", - "jukebox", - "july", - "jump", - "junk", - "jury", - "justice", - "juvenile", - "kangaroo", - "karate", - "keep", - "kennel", - "kept", - "kernels", - "kettle", - "keyboard", - "kickoff", - "kidneys", - "king", - "kiosk", - "kisses", - "kitchens", - "kiwi", - "knapsack", - "knee", - "knife", - "knowledge", - "knuckle", - "koala", - "laboratory", - "ladder", - "lagoon", - "lair", - "lakes", - "lamb", - "language", - "laptop", - "large", - "last", - "later", - "launching", - "lava", - "lawsuit", - "layout", - "lazy", - "lectures", - "ledge", - "leech", - "left", - "legion", - "leisure", - "lemon", - "lending", - "leopard", - "lesson", - "lettuce", - "lexicon", - "liar", - "library", - "licks", - "lids", - "lied", - "lifestyle", - "light", - "likewise", - "lilac", - "limits", - "linen", - "lion", - "lipstick", - "liquid", - "listen", - "lively", - "loaded", - "lobster", - "locker", - "lodge", - "lofty", - "logic", - "loincloth", - "long", - "looking", - "lopped", - "lordship", - "losing", - "lottery", - "loudly", - "love", - "lower", - "loyal", - "lucky", - "luggage", - "lukewarm", - "lullaby", - "lumber", - "lunar", - "lurk", - "lush", - "luxury", - "lymph", - "lynx", - "lyrics", - "macro", - "madness", - "magically", - "mailed", - "major", - "makeup", - "malady", - "mammal", - "maps", - "masterful", - "match", - "maul", - "maverick", - "maximum", - "mayor", - "maze", - "meant", - "mechanic", - "medicate", - "meeting", - "megabyte", - "melting", - "memoir", - "men", - "merger", - "mesh", - "metro", - "mews", - "mice", - "midst", - "mighty", - "mime", - "mirror", - "misery", - "mittens", - "mixture", - "moat", - "mobile", - "mocked", - "mohawk", - "moisture", - "molten", - "moment", - "money", - "moon", - "mops", - "morsel", - "mostly", - "motherly", - "mouth", - "movement", - "mowing", - "much", - "muddy", - "muffin", - "mugged", - "mullet", - "mumble", - "mundane", - "muppet", - "mural", - "musical", - "muzzle", - "myriad", - "mystery", - "myth", - "nabbing", - "nagged", - "nail", - "names", - "nanny", - "napkin", - "narrate", - "nasty", - "natural", - "nautical", - "navy", - "nearby", - "necklace", - "needed", - "negative", - "neither", - "neon", - "nephew", - "nerves", - "nestle", - "network", - "neutral", - "never", - "newt", - "nexus", - "nibs", - "niche", - "niece", - "nifty", - "nightly", - "nimbly", - "nineteen", - "nirvana", - "nitrogen", - "nobody", - "nocturnal", - "nodes", - "noises", - "nomad", - "noodles", - "northern", - "nostril", - "noted", - "nouns", - "novelty", - "nowhere", - "nozzle", - "nuance", - "nucleus", - "nudged", - "nugget", - "nuisance", - "null", - "number", - "nuns", - "nurse", - "nutshell", - "nylon", - "oaks", - "oars", - "oasis", - "oatmeal", - "obedient", - "object", - "obliged", - "obnoxious", - "observant", - "obtains", - "obvious", - "occur", - "ocean", - "october", - "odds", - "odometer", - "offend", - "often", - "oilfield", - "ointment", - "okay", - "older", - "olive", - "olympics", - "omega", - "omission", - "omnibus", - "onboard", - "oncoming", - "oneself", - "ongoing", - "onion", - "online", - "onslaught", - "onto", - "onward", - "oozed", - "opacity", - "opened", - "opposite", - "optical", - "opus", - "orange", - "orbit", - "orchid", - "orders", - "organs", - "origin", - "ornament", - "orphans", - "oscar", - "ostrich", - "otherwise", - "otter", - "ouch", - "ought", - "ounce", - "ourselves", - "oust", - "outbreak", - "oval", - "oven", - "owed", - "owls", - "owner", - "oxidant", - "oxygen", - "oyster", - "ozone", - "pact", - "paddles", - "pager", - "pairing", - "palace", - "pamphlet", - "pancakes", - "paper", - "paradise", - "pastry", - "patio", - "pause", - "pavements", - "pawnshop", - "payment", - "peaches", - "pebbles", - "peculiar", - "pedantic", - "peeled", - "pegs", - "pelican", - "pencil", - "people", - "pepper", - "perfect", - "pests", - "petals", - "phase", - "pheasants", - "phone", - "phrases", - "physics", - "piano", - "picked", - "pierce", - "pigment", - "piloted", - "pimple", - "pinched", - "pioneer", - "pipeline", - "pirate", - "pistons", - "pitched", - "pivot", - "pixels", - "pizza", - "playful", - "pledge", - "pliers", - "plotting", - "plus", - "plywood", - "poaching", - "pockets", - "podcast", - "poetry", - "point", - "poker", - "polar", - "ponies", - "pool", - "popular", - "portents", - "possible", - "potato", - "pouch", - "poverty", - "powder", - "pram", - "present", - "pride", - "problems", - "pruned", - "prying", - "psychic", - "public", - "puck", - "puddle", - "puffin", - "pulp", - "pumpkins", - "punch", - "puppy", - "purged", - "push", - "putty", - "puzzled", - "pylons", - "pyramid", - "python", - "queen", - "quick", - "quote", - "rabbits", - "racetrack", - "radar", - "rafts", - "rage", - "railway", - "raking", - "rally", - "ramped", - "randomly", - "rapid", - "rarest", - "rash", - "rated", - "ravine", - "rays", - "razor", - "react", - "rebel", - "recipe", - "reduce", - "reef", - "refer", - "regular", - "reheat", - "reinvest", - "rejoices", - "rekindle", - "relic", - "remedy", - "renting", - "reorder", - "repent", - "request", - "reruns", - "rest", - "return", - "reunion", - "revamp", - "rewind", - "rhino", - "rhythm", - "ribbon", - "richly", - "ridges", - "rift", - "rigid", - "rims", - "ringing", - "riots", - "ripped", - "rising", - "ritual", - "river", - "roared", - "robot", - "rockets", - "rodent", - "rogue", - "roles", - "romance", - "roomy", - "roped", - "roster", - "rotate", - "rounded", - "rover", - "rowboat", - "royal", - "ruby", - "rudely", - "ruffled", - "rugged", - "ruined", - "ruling", - "rumble", - "runway", - "rural", - "rustled", - "ruthless", - "sabotage", - "sack", - "sadness", - "safety", - "saga", - "sailor", - "sake", - "salads", - "sample", - "sanity", - "sapling", - "sarcasm", - "sash", - "satin", - "saucepan", - "saved", - "sawmill", - "saxophone", - "sayings", - "scamper", - "scenic", - "school", - "science", - "scoop", - "scrub", - "scuba", - "seasons", - "second", - "sedan", - "seeded", - "segments", - "seismic", - "selfish", - "semifinal", - "sensible", - "september", - "sequence", - "serving", - "session", - "setup", - "seventh", - "sewage", - "shackles", - "shelter", - "shipped", - "shocking", - "shrugged", - "shuffled", - "shyness", - "siblings", - "sickness", - "sidekick", - "sieve", - "sifting", - "sighting", - "silk", - "simplest", - "sincerely", - "sipped", - "siren", - "situated", - "sixteen", - "sizes", - "skater", - "skew", - "skirting", - "skulls", - "skydive", - "slackens", - "sleepless", - "slid", - "slower", - "slug", - "smash", - "smelting", - "smidgen", - "smog", - "smuggled", - "snake", - "sneeze", - "sniff", - "snout", - "snug", - "soapy", - "sober", - "soccer", - "soda", - "software", - "soggy", - "soil", - "solved", - "somewhere", - "sonic", - "soothe", - "soprano", - "sorry", - "southern", - "sovereign", - "sowed", - "soya", - "space", - "speedy", - "sphere", - "spiders", - "splendid", - "spout", - "sprig", - "spud", - "spying", - "square", - "stacking", - "stellar", - "stick", - "stockpile", - "strained", - "stunning", - "stylishly", - "subtly", - "succeed", - "suddenly", - "suede", - "suffice", - "sugar", - "suitcase", - "sulking", - "summon", - "sunken", - "superior", - "surfer", - "sushi", - "suture", - "swagger", - "swept", - "swiftly", - "sword", - "swung", - "syllabus", - "symptoms", - "syndrome", - "syringe", - "system", - "taboo", - "tacit", - "tadpoles", - "tagged", - "tail", - "taken", - "talent", - "tamper", - "tanks", - "tapestry", - "tarnished", - "tasked", - "tattoo", - "taunts", - "tavern", - "tawny", - "taxi", - "teardrop", - "technical", - "tedious", - "teeming", - "tell", - "template", - "tender", - "tepid", - "tequila", - "terminal", - "testing", - "tether", - "textbook", - "thaw", - "theatrics", - "thirsty", - "thorn", - "threaten", - "thumbs", - "thwart", - "ticket", - "tidy", - "tiers", - "tiger", - "tilt", - "timber", - "tinted", - "tipsy", - "tirade", - "tissue", - "titans", - "toaster", - "tobacco", - "today", - "toenail", - "toffee", - "together", - "toilet", - "token", - "tolerant", - "tomorrow", - "tonic", - "toolbox", - "topic", - "torch", - "tossed", - "total", - "touchy", - "towel", - "toxic", - "toyed", - "trash", - "trendy", - "tribal", - "trolling", - "truth", - "trying", - "tsunami", - "tubes", - "tucks", - "tudor", - "tuesday", - "tufts", - "tugs", - "tuition", - "tulips", - "tumbling", - "tunnel", - "turnip", - "tusks", - "tutor", - "tuxedo", - "twang", - "tweezers", - "twice", - "twofold", - "tycoon", - "typist", - "tyrant", - "ugly", - "ulcers", - "ultimate", - "umbrella", - "umpire", - "unafraid", - "unbending", - "uncle", - "under", - "uneven", - "unfit", - "ungainly", - "unhappy", - "union", - "unjustly", - "unknown", - "unlikely", - "unmask", - "unnoticed", - "unopened", - "unplugs", - "unquoted", - "unrest", - "unsafe", - "until", - "unusual", - "unveil", - "unwind", - "unzip", - "upbeat", - "upcoming", - "update", - "upgrade", - "uphill", - "upkeep", - "upload", - "upon", - "upper", - "upright", - "upstairs", - "uptight", - "upwards", - "urban", - "urchins", - "urgent", - "usage", - "useful", - "usher", - "using", - "usual", - "utensils", - "utility", - "utmost", - "utopia", - "uttered", - "vacation", - "vague", - "vain", - "value", - "vampire", - "vane", - "vapidly", - "vary", - "vastness", - "vats", - "vaults", - "vector", - "veered", - "vegan", - "vehicle", - "vein", - "velvet", - "venomous", - "verification", - "vessel", - "veteran", - "vexed", - "vials", - "vibrate", - "victim", - "video", - "viewpoint", - "vigilant", - "viking", - "village", - "vinegar", - "violin", - "vipers", - "virtual", - "visited", - "vitals", - "vivid", - "vixen", - "vocal", - "vogue", - "voice", - "volcano", - "vortex", - "voted", - "voucher", - "vowels", - "voyage", - "vulture", - "wade", - "waffle", - "wagtail", - "waist", - "waking", - "wallets", - "wanted", - "warped", - "washing", - "water", - "waveform", - "waxing", - "wayside", - "weavers", - "website", - "wedge", - "weekday", - "weird", - "welders", - "went", - "wept", - "were", - "western", - "wetsuit", - "whale", - "when", - "whipped", - "whole", - "wickets", - "width", - "wield", - "wife", - "wiggle", - "wildly", - "winter", - "wipeout", - "wiring", - "wise", - "withdrawn", - "wives", - "wizard", - "wobbly", - "woes", - "woken", - "wolf", - "womanly", - "wonders", - "woozy", - "worry", - "wounded", - "woven", - "wrap", - "wrist", - "wrong", - "yacht", - "yahoo", - "yanks", - "yard", - "yawning", - "yearbook", - "yellow", - "yesterday", - "yeti", - "yields", - "yodel", - "yoga", - "younger", - "yoyo", - "zapped", - "zeal", - "zebra", - "zero", - "zesty", - "zigzags", - "zinger", - "zippers", - "zodiac", - "zombie", - "zones", - "zoom" - }; -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/validator/BitcoinAddressType.java b/app/src/main/java/com/m2049r/xmrwallet/util/validator/BitcoinAddressType.java deleted file mode 100644 index a1415b1..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/validator/BitcoinAddressType.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.m2049r.xmrwallet.util.validator; - -import lombok.Getter; - -public enum BitcoinAddressType { - BTC(Type.BTC, Type.BTC_BECH32_PREFIX), - LTC(Type.LTC, Type.LTC_BECH32_PREFIX), - DASH(Type.DASH, null), - DOGE(Type.DOGE, null); - - @Getter - private final byte[] production; - @Getter - private final byte[] testnet; - - @Getter - private final String productionBech32Prefix; - @Getter - private final String testnetBech32Prefix; - - public boolean hasBech32() { - return productionBech32Prefix != null; - } - - public String getBech32Prefix(boolean testnet) { - return testnet ? testnetBech32Prefix : productionBech32Prefix; - } - - BitcoinAddressType(byte[][] types, String[] bech32Prefix) { - production = types[0]; - testnet = types[1]; - if (bech32Prefix != null) { - productionBech32Prefix = bech32Prefix[0]; - testnetBech32Prefix = bech32Prefix[1]; - } else { - productionBech32Prefix = null; - testnetBech32Prefix = null; - } - } - - // Java is silly and doesn't allow array initializers in the construction - private static class Type { - private static final byte[][] BTC = {{0x00, 0x05}, {0x6f, (byte) 0xc4}}; - private static final String[] BTC_BECH32_PREFIX = {"bc", "tb"}; - private static final byte[][] LTC = {{0x30, 0x05, 0x32}, {0x6f, (byte) 0xc4, 0x3a}}; - private static final String[] LTC_BECH32_PREFIX = {"ltc", "tltc"}; - private static final byte[][] DASH = {{0x4c, 0x10}, {(byte) 0x8c, 0x13}}; - private static final byte[][] DOGE = {{0x1e, 0x16}, {0x71, (byte) 0xc4}}; - } - -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/validator/BitcoinAddressValidator.java b/app/src/main/java/com/m2049r/xmrwallet/util/validator/BitcoinAddressValidator.java deleted file mode 100644 index 5759089..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/validator/BitcoinAddressValidator.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) 2017 m2049r er al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.util.validator; - -// mostly based on https://rosettacode.org/wiki/Bitcoin/address_validation#Java - -import com.m2049r.xmrwallet.data.Crypto; -import com.m2049r.xmrwallet.model.NetworkType; -import com.m2049r.xmrwallet.model.WalletManager; - -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -public class BitcoinAddressValidator { - private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; - - public static Crypto validate(String address) { - for (BitcoinAddressType type : BitcoinAddressType.values()) { - if (validate(address, type)) - return Crypto.valueOf(type.name()); - } - return null; - } - - // just for tests - public static boolean validateBTC(String addrress, boolean testnet) { - return validate(addrress, BitcoinAddressType.BTC, testnet); - } - - public static boolean validate(String addrress, BitcoinAddressType type, boolean testnet) { - if (validate(addrress, testnet ? type.getTestnet() : type.getProduction())) - return true; - if (type.hasBech32()) - return validateBech32Segwit(addrress, type, testnet); - else - return false; - } - - public static boolean validate(String addrress, BitcoinAddressType type) { - final boolean testnet = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet; - return validate(addrress, type, testnet); - } - - public static boolean validate(String addrress, byte[] addressTypes) { - if (addrress.length() < 26 || addrress.length() > 35) - return false; - byte[] decoded = decodeBase58To25Bytes(addrress); - if (decoded == null) - return false; - int v = decoded[0] & 0xFF; - boolean nok = true; - for (byte b : addressTypes) { - nok = nok && (v != (b & 0xFF)); - } - if (nok) return false; - - byte[] hash1 = sha256(Arrays.copyOfRange(decoded, 0, 21)); - byte[] hash2 = sha256(hash1); - - return Arrays.equals(Arrays.copyOfRange(hash2, 0, 4), Arrays.copyOfRange(decoded, 21, 25)); - } - - private static byte[] decodeBase58To25Bytes(String input) { - BigInteger num = BigInteger.ZERO; - for (char t : input.toCharArray()) { - int p = ALPHABET.indexOf(t); - if (p == -1) - return null; - num = num.multiply(BigInteger.valueOf(58)).add(BigInteger.valueOf(p)); - } - - byte[] result = new byte[25]; - byte[] numBytes = num.toByteArray(); - if (num.bitLength() > 200) return null; - - if (num.bitLength() == 200) { - System.arraycopy(numBytes, 1, result, 0, 25); - } else { - System.arraycopy(numBytes, 0, result, result.length - numBytes.length, numBytes.length); - } - return result; - } - - private static byte[] sha256(byte[] data) { - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(data); - return md.digest(); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException(e); - } - } - - // - // validate Bech32 segwit - // see https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki for spec - // - - private static final String DATA_CHARS = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; - - public static boolean validateBech32Segwit(String bech32, BitcoinAddressType type, boolean testnet) { - if (!bech32.equals(bech32.toLowerCase()) && !bech32.equals(bech32.toUpperCase())) { - return false; // mixing upper and lower case not allowed - } - bech32 = bech32.toLowerCase(); - - if (!bech32.startsWith(type.getBech32Prefix(testnet))) return false; - - final int hrpLength = type.getBech32Prefix(testnet).length(); - - if ((bech32.length() < (12 + hrpLength)) || (bech32.length() > (72 + hrpLength))) - return false; - int mod = (bech32.length() - hrpLength) % 8; - if ((mod == 6) || (mod == 1) || (mod == 3)) return false; - - int sep = -1; - final byte[] bytes = bech32.getBytes(StandardCharsets.US_ASCII); - for (int i = 0; i < bytes.length; i++) { - if ((bytes[i] < 33) || (bytes[i] > 126)) { - return false; - } - if (bytes[i] == 49) sep = i; // 49 := '1' in ASCII - } - - if (sep != hrpLength) return false; - if (sep > bytes.length - 7) { - return false; // min 6 bytes data - } - if (bytes.length < 8) { // hrp{min}=1 + sep=1 + data{min}=6 := 8 - return false; // too short - } - if (bytes.length > 90) { - return false; // too long - } - - final byte[] hrp = Arrays.copyOfRange(bytes, 0, sep); - - final byte[] data = Arrays.copyOfRange(bytes, sep + 1, bytes.length); - for (int i = 0; i < data.length; i++) { - int b = DATA_CHARS.indexOf(data[i]); - if (b < 0) return false; // invalid character - data[i] = (byte) b; - } - - if (!validateBech32Data(data)) return false; - - return verifyChecksum(hrp, data); - } - - private static int polymod(byte[] values) { - final int[] GEN = {0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3}; - int chk = 1; - for (byte v : values) { - byte b = (byte) (chk >> 25); - chk = ((chk & 0x1ffffff) << 5) ^ v; - for (int i = 0; i < 5; i++) { - chk ^= ((b >> i) & 1) == 1 ? GEN[i] : 0; - } - } - return chk; - } - - private static byte[] hrpExpand(byte[] hrp) { - final byte[] expanded = new byte[(2 * hrp.length) + 1]; - int i = 0; - for (byte b : hrp) { - expanded[i++] = (byte) (b >> 5); - } - expanded[i++] = 0; - for (byte b : hrp) { - expanded[i++] = (byte) (b & 0x1f); - } - return expanded; - } - - private static boolean verifyChecksum(byte[] hrp, byte[] data) { - final byte[] hrpExpanded = hrpExpand(hrp); - final byte[] values = new byte[hrpExpanded.length + data.length]; - System.arraycopy(hrpExpanded, 0, values, 0, hrpExpanded.length); - System.arraycopy(data, 0, values, hrpExpanded.length, data.length); - return (polymod(values) == 1); - } - - private static boolean validateBech32Data(final byte[] data) { - if ((data[0] < 0) || (data[0] > 16)) return false; // witness version - final int programLength = data.length - 1 - 6; // 1-byte version at beginning & 6-byte checksum at end - - // since we are coming from our own decoder, we don't need to verify data is 5-bit bytes - - final int convertedSize = programLength * 5 / 8; - final int remainderSize = programLength * 5 % 8; - - if ((convertedSize < 2) || (convertedSize > 40)) return false; - - if ((data[0] == 0) && (convertedSize != 20) && (convertedSize != 32)) return false; - - if (remainderSize >= 5) return false; - // ignore checksum at end and get last byte of program - return (data[data.length - 1 - 6] & ((1 << remainderSize) - 1)) == 0; - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/validator/EthAddressValidator.java b/app/src/main/java/com/m2049r/xmrwallet/util/validator/EthAddressValidator.java deleted file mode 100644 index 3e4c476..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/validator/EthAddressValidator.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2017 m2049r er al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.util.validator; - -// mostly based on https://github.com/ognus/wallet-address-validator/blob/master/src/ethereum_validator.js - -import com.theromus.sha.Keccak; -import com.theromus.sha.Parameters; - -import java.nio.charset.StandardCharsets; -import java.util.regex.Pattern; - -public class EthAddressValidator { - static private final Pattern ETH_ADDRESS = Pattern.compile("^0x[0-9a-fA-F]{40}$"); - static private final Pattern ETH_ALLLOWER = Pattern.compile("^0x[0-9a-f]{40}$"); - static private final Pattern ETH_ALLUPPER = Pattern.compile("^0x[0-9A-F]{40}$"); - - public static boolean validate(String address) { - // Check if it has the basic requirements of an address - if (!ETH_ADDRESS.matcher(address).matches()) - return false; - - // If it's all small caps or all all caps, return true - if (ETH_ALLLOWER.matcher(address).matches() || ETH_ALLUPPER.matcher(address).matches()) { - return true; - } - - // Otherwise check each case - return validateChecksum(address); - } - - private static boolean validateChecksum(String address) { - // Check each case - address = address.substring(2); // strip 0x - - Keccak keccak = new Keccak(); - final byte[] addressHash = keccak.getHash( - address.toLowerCase().getBytes(StandardCharsets.US_ASCII), - Parameters.KECCAK_256); - for (int i = 0; i < 40; i++) { - boolean upper = (addressHash[i / 2] & ((i % 2) == 0 ? 128 : 8)) != 0; - char c = address.charAt(i); - if (Character.isAlphabetic(c)) { - if (Character.isUpperCase(c) && !upper) return false; - if (Character.isLowerCase(c) && upper) return false; - } - } - return true; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/theromus/sha/Keccak.java b/app/src/main/java/com/theromus/sha/Keccak.java deleted file mode 100644 index 0163a31..0000000 --- a/app/src/main/java/com/theromus/sha/Keccak.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.theromus.sha; - -import static com.theromus.utils.HexUtils.leftRotate64; -import static com.theromus.utils.HexUtils.convertToUint; -import static com.theromus.utils.HexUtils.convertFromLittleEndianTo64; -import static com.theromus.utils.HexUtils.convertFrom64ToLittleEndian; -import static java.lang.Math.min; -import static java.lang.System.arraycopy; -import static java.util.Arrays.fill; - -import java.io.ByteArrayOutputStream; -import java.math.BigInteger; - - -/** - * Keccak implementation. - * - * @author romus - */ -public class Keccak { - - private static BigInteger BIT_64 = new BigInteger("18446744073709551615"); - - /** - * Do hash. - * - * @param message input data - * @param parameter keccak param - * @return byte-array result - */ - public byte[] getHash(final byte[] message, final Parameters parameter) { - int[] uState = new int[200]; - int[] uMessage = convertToUint(message); - - - int rateInBytes = parameter.getRate() / 8; - int blockSize = 0; - int inputOffset = 0; - - // Absorbing phase - while (inputOffset < uMessage.length) { - blockSize = min(uMessage.length - inputOffset, rateInBytes); - for (int i = 0; i < blockSize; i++) { - uState[i] = uState[i] ^ uMessage[i + inputOffset]; - } - - inputOffset = inputOffset + blockSize; - if (blockSize == rateInBytes) { - doKeccakf(uState); - blockSize = 0; - } - } - - // Padding phase - uState[blockSize] = uState[blockSize] ^ parameter.getD(); - if ((parameter.getD() & 0x80) != 0 && blockSize == (rateInBytes - 1)) { - doKeccakf(uState); - } - - uState[rateInBytes - 1] = uState[rateInBytes - 1] ^ 0x80; - doKeccakf(uState); - - // Squeezing phase - ByteArrayOutputStream byteResults = new ByteArrayOutputStream(); - int tOutputLen = parameter.getOutputLen() / 8; - while (tOutputLen > 0) { - blockSize = min(tOutputLen, rateInBytes); - for (int i = 0; i < blockSize; i++) { - byteResults.write((byte) uState[i]); - } - - tOutputLen -= blockSize; - if (tOutputLen > 0) { - doKeccakf(uState); - } - } - - return byteResults.toByteArray(); - } - - private void doKeccakf(final int[] uState) { - BigInteger[][] lState = new BigInteger[5][5]; - - for (int i = 0; i < 5; i++) { - for (int j = 0; j < 5; j++) { - int[] data = new int[8]; - arraycopy(uState, 8 * (i + 5 * j), data, 0, data.length); - lState[i][j] = convertFromLittleEndianTo64(data); - } - } - roundB(lState); - - fill(uState, 0); - for (int i = 0; i < 5; i++) { - for (int j = 0; j < 5; j++) { - int[] data = convertFrom64ToLittleEndian(lState[i][j]); - arraycopy(data, 0, uState, 8 * (i + 5 * j), data.length); - } - } - - } - - /** - * Permutation on the given state. - * - * @param state state - */ - private void roundB(final BigInteger[][] state) { - int LFSRstate = 1; - for (int round = 0; round < 24; round++) { - BigInteger[] C = new BigInteger[5]; - BigInteger[] D = new BigInteger[5]; - - // θ step - for (int i = 0; i < 5; i++) { - C[i] = state[i][0].xor(state[i][1]).xor(state[i][2]).xor(state[i][3]).xor(state[i][4]); - } - - for (int i = 0; i < 5; i++) { - D[i] = C[(i + 4) % 5].xor(leftRotate64(C[(i + 1) % 5], 1)); - } - - for (int i = 0; i < 5; i++) { - for (int j = 0; j < 5; j++) { - state[i][j] = state[i][j].xor(D[i]); - } - } - - //ρ and π steps - int x = 1, y = 0; - BigInteger current = state[x][y]; - for (int i = 0; i < 24; i++) { - int tX = x; - x = y; - y = (2 * tX + 3 * y) % 5; - - BigInteger shiftValue = current; - current = state[x][y]; - - state[x][y] = leftRotate64(shiftValue, (i + 1) * (i + 2) / 2); - } - - //χ step - for (int j = 0; j < 5; j++) { - BigInteger[] t = new BigInteger[5]; - for (int i = 0; i < 5; i++) { - t[i] = state[i][j]; - } - - for (int i = 0; i < 5; i++) { - // ~t[(i + 1) % 5] - BigInteger invertVal = t[(i + 1) % 5].xor(BIT_64); - // t[i] ^ ((~t[(i + 1) % 5]) & t[(i + 2) % 5]) - state[i][j] = t[i].xor(invertVal.and(t[(i + 2) % 5])); - } - } - - //ι step - for (int i = 0; i < 7; i++) { - LFSRstate = ((LFSRstate << 1) ^ ((LFSRstate >> 7) * 0x71)) % 256; - // pow(2, i) - 1 - int bitPosition = (1 << i) - 1; - if ((LFSRstate & 2) != 0) { - state[0][0] = state[0][0].xor(new BigInteger("1").shiftLeft(bitPosition)); - } - } - } - } - -} diff --git a/app/src/main/java/com/theromus/sha/Parameters.java b/app/src/main/java/com/theromus/sha/Parameters.java deleted file mode 100644 index 6835b5a..0000000 --- a/app/src/main/java/com/theromus/sha/Parameters.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.theromus.sha; - -/** - * The parameters defining the standard FIPS 202. - * - * @author romus - */ -public enum Parameters { - KECCAK_224 (1152, 0x01, 224), - KECCAK_256 (1088, 0x01, 256), - KECCAK_384 (832, 0x01, 384), - KECCAK_512 (576, 0x01, 512), - - SHA3_224 (1152, 0x06, 224), - SHA3_256 (1088, 0x06, 256), - SHA3_384 (832, 0x06, 384), - SHA3_512 (576, 0x06, 512), - - SHAKE128 (1344, 0x1F, 256), - SHAKE256 (1088, 0x1F, 512); - - private final int rate; - - /** - * Delimited suffix. - */ - public final int d; - - /** - * Output length (bits). - */ - public final int outputLen; - - Parameters(int rate, int d, int outputLen) { - this.rate = rate; - this.d = d; - this.outputLen = outputLen; - } - - public int getRate() { - return rate; - } - - public int getD() { - return d; - } - - public int getOutputLen() { - return outputLen; - } -} diff --git a/app/src/main/java/com/theromus/utils/HexUtils.java b/app/src/main/java/com/theromus/utils/HexUtils.java deleted file mode 100644 index d1fc903..0000000 --- a/app/src/main/java/com/theromus/utils/HexUtils.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.theromus.utils; - - -import java.io.ByteArrayOutputStream; -import java.math.BigInteger; - -/** - * Hex-utils. - * - * @author romus - */ -public class HexUtils { - - private static final byte[] ENCODE_BYTE_TABLE = { - (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', - (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' - }; - - /** - * Convert byte array to unsigned array. - * - * @param data byte array - * @return unsigned array - */ - public static int[] convertToUint(final byte[] data) { - int[] converted = new int[data.length]; - for (int i = 0; i < data.length; i++) { - converted[i] = data[i] & 0xFF; - } - - return converted; - } - - /** - * Convert LE to 64-bit value (unsigned long). - * - * @param data data - * @return 64-bit value (unsigned long) - */ - public static BigInteger convertFromLittleEndianTo64(final int[] data) { - BigInteger uLong = new BigInteger("0"); - for (int i = 0; i < 8; i++) { - uLong = uLong.add(new BigInteger(Integer.toString(data[i])).shiftLeft(8 * i)); - } - - return uLong; - } - - /** - * Convert 64-bit (unsigned long) value to LE. - * - * @param uLong 64-bit value (unsigned long) - * @return LE - */ - public static int[] convertFrom64ToLittleEndian(final BigInteger uLong) { - int[] data = new int[8]; - BigInteger mod256 = new BigInteger("256"); - for (int i = 0; i < 8; i++) { - data[i] = uLong.shiftRight((8 * i)).mod(mod256).intValue(); - } - - return data; - } - - /** - * Bitwise rotate left. - * - * @param value unsigned long value - * @param rotate rotate left - * @return result - */ - public static BigInteger leftRotate64(final BigInteger value, final int rotate) { - BigInteger lp = value.shiftRight(64 - (rotate % 64)); - BigInteger rp = value.shiftLeft(rotate % 64); - - return lp.add(rp).mod(new BigInteger("18446744073709551616")); - } - - /** - * Convert bytes to string. - * - * @param data bytes array - * @return string - */ - public static String convertBytesToString(final byte[] data) { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - for (int i = 0; i < data.length; i++) { - int uVal = data[i] & 0xFF; - - buffer.write(ENCODE_BYTE_TABLE[(uVal >>> 4)]); - buffer.write(ENCODE_BYTE_TABLE[uVal & 0xF]); - } - - return new String(buffer.toByteArray()); - } - -} diff --git a/app/src/main/java/info/guardianproject/netcipher/client/StrongOkHttpClientBuilder.java b/app/src/main/java/info/guardianproject/netcipher/client/StrongOkHttpClientBuilder.java deleted file mode 100644 index 60ee5a4..0000000 --- a/app/src/main/java/info/guardianproject/netcipher/client/StrongOkHttpClientBuilder.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2012-2016 Nathan Freitas - * Copyright 2015 str4d - * Portions Copyright (c) 2016 CommonsWare, LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package info.guardianproject.netcipher.client; - -import android.content.Context; -import android.content.Intent; -import javax.net.ssl.SSLSocketFactory; -import okhttp3.OkHttpClient; -import okhttp3.Request; - -/** - * Creates an OkHttpClient using NetCipher configuration. Use - * build() if you have no other OkHttpClient configuration - * that you need to perform. Or, use applyTo() to augment an - * existing OkHttpClient.Builder with NetCipher. - */ -public class StrongOkHttpClientBuilder extends - StrongBuilderBase { - /** - * Creates a StrongOkHttpClientBuilder using the strongest set - * of options for security. Use this if the strongest set of - * options is what you want; otherwise, create a - * builder via the constructor and configure it as you see fit. - * - * @param context any Context will do - * @return a configured StrongOkHttpClientBuilder - * @throws Exception - */ - static public StrongOkHttpClientBuilder forMaxSecurity(Context context) - throws Exception { - return(new StrongOkHttpClientBuilder(context) - .withBestProxy()); - } - - /** - * Creates a builder instance. - * - * @param context any Context will do; builder will hold onto - * Application context - */ - public StrongOkHttpClientBuilder(Context context) { - super(context); - } - - /** - * Copy constructor. - * - * @param original builder to clone - */ - public StrongOkHttpClientBuilder(StrongOkHttpClientBuilder original) { - super(original); - } - - /** - * OkHttp3 does not support SOCKS proxies: - * https://github.com/square/okhttp/issues/2315 - * - * @return false - */ - @Override - public boolean supportsSocksProxy() { - return(true); - } - - /** - * {@inheritDoc} - */ - @Override - public OkHttpClient build(Intent status) { - return(applyTo(new OkHttpClient.Builder(), status).build()); - } - - /** - * Adds NetCipher configuration to an existing OkHttpClient.Builder, - * in case you have additional configuration that you wish to - * perform. - * - * @param builder a new or partially-configured OkHttpClient.Builder - * @return the same builder - */ - public OkHttpClient.Builder applyTo(OkHttpClient.Builder builder, Intent status) { - SSLSocketFactory factory=buildSocketFactory(); - - if (factory!=null) { - builder.sslSocketFactory(factory); - } - - return(builder - .proxy(buildProxy(status))); - } - - @Override - protected String get(Intent status, OkHttpClient connection, - String url) throws Exception { - Request request=new Request.Builder().url(url).build(); - - return(connection.newCall(request).execute().body().string()); - } -} diff --git a/app/src/main/res/drawable/button_bg.xml b/app/src/main/res/drawable/button_bg.xml index f5d3fc4..23d849f 100644 --- a/app/src/main/res/drawable/button_bg.xml +++ b/app/src/main/res/drawable/button_bg.xml @@ -1,7 +1,7 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_bg_disabled.xml b/app/src/main/res/drawable/button_bg_disabled.xml index 9c845dd..71f1be9 100644 --- a/app/src/main/res/drawable/button_bg_disabled.xml +++ b/app/src/main/res/drawable/button_bg_disabled.xml @@ -4,8 +4,6 @@ - - + + diff --git a/app/src/main/res/drawable/button_bg_enabled.xml b/app/src/main/res/drawable/button_bg_enabled.xml index 6a9f3d0..0432696 100644 --- a/app/src/main/res/drawable/button_bg_enabled.xml +++ b/app/src/main/res/drawable/button_bg_enabled.xml @@ -3,9 +3,7 @@ android:shape="rectangle"> - - + android:right="8dp" /> + + diff --git a/app/src/main/res/drawable/edittext_bg.xml b/app/src/main/res/drawable/edittext_bg.xml index 5a0f345..f224a03 100644 --- a/app/src/main/res/drawable/edittext_bg.xml +++ b/app/src/main/res/drawable/edittext_bg.xml @@ -6,8 +6,6 @@ android:left="12dp" android:right="12dp" android:top="12dp" /> - - + + diff --git a/app/src/main/res/drawable/ic_content_copy_24dp.xml b/app/src/main/res/drawable/ic_content_copy_24dp.xml index 7c67aeb..ff28c0a 100644 --- a/app/src/main/res/drawable/ic_content_copy_24dp.xml +++ b/app/src/main/res/drawable/ic_content_copy_24dp.xml @@ -1,9 +1,9 @@ + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z" /> diff --git a/app/src/main/res/drawable/ic_monero_qr.xml b/app/src/main/res/drawable/ic_monero_qr.xml index 2456cf8..6357d42 100644 --- a/app/src/main/res/drawable/ic_monero_qr.xml +++ b/app/src/main/res/drawable/ic_monero_qr.xml @@ -1,17 +1,18 @@ - + - + + android:right="2dp" + android:top="2dp" /> - + android:width="1dp" + android:color="#fff" /> + diff --git a/app/src/main/res/drawable/sync_progress_bar_drawable.xml b/app/src/main/res/drawable/sync_progress_bar_drawable.xml index 0e51519..25b5e7f 100644 --- a/app/src/main/res/drawable/sync_progress_bar_drawable.xml +++ b/app/src/main/res/drawable/sync_progress_bar_drawable.xml @@ -2,17 +2,14 @@ - + - + - + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 432bd65..90ca846 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,19 +1,20 @@ + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 6451bf2..57dae7c 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -23,14 +23,14 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" - android:layout_marginEnd="24dp" android:layout_marginTop="24dp" + android:layout_marginEnd="24dp" android:textSize="24sp" android:textStyle="bold" - tools:text="100.000000000000 XMR" app:layout_constraintEnd_toStartOf="@id/settings_imageview" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent"/> + app:layout_constraintTop_toTopOf="parent" + tools:text="100.000000000000 XMR" /> + app:layout_constraintTop_toBottomOf="@id/settings_imageview" + tools:text="+ 100.000000000000 confirming" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/balance_locked_textview" + app:layout_constraintVertical_bias="0.0" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@id/receive_send_buttons_layout" /> + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> +