From bf5ed793b37c0ffc93fa05684fd0be0442305e82 Mon Sep 17 00:00:00 2001
From: m2049r
Date: Sat, 4 Aug 2018 01:03:26 +0200
Subject: [PATCH] Monerujo for Ledger Nano S (#377)
---
app/build.gradle | 4 +-
app/src/main/AndroidManifest.xml | 28 +-
app/src/main/cpp/monerujo.cpp | 147 ++++++++
app/src/main/cpp/monerujo_ledger.h | 46 +++
.../main/java/com/btchip/BTChipException.java | 53 +++
.../java/com/btchip/comm/BTChipTransport.java | 31 ++
.../java/com/btchip/comm/LedgerHelper.java | 126 +++++++
.../android/BTChipTransportAndroidHID.java | 145 ++++++++
app/src/main/java/com/btchip/utils/Dump.java | 62 ++++
.../com/m2049r/xmrwallet/BaseActivity.java | 105 ++++++
.../m2049r/xmrwallet/GenerateFragment.java | 25 ++
.../xmrwallet/GenerateReviewFragment.java | 61 +++-
.../com/m2049r/xmrwallet/LoginActivity.java | 328 +++++++++++++-----
.../com/m2049r/xmrwallet/LoginFragment.java | 75 ++--
.../com/m2049r/xmrwallet/ReceiveFragment.java | 109 ++++--
.../com/m2049r/xmrwallet/WalletActivity.java | 94 +++--
.../xmrwallet/dialog/ProgressDialog.java | 130 +++++++
.../m2049r/xmrwallet/ledger/Instruction.java | 142 ++++++++
.../com/m2049r/xmrwallet/ledger/Ledger.java | 272 +++++++++++++++
.../ledger/LedgerProgressDialog.java | 162 +++++++++
.../com/m2049r/xmrwallet/model/Wallet.java | 1 +
.../m2049r/xmrwallet/model/WalletManager.java | 24 ++
.../xmrwallet/service/WalletService.java | 4 +
.../com/m2049r/xmrwallet/util/Helper.java | 12 +-
.../com/m2049r/xmrwallet/util/Notice.java | 9 +
app/src/main/res/anim/fab_close.xml | 2 +-
app/src/main/res/anim/fab_open.xml | 2 +-
.../main/res/drawable/ic_ledger_restore.xml | 15 +
.../res/layout/dialog_ledger_progress.xml | 54 +++
app/src/main/res/layout/fragment_review.xml | 121 ++++---
app/src/main/res/layout/layout_fabmenu.xml | 33 ++
.../main/res/menu/create_wallet_ledger.xml | 12 +
app/src/main/res/values-de/help.xml | 13 +-
app/src/main/res/values-de/strings.xml | 17 +
app/src/main/res/values-el/help.xml | 11 +
app/src/main/res/values-el/strings.xml | 19 +-
app/src/main/res/values-es/help.xml | 12 +
app/src/main/res/values-es/strings.xml | 18 +
app/src/main/res/values-fr/help.xml | 11 +
app/src/main/res/values-fr/strings.xml | 18 +-
app/src/main/res/values-hu/help.xml | 11 +
app/src/main/res/values-hu/strings.xml | 18 +-
app/src/main/res/values-it/help.xml | 11 +
app/src/main/res/values-it/strings.xml | 17 +
app/src/main/res/values-nb/help.xml | 11 +
app/src/main/res/values-nb/strings.xml | 17 +
app/src/main/res/values-pt/help.xml | 11 +
app/src/main/res/values-pt/strings.xml | 18 +-
app/src/main/res/values-ro/help.xml | 11 +
app/src/main/res/values-ro/strings.xml | 19 +-
app/src/main/res/values-ru/help.xml | 11 +
app/src/main/res/values-ru/strings.xml | 18 +-
app/src/main/res/values-sv/help.xml | 11 +
app/src/main/res/values-sv/strings.xml | 18 +-
app/src/main/res/values-zh-rCN/help.xml | 11 +
app/src/main/res/values-zh-rCN/strings.xml | 17 +
app/src/main/res/values-zh-rTW/help.xml | 11 +
app/src/main/res/values-zh-rTW/strings.xml | 17 +
app/src/main/res/values/help.xml | 33 +-
app/src/main/res/values/strings.xml | 20 +-
app/src/main/res/values/styles.xml | 1 -
app/src/main/res/xml/usb_device_filter.xml | 9 +
external-libs/monero/include/wallet2_api.h | 17 +
63 files changed, 2639 insertions(+), 252 deletions(-)
create mode 100644 app/src/main/cpp/monerujo_ledger.h
create mode 100644 app/src/main/java/com/btchip/BTChipException.java
create mode 100644 app/src/main/java/com/btchip/comm/BTChipTransport.java
create mode 100644 app/src/main/java/com/btchip/comm/LedgerHelper.java
create mode 100644 app/src/main/java/com/btchip/comm/android/BTChipTransportAndroidHID.java
create mode 100644 app/src/main/java/com/btchip/utils/Dump.java
create mode 100644 app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java
create mode 100644 app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java
create mode 100644 app/src/main/java/com/m2049r/xmrwallet/ledger/Instruction.java
create mode 100644 app/src/main/java/com/m2049r/xmrwallet/ledger/Ledger.java
create mode 100644 app/src/main/java/com/m2049r/xmrwallet/ledger/LedgerProgressDialog.java
create mode 100644 app/src/main/res/drawable/ic_ledger_restore.xml
create mode 100644 app/src/main/res/layout/dialog_ledger_progress.xml
create mode 100644 app/src/main/res/menu/create_wallet_ledger.xml
create mode 100644 app/src/main/res/xml/usb_device_filter.xml
diff --git a/app/build.gradle b/app/build.gradle
index a55f8824..f1f71bca 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 25
- versionCode 102
- versionName "1.5.12 'Maximum Nachos'"
+ versionCode 111
+ versionName "1.6.1 'Nano S'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4bf5b3a6..89197384 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,11 +10,11 @@
+
+
+
+
+
+
+
+
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")));
return JNI_VERSION_1_6;
}
#ifdef __cplusplus
@@ -353,6 +356,39 @@ Java_com_m2049r_xmrwallet_model_WalletManager_createWalletFromKeysJ(JNIEnv *env,
return reinterpret_cast(wallet);
}
+
+// virtual void setSubaddressLookahead(uint32_t major, uint32_t minor) = 0;
+
+JNIEXPORT jlong JNICALL
+Java_com_m2049r_xmrwallet_model_WalletManager_createWalletFromDeviceJ(JNIEnv *env, jobject instance,
+ jstring path,
+ jstring password,
+ jint networkType,
+ jstring deviceName,
+ jlong restoreHeight,
+ jstring subaddressLookahead) {
+ const char *_path = env->GetStringUTFChars(path, NULL);
+ const char *_password = env->GetStringUTFChars(password, NULL);
+ Monero::NetworkType _networkType = static_cast(networkType);
+ const char *_deviceName = env->GetStringUTFChars(deviceName, NULL);
+ const char *_subaddressLookahead = env->GetStringUTFChars(subaddressLookahead, NULL);
+
+ Bitmonero::Wallet *wallet =
+ Bitmonero::WalletManagerFactory::getWalletManager()->createWalletFromDevice(
+ std::string(_path),
+ std::string(_password),
+ _networkType,
+ std::string(_deviceName),
+ (uint64_t) restoreHeight,
+ std::string(_subaddressLookahead));
+
+ env->ReleaseStringUTFChars(path, _path);
+ env->ReleaseStringUTFChars(password, _password);
+ env->ReleaseStringUTFChars(deviceName, _deviceName);
+ env->ReleaseStringUTFChars(subaddressLookahead, _subaddressLookahead);
+ return reinterpret_cast(wallet);
+}
+
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_walletExists(JNIEnv *env, jobject instance,
jstring path) {
@@ -378,6 +414,20 @@ Java_com_m2049r_xmrwallet_model_WalletManager_verifyWalletPassword(JNIEnv *env,
return static_cast(passwordOk);
}
+//virtual int queryWalletHardware(const std::string &keys_file_name, const std::string &password) const = 0;
+JNIEXPORT jint JNICALL
+Java_com_m2049r_xmrwallet_model_WalletManager_queryWalletHardware(JNIEnv *env, jobject instance,
+ jstring keys_file_name,
+ jstring password) {
+ const char *_keys_file_name = env->GetStringUTFChars(keys_file_name, NULL);
+ const char *_password = env->GetStringUTFChars(password, NULL);
+ int hardwareId =
+ Bitmonero::WalletManagerFactory::getWalletManager()->
+ queryWalletHardware(std::string(_keys_file_name), std::string(_password));
+ env->ReleaseStringUTFChars(keys_file_name, _keys_file_name);
+ env->ReleaseStringUTFChars(password, _password);
+ return static_cast(hardwareId);
+}
JNIEXPORT jobject JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_findWallets(JNIEnv *env, jobject instance,
@@ -712,6 +762,13 @@ Java_com_m2049r_xmrwallet_model_Wallet_isSynchronized(JNIEnv *env, jobject insta
return static_cast(wallet->synchronized());
}
+JNIEXPORT jboolean JNICALL
+Java_com_m2049r_xmrwallet_model_Wallet_isKeyOnDevice(JNIEnv *env, jobject instance) {
+ Bitmonero::Wallet *wallet = getHandle(env, instance);
+ bool key_on_device = wallet->isKeyOnDevice();
+ return static_cast(key_on_device);
+}
+
//void cn_slow_hash(const void *data, size_t length, char *hash); // from crypto/hash-ops.h
JNIEXPORT jbyteArray JNICALL
Java_com_m2049r_xmrwallet_util_KeyStoreHelper_slowHash(JNIEnv *env, jobject clazz,
@@ -1309,6 +1366,96 @@ Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jobject i
Bitmonero::WalletManagerFactory::setLogLevel(level);
}
+//
+// Ledger Stuff
+//
+
+#include "monerujo_ledger.h"
+
+/**
+ * @brief LedgerExchange - exchange data with Ledger Device
+ * @param pbSendBuffer - buffer for data to send
+ * @param cbSendLength - length of send buffer
+ * @param pbRecvBuffer - buffer for received data
+ * @param pcbRecvLength - pointer to size of receive buffer
+ * gets set with length of received data on successful return
+ * @return SCARD_S_SUCCESS - success
+ * SCARD_E_NO_READERS_AVAILABLE - no device connected / found
+ * SCARD_E_INSUFFICIENT_BUFFER - pbRecvBuffer is too small for the response
+ */
+LONG LedgerExchange(
+ LPCBYTE pbSendBuffer,
+ DWORD cbSendLength,
+ LPBYTE pbRecvBuffer,
+ LPDWORD pcbRecvLength) {
+ LOGD("LedgerExchange");
+ JNIEnv *jenv;
+ int envStat = attachJVM(&jenv);
+ if (envStat == JNI_ERR) return -1;
+
+ jmethodID exchangeMethod = jenv->GetStaticMethodID(class_Ledger, "Exchange", "([B)[B");
+
+ jsize sendLen = static_cast(cbSendLength);
+ jbyteArray dataSend = jenv->NewByteArray(sendLen);
+ jenv->SetByteArrayRegion(dataSend, 0, sendLen, (jbyte *) pbSendBuffer);
+ jbyteArray dataRecv = (jbyteArray) jenv->CallStaticObjectMethod(class_Ledger, exchangeMethod,
+ dataSend);
+ jenv->DeleteLocalRef(dataSend);
+ if (dataRecv == nullptr) {
+ detachJVM(jenv, envStat);
+ LOGD("LedgerExchange SCARD_E_NO_READERS_AVAILABLE");
+ return SCARD_E_NO_READERS_AVAILABLE;
+ }
+ jsize len = jenv->GetArrayLength(dataRecv);
+ LOGD("LedgerExchange SCARD_S_SUCCESS %ld/%d", cbSendLength, len);
+ if (len <= *pcbRecvLength) {
+ *pcbRecvLength = static_cast(len);
+ jenv->GetByteArrayRegion(dataRecv, 0, len, (jbyte *) pbRecvBuffer);
+ jenv->DeleteLocalRef(dataRecv);
+ detachJVM(jenv, envStat);
+ return SCARD_S_SUCCESS;
+ } else {
+ jenv->DeleteLocalRef(dataRecv);
+ detachJVM(jenv, envStat);
+ LOGE("LedgerExchange SCARD_E_INSUFFICIENT_BUFFER");
+ return SCARD_E_INSUFFICIENT_BUFFER;
+ }
+}
+
+/**
+ * @brief LedgerFind - find Ledger Device and return it's name
+ * @param buffer - buffer for name of found device
+ * @param len - length of buffer
+ * @return 0 - success
+ * -1 - no device connected / found
+ * -2 - JVM not found
+ */
+int LedgerFind(char *buffer, size_t len) {
+ LOGD("LedgerName");
+ JNIEnv *jenv;
+ int envStat = attachJVM(&jenv);
+ if (envStat == JNI_ERR) return -2;
+
+ jmethodID nameMethod = jenv->GetStaticMethodID(class_Ledger, "Name", "()Ljava/lang/String;");
+ jstring name = (jstring) jenv->CallStaticObjectMethod(class_Ledger, nameMethod);
+
+ int ret;
+ if (name != nullptr) {
+ const char *_name = jenv->GetStringUTFChars(name, NULL);
+ strncpy(buffer, _name, len);
+ jenv->ReleaseStringUTFChars(name, _name);
+ buffer[len - 1] = 0; // terminate in case _name is bigger
+ ret = 0;
+ LOGD("LedgerName is %s", buffer);
+ } else {
+ buffer[0] = 0;
+ ret = -1;
+ }
+
+ detachJVM(jenv, envStat);
+ return ret;
+}
+
#ifdef __cplusplus
}
#endif
diff --git a/app/src/main/cpp/monerujo_ledger.h b/app/src/main/cpp/monerujo_ledger.h
new file mode 100644
index 00000000..7e523bfd
--- /dev/null
+++ b/app/src/main/cpp/monerujo_ledger.h
@@ -0,0 +1,46 @@
+/**
+ * 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.
+ */
+
+#ifndef XMRWALLET_LEDGER_H
+#define XMRWALLET_LEDGER_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define SCARD_S_SUCCESS ((LONG)0x00000000) /**< No error was encountered. */
+#define SCARD_E_INSUFFICIENT_BUFFER ((LONG)0x80100008) /**< The data buffer to receive returned data is too small for the returned data. */
+#define SCARD_E_NO_READERS_AVAILABLE ((LONG)0x8010002E) /**< Cannot find a smart card reader. */
+
+typedef long LONG;
+typedef unsigned long DWORD;
+typedef DWORD *LPDWORD;
+typedef unsigned char BYTE;
+typedef BYTE *LPBYTE;
+typedef const BYTE *LPCBYTE;
+
+typedef char CHAR;
+typedef CHAR *LPSTR;
+
+int LedgerFind(char *buffer, size_t len);
+LONG LedgerExchange(LPCBYTE pbSendBuffer, DWORD cbSendLength, LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //XMRWALLET_LEDGER_H
diff --git a/app/src/main/java/com/btchip/BTChipException.java b/app/src/main/java/com/btchip/BTChipException.java
new file mode 100644
index 00000000..440a3ad3
--- /dev/null
+++ b/app/src/main/java/com/btchip/BTChipException.java
@@ -0,0 +1,53 @@
+/*
+ *******************************************************************************
+ * 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
new file mode 100644
index 00000000..5fe921f6
--- /dev/null
+++ b/app/src/main/java/com/btchip/comm/BTChipTransport.java
@@ -0,0 +1,31 @@
+/*
+ *******************************************************************************
+ * 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 {
+ public byte[] exchange(byte[] command);
+
+ public void close();
+
+ public 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
new file mode 100644
index 00000000..db24899e
--- /dev/null
+++ b/app/src/main/java/com/btchip/comm/LedgerHelper.java
@@ -0,0 +1,126 @@
+/*
+ *******************************************************************************
+ * 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
new file mode 100644
index 00000000..e4cbd43d
--- /dev/null
+++ b/app/src/main/java/com/btchip/comm/android/BTChipTransportAndroidHID.java
@@ -0,0 +1,145 @@
+/*
+ *******************************************************************************
+ * 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) && (device.getProductId() == PID_HID)) {
+ 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_HID = 0x0001;
+
+ 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
new file mode 100644
index 00000000..2d453fc1
--- /dev/null
+++ b/app/src/main/java/com/btchip/utils/Dump.java
@@ -0,0 +1,62 @@
+/*
+ *******************************************************************************
+ * 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/xmrwallet/BaseActivity.java b/app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java
new file mode 100644
index 00000000..8ef124d0
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java
@@ -0,0 +1,105 @@
+package com.m2049r.xmrwallet;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+
+import com.m2049r.xmrwallet.dialog.ProgressDialog;
+import com.m2049r.xmrwallet.ledger.Ledger;
+import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
+
+import timber.log.Timber;
+
+public class BaseActivity extends SecureActivity implements GenerateReviewFragment.ProgressListener {
+
+ ProgressDialog progressDialog = null;
+
+ private class SimpleProgressDialog extends ProgressDialog {
+
+ SimpleProgressDialog(Context context, int msgId) {
+ super(context);
+ setCancelable(false);
+ setMessage(context.getString(msgId));
+ }
+
+ @Override
+ public void onBackPressed() {
+ // prevent back button
+ }
+ }
+
+ @Override
+ public void showProgressDialog(int msgId) {
+ showProgressDialog(msgId, 0);
+ }
+
+ public void showProgressDialog(int msgId, long delay) {
+ dismissProgressDialog(); // just in case
+ progressDialog = new SimpleProgressDialog(BaseActivity.this, msgId);
+ if (delay > 0) {
+ Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ public void run() {
+ if (progressDialog != null) progressDialog.show();
+ }
+ }, delay);
+ } else {
+ progressDialog.show();
+ }
+ }
+
+ @Override
+ public void showLedgerProgressDialog(int mode) {
+ dismissProgressDialog(); // just in case
+ progressDialog = new LedgerProgressDialog(BaseActivity.this, mode);
+ Ledger.setListener((Ledger.Listener) progressDialog);
+ progressDialog.show();
+ }
+
+ @Override
+ public void dismissProgressDialog() {
+ if (progressDialog == null) return; // nothing to do
+ if (progressDialog instanceof Ledger.Listener) {
+ Ledger.unsetListener((Ledger.Listener) progressDialog);
+ }
+ if (progressDialog.isShowing()) {
+ progressDialog.dismiss();
+ }
+ progressDialog = null;
+ }
+
+ static final int RELEASE_WAKE_LOCK_DELAY = 5000; // millisconds
+
+ private PowerManager.WakeLock wl = null;
+
+ void acquireWakeLock() {
+ if ((wl != null) && wl.isHeld()) return;
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ this.wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getString(R.string.app_name));
+ try {
+ wl.acquire();
+ Timber.d("WakeLock acquired");
+ } catch (SecurityException ex) {
+ Timber.w("WakeLock NOT acquired: %s", ex.getLocalizedMessage());
+ wl = null;
+ }
+ }
+
+ void releaseWakeLock(int delayMillis) {
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ releaseWakeLock();
+ }
+ }, delayMillis);
+ }
+
+ void releaseWakeLock() {
+ if ((wl == null) || !wl.isHeld()) return;
+ wl.release();
+ wl = null;
+ Timber.d("WakeLock released");
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java
index 5a14739c..36acae4d 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java
@@ -61,6 +61,7 @@ public class GenerateFragment extends Fragment {
static final String TYPE_NEW = "new";
static final String TYPE_KEY = "key";
static final String TYPE_SEED = "seed";
+ static final String TYPE_LEDGER = "ledger";
static final String TYPE_VIEWONLY = "view";
private TextInputLayout etWalletName;
@@ -190,6 +191,17 @@ public class GenerateFragment extends Fragment {
return false;
}
});
+ } else if (type.equals(TYPE_LEDGER)) {
+ etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
+ etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
+ etWalletRestoreHeight.requestFocus();
+ return true;
+ }
+ return false;
+ }
+ });
} else if (type.equals(TYPE_SEED)) {
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
@@ -485,6 +497,12 @@ public class GenerateFragment extends Fragment {
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
}
activityCallback.onGenerate(name, crazyPass, seed, height);
+ } else if (type.equals(TYPE_LEDGER)) {
+ bGenerate.setEnabled(false);
+ if (fingerprintAuthAllowed) {
+ KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
+ }
+ activityCallback.onGenerateLedger(name, crazyPass, height);
} else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) {
if (checkAddress() && checkViewKey() && checkSpendKey()) {
bGenerate.setEnabled(false);
@@ -523,6 +541,8 @@ public class GenerateFragment extends Fragment {
return getString(R.string.generate_wallet_type_new);
case TYPE_SEED:
return getString(R.string.generate_wallet_type_seed);
+ case TYPE_LEDGER:
+ return getString(R.string.generate_wallet_type_ledger);
case TYPE_VIEWONLY:
return getString(R.string.generate_wallet_type_view);
default:
@@ -540,6 +560,8 @@ public class GenerateFragment extends Fragment {
void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height);
+ void onGenerateLedger(String name, String password, long height);
+
void setTitle(String title);
void setToolbarButton(int type);
@@ -575,6 +597,9 @@ public class GenerateFragment extends Fragment {
case TYPE_SEED:
inflater.inflate(R.menu.create_wallet_seed, menu);
break;
+ case TYPE_LEDGER:
+ inflater.inflate(R.menu.create_wallet_ledger, menu);
+ break;
case TYPE_VIEWONLY:
inflater.inflate(R.menu.create_wallet_view, menu);
break;
diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java
index 6f442464..034b5073 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java
@@ -43,6 +43,8 @@ import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
+import com.m2049r.xmrwallet.ledger.Ledger;
+import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
@@ -52,8 +54,6 @@ import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.widget.Toolbar;
-import java.io.File;
-
import timber.log.Timber;
public class GenerateReviewFragment extends Fragment {
@@ -76,6 +76,9 @@ public class GenerateReviewFragment extends Fragment {
private ImageButton bCopyAddress;
private LinearLayout llAdvancedInfo;
private LinearLayout llPassword;
+ private LinearLayout llMnemonic;
+ private LinearLayout llSpendKey;
+ private LinearLayout llViewKey;
private Button bAdvancedInfo;
private Button bAccept;
@@ -99,6 +102,9 @@ public class GenerateReviewFragment extends Fragment {
bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo);
llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo);
llPassword = (LinearLayout) view.findViewById(R.id.llPassword);
+ llMnemonic = (LinearLayout) view.findViewById(R.id.llMnemonic);
+ llSpendKey = (LinearLayout) view.findViewById(R.id.llSpendKey);
+ llViewKey = (LinearLayout) view.findViewById(R.id.llViewKey);
bAccept = (Button) view.findViewById(R.id.bAccept);
@@ -142,7 +148,6 @@ public class GenerateReviewFragment extends Fragment {
}
void showDetails() {
- showProgress();
tvWalletPassword.setText(null);
new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, walletPath);
}
@@ -188,6 +193,20 @@ public class GenerateReviewFragment extends Fragment {
boolean isWatchOnly;
Wallet.Status status;
+ boolean dialogOpened = false;
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ showProgress();
+ if ((walletPath != null)
+ && (WalletManager.getInstance().queryWalletHardware(walletPath + ".keys", getPassword()) == 1)
+ && (progressCallback != null)) {
+ progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
+ dialogOpened = true;
+ }
+ }
+
@Override
protected Boolean doInBackground(String... params) {
if (params.length != 1) return false;
@@ -212,7 +231,11 @@ public class GenerateReviewFragment extends Fragment {
address = wallet.getAddress();
seed = wallet.getSeed();
- viewKey = wallet.getSecretViewKey();
+ if (wallet.isKeyOnDevice()) {
+ viewKey = Ledger.Key();
+ } else {
+ viewKey = wallet.getSecretViewKey();
+ }
spendKey = isWatchOnly ? getActivity().getString(R.string.label_watchonly) : wallet.getSecretSpendKey();
isWatchOnly = wallet.isWatchOnly();
if (closeWallet) wallet.close();
@@ -222,6 +245,8 @@ public class GenerateReviewFragment extends Fragment {
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
+ if (dialogOpened)
+ progressCallback.dismissProgressDialog();
if (!isAdded()) return; // never mind
walletName = name;
if (result) {
@@ -232,10 +257,22 @@ public class GenerateReviewFragment extends Fragment {
llPassword.setVisibility(View.VISIBLE);
tvWalletPassword.setText(getPassword());
tvWalletAddress.setText(address);
- tvWalletMnemonic.setText(seed);
- tvWalletViewKey.setText(viewKey);
- tvWalletSpendKey.setText(spendKey);
- bAdvancedInfo.setVisibility(View.VISIBLE);
+ if (!seed.isEmpty()) {
+ llMnemonic.setVisibility(View.VISIBLE);
+ tvWalletMnemonic.setText(seed);
+ }
+ boolean showAdvanced = false;
+ if (isKeyValid(viewKey)) {
+ llViewKey.setVisibility(View.VISIBLE);
+ tvWalletViewKey.setText(viewKey);
+ showAdvanced = true;
+ }
+ if (isKeyValid(spendKey)) {
+ llSpendKey.setVisibility(View.VISIBLE);
+ tvWalletSpendKey.setText(spendKey);
+ showAdvanced = true;
+ }
+ if (showAdvanced) bAdvancedInfo.setVisibility(View.VISIBLE);
bCopyAddress.setClickable(true);
bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp);
activityCallback.setTitle(name, getString(R.string.details_title));
@@ -267,6 +304,8 @@ public class GenerateReviewFragment extends Fragment {
public interface ProgressListener {
void showProgressDialog(int msgId);
+ void showLedgerProgressDialog(int mode);
+
void dismissProgressDialog();
}
@@ -577,4 +616,10 @@ public class GenerateReviewFragment extends Fragment {
return openDialog;
}
+ private boolean isKeyValid(String key) {
+ return (key != null) && (key.length() == 64)
+ && !key.equals("0000000000000000000000000000000000000000000000000000000000000000")
+ && !key.toLowerCase().equals("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
+ // ledger implmenetation returns the spend key as f's
+ }
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
index d63a424c..0c2ebb10 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
@@ -16,18 +16,20 @@
package com.m2049r.xmrwallet;
-import android.app.Activity;
import android.app.AlertDialog;
-import android.app.ProgressDialog;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
import android.media.MediaScannerConnection;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
@@ -46,6 +48,8 @@ import com.m2049r.xmrwallet.dialog.AboutFragment;
import com.m2049r.xmrwallet.dialog.CreditsFragment;
import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.dialog.PrivacyFragment;
+import com.m2049r.xmrwallet.ledger.Ledger;
+import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
@@ -72,10 +76,10 @@ import java.util.Locale;
import timber.log.Timber;
-public class LoginActivity extends SecureActivity
+public class LoginActivity extends BaseActivity
implements LoginFragment.Listener, GenerateFragment.Listener,
GenerateReviewFragment.Listener, GenerateReviewFragment.AcceptListener,
- GenerateReviewFragment.ProgressListener, ReceiveFragment.Listener {
+ ReceiveFragment.Listener {
private static final String GENERATE_STACK = "gen";
static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms
@@ -102,6 +106,11 @@ public class LoginActivity extends SecureActivity
toolbar.setTitle(title, subtitle);
}
+ @Override
+ public boolean hasLedger() {
+ return Ledger.isConnected();
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
Timber.d("onCreate()");
@@ -140,6 +149,8 @@ public class LoginActivity extends SecureActivity
} else {
Timber.i("Waiting for permissions");
}
+
+ processIntent(getIntent());
}
boolean checkServiceRunning() {
@@ -517,39 +528,12 @@ public class LoginActivity extends SecureActivity
super.onPause();
}
- ProgressDialog progressDialog = null;
-
- @Override
- public void showProgressDialog(int msgId) {
- showProgressDialog(msgId, 0);
- }
-
- private void showProgressDialog(int msgId, long delay) {
- dismissProgressDialog(); // just in case
- progressDialog = new MyProgressDialog(LoginActivity.this, msgId);
- if (delay > 0) {
- Handler handler = new Handler();
- handler.postDelayed(new Runnable() {
- public void run() {
- if (progressDialog != null) progressDialog.show();
- }
- }, delay);
- } else {
- progressDialog.show();
- }
- }
-
- @Override
- public void dismissProgressDialog() {
- if (progressDialog != null && progressDialog.isShowing()) {
- progressDialog.dismiss();
- }
- progressDialog = null;
- }
-
@Override
protected void onDestroy() {
+ Timber.d("onDestroy");
dismissProgressDialog();
+ unregisterDetachReceiver();
+ Ledger.disconnect();
super.onDestroy();
}
@@ -562,25 +546,9 @@ public class LoginActivity extends SecureActivity
// and show a progress dialog, but only if there isn't one already
new AsyncWaitForService().execute();
}
+ if (!Ledger.isConnected()) attachLedger();
}
- private class MyProgressDialog extends ProgressDialog {
- Activity activity;
-
- MyProgressDialog(Activity activity, int msgId) {
- super(activity);
- this.activity = activity;
- setCancelable(false);
- setMessage(activity.getString(msgId));
- }
-
- @Override
- public void onBackPressed() {
- // prevent back button
- }
- }
-
-
private class AsyncWaitForService extends AsyncTask {
@Override
protected void onPreExecute() {
@@ -730,7 +698,12 @@ public class LoginActivity extends SecureActivity
@Override
protected void onPreExecute() {
super.onPreExecute();
- showProgressDialog(R.string.generate_wallet_creating);
+ acquireWakeLock();
+ if (walletCreator.isLedger()) {
+ showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
+ } else {
+ showProgressDialog(R.string.generate_wallet_creating);
+ }
}
@Override
@@ -763,6 +736,7 @@ public class LoginActivity extends SecureActivity
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
+ releaseWakeLock(RELEASE_WAKE_LOCK_DELAY);
if (isDestroyed()) {
return;
}
@@ -794,12 +768,20 @@ public class LoginActivity extends SecureActivity
interface WalletCreator {
boolean createWallet(File aFile, String password);
+ boolean isLedger();
+
}
@Override
public void onGenerate(final String name, final String password) {
createWallet(name, password,
new WalletCreator() {
+ @Override
+ public boolean isLedger() {
+ return false;
+ }
+
+ @Override
public boolean createWallet(File aFile, String password) {
Wallet newWallet = WalletManager.getInstance()
.createWallet(aFile, password, MNEMONIC_LANGUAGE);
@@ -819,9 +801,40 @@ public class LoginActivity extends SecureActivity
final long restoreHeight) {
createWallet(name, password,
new WalletCreator() {
+ @Override
+ public boolean isLedger() {
+ return false;
+ }
+
+ @Override
public boolean createWallet(File aFile, String password) {
- Wallet newWallet = WalletManager.getInstance().
- recoveryWallet(aFile, password, seed, restoreHeight);
+ Wallet newWallet = WalletManager.getInstance()
+ .recoveryWallet(aFile, password, seed, restoreHeight);
+ boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
+ if (!success) {
+ Timber.e(newWallet.getErrorString());
+ toast(newWallet.getErrorString());
+ }
+ newWallet.close();
+ return success;
+ }
+ });
+ }
+
+ @Override
+ public void onGenerateLedger(final String name, final String password, final long restoreHeight) {
+ createWallet(name, password,
+ new WalletCreator() {
+ @Override
+ public boolean isLedger() {
+ return true;
+ }
+
+ @Override
+ public boolean createWallet(File aFile, String password) {
+ Wallet newWallet = WalletManager.getInstance()
+ .createWalletFromDevice(aFile, password,
+ restoreHeight, "Ledger");
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
if (!success) {
Timber.e(newWallet.getErrorString());
@@ -839,6 +852,12 @@ public class LoginActivity extends SecureActivity
final long restoreHeight) {
createWallet(name, password,
new WalletCreator() {
+ @Override
+ public boolean isLedger() {
+ return false;
+ }
+
+ @Override
public boolean createWallet(File aFile, String password) {
Wallet newWallet = WalletManager.getInstance()
.createWalletWithKeys(aFile, password, MNEMONIC_LANGUAGE, restoreHeight,
@@ -863,6 +882,15 @@ public class LoginActivity extends SecureActivity
});
}
+ void toast(final int msgId) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(LoginActivity.this, getString(msgId), Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
@Override
public void onAccept(final String name, final String password) {
File walletFolder = getStorageRoot();
@@ -870,26 +898,9 @@ public class LoginActivity extends SecureActivity
Timber.d("New Wallet %s", walletFile.getAbsolutePath());
walletFile.delete(); // when recovering wallets, the cache seems corrupt
- boolean rc = testWallet(walletFile.getAbsolutePath(), password) == Wallet.Status.Status_Ok;
-
- if (rc) {
- popFragmentStack(GENERATE_STACK);
- Toast.makeText(LoginActivity.this,
- getString(R.string.generate_wallet_created), Toast.LENGTH_SHORT).show();
- } else {
- Timber.e("Wallet store failed to %s", walletFile.getAbsolutePath());
- Toast.makeText(LoginActivity.this, getString(R.string.generate_wallet_create_failed), Toast.LENGTH_LONG).show();
- }
- }
-
- Wallet.Status testWallet(String path, String password) {
- Timber.d("testing wallet %s", path);
- Wallet aWallet = WalletManager.getInstance().openWallet(path, password);
- if (aWallet == null) return Wallet.Status.Status_Error; // does this ever happen?
- Wallet.Status status = aWallet.getStatus();
- Timber.d("wallet tested %s", aWallet.getStatus());
- aWallet.close();
- return status;
+ popFragmentStack(GENERATE_STACK);
+ Toast.makeText(LoginActivity.this,
+ getString(R.string.generate_wallet_created), Toast.LENGTH_SHORT).show();
}
boolean walletExists(File walletFile, boolean any) {
@@ -1040,6 +1051,9 @@ public class LoginActivity extends SecureActivity
case R.id.action_create_help_seed:
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_seed);
return true;
+ case R.id.action_create_help_ledger:
+ HelpFragment.display(getSupportFragmentManager(), R.string.help_create_ledger);
+ return true;
case R.id.action_details_help:
HelpFragment.display(getSupportFragmentManager(), R.string.help_details);
return true;
@@ -1150,14 +1164,166 @@ public class LoginActivity extends SecureActivity
File walletFile = Helper.getWalletFile(this, walletNode.getName());
if (WalletManager.getInstance().walletExists(walletFile)) {
WalletManager.getInstance().setDaemon(walletNode);
- Helper.promptPassword(LoginActivity.this, walletNode.getName(), false, new Helper.PasswordAction() {
- @Override
- public void action(String walletName, String password, boolean fingerprintUsed) {
- startWallet(walletName, password, fingerprintUsed);
- }
- });
+ Helper.promptPassword(LoginActivity.this, walletNode.getName(), false,
+ new Helper.PasswordAction() {
+ @Override
+ public void action(String walletName, String password, boolean fingerprintUsed) {
+ String keyPath = new File(Helper.getWalletRoot(LoginActivity.this),
+ walletName + ".keys").getAbsolutePath();
+ // check if we need connected hardware
+ int hw = WalletManager.getInstance().queryWalletHardware(keyPath, password);
+ if ((hw == 1) && (!hasLedger())) {
+ toast(R.string.open_wallet_ledger_missing);
+ } else {
+ // hw could be < 0 meaning the password is wrong - this gets dealt with later
+ startWallet(walletName, password, fingerprintUsed);
+ }
+ }
+ });
} else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
}
}
+
+ // USB Stuff - (Ledger)
+
+ private static final String ACTION_USB_PERMISSION = "com.m2049r.xmrwallet.USB_PERMISSION";
+
+ void attachLedger() {
+ final UsbManager usbManager = getUsbManager();
+ UsbDevice device = Ledger.findDevice(usbManager);
+ if (device != null) {
+ if (usbManager.hasPermission(device)) {
+ connectLedger(usbManager, device);
+ } else {
+ registerReceiver(usbPermissionReceiver, new IntentFilter(ACTION_USB_PERMISSION));
+ usbManager.requestPermission(device,
+ PendingIntent.getBroadcast(this, 0,
+ new Intent(ACTION_USB_PERMISSION), 0));
+ }
+ } else {
+ Timber.d("no ledger device found");
+ }
+ }
+
+ private final BroadcastReceiver usbPermissionReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (ACTION_USB_PERMISSION.equals(action)) {
+ unregisterReceiver(usbPermissionReceiver);
+ synchronized (this) {
+ UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
+ if (device != null) {
+ connectLedger(getUsbManager(), device);
+ }
+ } else {
+ Timber.w("User denied permission for device %s", device.getProductName());
+ }
+ }
+ }
+ }
+ };
+
+ private void connectLedger(UsbManager usbManager, final UsbDevice usbDevice) {
+ try {
+ Ledger.connect(usbManager, usbDevice);
+ registerDetachReceiver();
+ onLedgerAction();
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(LoginActivity.this,
+ getString(R.string.toast_ledger_attached, usbDevice.getProductName()),
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+ });
+ } catch (IOException ex) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(LoginActivity.this,
+ getString(R.string.open_wallet_ledger_missing),
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+ });
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ processIntent(intent);
+ }
+
+ private void processIntent(Intent intent) {
+ String action = intent.getAction();
+ if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
+ synchronized (this) {
+ final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ if (device != null) {
+ final UsbManager usbManager = getUsbManager();
+ if (usbManager.hasPermission(device)) {
+ Timber.d("Ledger attached by intent");
+ connectLedger(usbManager, device);
+ }
+ }
+ }
+ }
+ }
+
+ BroadcastReceiver detachReceiver;
+
+ private void unregisterDetachReceiver() {
+ if (detachReceiver != null) unregisterReceiver(detachReceiver);
+ }
+
+ private void registerDetachReceiver() {
+ detachReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
+ unregisterDetachReceiver();
+ final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ Timber.i("Ledger detached!");
+ if (device != null)
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(LoginActivity.this,
+ getString(R.string.toast_ledger_detached, device.getProductName()),
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+ });
+ Ledger.disconnect();
+ onLedgerAction();
+ }
+ }
+ };
+
+ registerReceiver(detachReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED));
+ }
+
+ public void onLedgerAction() {
+ Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
+ if (f instanceof GenerateFragment) {
+ onBackPressed();
+ } else if (f instanceof LoginFragment) {
+ if (((LoginFragment) f).isFabOpen()) {
+ ((LoginFragment) f).animateFAB();
+ }
+ }
+ }
+
+ // get UsbManager or die trying
+ @NonNull
+ private UsbManager getUsbManager() {
+ final UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
+ if (usbManager == null) {
+ throw new IllegalStateException("no USB_SERVICE");
+ }
+ return usbManager;
+ }
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java
index ce97e3e8..fe67dd6c 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java
@@ -103,6 +103,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
void setNetworkType(NetworkType networkType);
+ boolean hasLedger();
}
@Override
@@ -145,11 +146,13 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabView = (FloatingActionButton) view.findViewById(R.id.fabView);
fabKey = (FloatingActionButton) view.findViewById(R.id.fabKey);
fabSeed = (FloatingActionButton) view.findViewById(R.id.fabSeed);
+ fabLedger = (FloatingActionButton) view.findViewById(R.id.fabLedger);
fabNewL = (RelativeLayout) view.findViewById(R.id.fabNewL);
fabViewL = (RelativeLayout) view.findViewById(R.id.fabViewL);
fabKeyL = (RelativeLayout) view.findViewById(R.id.fabKeyL);
fabSeedL = (RelativeLayout) view.findViewById(R.id.fabSeedL);
+ fabLedgerL = (RelativeLayout) view.findViewById(R.id.fabLedgerL);
fab_pulse = AnimationUtils.loadAnimation(getContext(), R.anim.fab_pulse);
fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen);
@@ -163,6 +166,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabView.setOnClickListener(this);
fabKey.setOnClickListener(this);
fabSeed.setOnClickListener(this);
+ fabLedger.setOnClickListener(this);
fabScreen.setOnClickListener(this);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list);
@@ -173,7 +177,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
etDummy = (EditText) view.findViewById(R.id.etDummy);
ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice);
- Notice.showAll(llNotice,".*_login");
+ Notice.showAll(llNotice, ".*_login");
etDaemonAddress = (DropDownEditText) view.findViewById(R.id.etDaemonAddress);
nodeAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line);
@@ -426,9 +430,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
}
private boolean isFabOpen = false;
- private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed;
+ private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabLedger;
private FrameLayout fabScreen;
- private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL;
+ private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, fabLedgerL;
private Animation fab_open, fab_close, rotate_forward, rotate_backward, fab_open_screen, fab_close_screen;
private Animation fab_pulse;
@@ -437,32 +441,53 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
}
public void animateFAB() {
- if (isFabOpen) {
- fabScreen.setVisibility(View.INVISIBLE);
+ if (isFabOpen) { // close the fab
fabScreen.setClickable(false);
fabScreen.startAnimation(fab_close_screen);
fab.startAnimation(rotate_backward);
- fabNewL.startAnimation(fab_close);
- fabNew.setClickable(false);
- fabViewL.startAnimation(fab_close);
- fabView.setClickable(false);
- fabKeyL.startAnimation(fab_close);
- fabKey.setClickable(false);
- fabSeedL.startAnimation(fab_close);
- fabSeed.setClickable(false);
+ if (fabLedgerL.getVisibility() == View.VISIBLE) {
+ fabLedgerL.startAnimation(fab_close);
+ fabLedger.setClickable(false);
+ } else {
+ fabNewL.startAnimation(fab_close);
+ fabNew.setClickable(false);
+ fabViewL.startAnimation(fab_close);
+ fabView.setClickable(false);
+ fabKeyL.startAnimation(fab_close);
+ fabKey.setClickable(false);
+ fabSeedL.startAnimation(fab_close);
+ fabSeed.setClickable(false);
+ }
isFabOpen = false;
- } else {
+ } else { // open the fab
fabScreen.setClickable(true);
fabScreen.startAnimation(fab_open_screen);
fab.startAnimation(rotate_forward);
- fabNewL.startAnimation(fab_open);
- fabNew.setClickable(true);
- fabViewL.startAnimation(fab_open);
- fabView.setClickable(true);
- fabKeyL.startAnimation(fab_open);
- fabKey.setClickable(true);
- fabSeedL.startAnimation(fab_open);
- fabSeed.setClickable(true);
+ if (activityCallback.hasLedger()) {
+ fabLedgerL.setVisibility(View.VISIBLE);
+ fabNewL.setVisibility(View.GONE);
+ fabViewL.setVisibility(View.GONE);
+ fabKeyL.setVisibility(View.GONE);
+ fabSeedL.setVisibility(View.GONE);
+
+ fabLedgerL.startAnimation(fab_open);
+ fabLedger.setClickable(true);
+ } else {
+ fabLedgerL.setVisibility(View.GONE);
+ fabNewL.setVisibility(View.VISIBLE);
+ fabViewL.setVisibility(View.VISIBLE);
+ fabKeyL.setVisibility(View.VISIBLE);
+ fabSeedL.setVisibility(View.VISIBLE);
+
+ fabNewL.startAnimation(fab_open);
+ fabNew.setClickable(true);
+ fabViewL.startAnimation(fab_open);
+ fabView.setClickable(true);
+ fabKeyL.startAnimation(fab_open);
+ fabKey.setClickable(true);
+ fabSeedL.startAnimation(fab_open);
+ fabSeed.setClickable(true);
+ }
isFabOpen = true;
}
}
@@ -470,6 +495,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override
public void onClick(View v) {
int id = v.getId();
+ Timber.d("onClick %d/%d", id, R.id.fabLedger);
switch (id) {
case R.id.fab:
animateFAB();
@@ -491,6 +517,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
break;
+ case R.id.fabLedger:
+ Timber.d("FAB_LEDGER");
+ animateFAB();
+ activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
+ break;
case R.id.fabScreen:
animateFAB();
break;
diff --git a/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java b/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java
index 3e92f63c..8b19dd8d 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java
@@ -47,6 +47,7 @@ import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.m2049r.xmrwallet.data.BarcodeData;
+import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
@@ -62,7 +63,6 @@ import timber.log.Timber;
public class ReceiveFragment extends Fragment {
private ProgressBar pbProgress;
- private View llAddress;
private TextView tvAddressLabel;
private TextView tvAddress;
private TextInputLayout etPaymentId;
@@ -93,7 +93,6 @@ public class ReceiveFragment extends Fragment {
View view = inflater.inflate(R.layout.fragment_receive, container, false);
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
- llAddress = view.findViewById(R.id.llAddress);
tvAddressLabel = (TextView) view.findViewById(R.id.tvAddressLabel);
tvAddress = (TextView) view.findViewById(R.id.tvAddress);
etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId);
@@ -177,23 +176,9 @@ public class ReceiveFragment extends Fragment {
enableSubaddressButton(false);
enableCopyAddress(false);
- final Runnable resetSize = new Runnable() {
- public void run() {
- tvAddress.animate().setDuration(125).scaleX(1).scaleY(1).start();
- }
- };
-
final Runnable newAddress = new Runnable() {
public void run() {
- tvAddress.setText(wallet.getNewSubaddress());
- tvAddressLabel.setText(getString(R.string.generate_address_label_sub,
- wallet.getNumSubaddresses() - 1));
- storeWallet();
- generateQr();
- enableCopyAddress(true);
- tvAddress.animate().alpha(1).setDuration(125)
- .scaleX(1.2f).scaleY(1.2f)
- .withEndAction(resetSize).start();
+ getNewSubaddress();
}
};
@@ -315,18 +300,39 @@ public class ReceiveFragment extends Fragment {
}
private void loadAndShow(String walletPath, String password) {
- new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR,
- walletPath, password);
+ new AsyncShow(walletPath, password).executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR);
}
- private class AsyncShow extends AsyncTask {
- String password;
+ GenerateReviewFragment.ProgressListener progressCallback = null;
+
+ private class AsyncShow extends AsyncTask {
+ final private String walletPath;
+ final private String password;
+
+
+ AsyncShow(String walletPath, String passsword) {
+ super();
+ this.walletPath = walletPath;
+ this.password = passsword;
+ }
+
+ boolean dialogOpened = false;
@Override
- protected Boolean doInBackground(String... params) {
- if (params.length != 2) return false;
- String walletPath = params[0];
- password = params[1];
+ protected void onPreExecute() {
+ super.onPreExecute();
+ showProgress();
+ if ((walletPath != null)
+ && (WalletManager.getInstance().queryWalletHardware(walletPath + ".keys", password) == 1)
+ && (progressCallback != null)) {
+ progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
+ dialogOpened = true;
+ }
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ if (params.length != 0) return false;
wallet = WalletManager.getInstance().openWallet(walletPath, password);
isMyWallet = true;
return true;
@@ -335,6 +341,8 @@ public class ReceiveFragment extends Fragment {
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
+ if (dialogOpened)
+ progressCallback.dismissProgressDialog();
if (!isAdded()) return; // never mind
if (result) {
show();
@@ -495,6 +503,10 @@ public class ReceiveFragment extends Fragment {
throw new ClassCastException(context.toString()
+ " must implement Listener");
}
+ if (context instanceof GenerateReviewFragment.ProgressListener) {
+ this.progressCallback = (GenerateReviewFragment.ProgressListener) context;
+ }
+
}
@Override
@@ -513,4 +525,51 @@ public class ReceiveFragment extends Fragment {
}
super.onDetach();
}
+
+ private void getNewSubaddress() {
+ new AsyncSubaddress().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR);
+ }
+
+ private class AsyncSubaddress extends AsyncTask {
+ private String newSubaddress;
+
+ boolean dialogOpened = false;
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ if (wallet.isKeyOnDevice() && (progressCallback != null)) {
+ progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS);
+ dialogOpened = true;
+ }
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ if (params.length != 0) return false;
+ newSubaddress = wallet.getNewSubaddress();
+ storeWallet();
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ super.onPostExecute(result);
+ if (dialogOpened)
+ progressCallback.dismissProgressDialog();
+ tvAddress.setText(newSubaddress);
+ tvAddressLabel.setText(getString(R.string.generate_address_label_sub,
+ wallet.getNumSubaddresses() - 1));
+ generateQr();
+ enableCopyAddress(true);
+ final Runnable resetSize = new Runnable() {
+ public void run() {
+ tvAddress.animate().setDuration(125).scaleX(1).scaleY(1).start();
+ }
+ };
+ tvAddress.animate().alpha(1).setDuration(125)
+ .scaleX(1.2f).scaleY(1.2f)
+ .withEndAction(resetSize).start();
+ }
+ }
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
index 52de0c93..e6d4fb00 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
@@ -24,6 +24,7 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
@@ -51,12 +52,14 @@ import com.m2049r.xmrwallet.dialog.CreditsFragment;
import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment;
import com.m2049r.xmrwallet.fragment.send.SendFragment;
+import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.util.UserNotes;
import com.m2049r.xmrwallet.widget.Toolbar;
@@ -65,7 +68,7 @@ import java.util.List;
import timber.log.Timber;
-public class WalletActivity extends SecureActivity implements WalletFragment.Listener,
+public class WalletActivity extends BaseActivity implements WalletFragment.Listener,
WalletService.Observer, SendFragment.Listener, TxFragment.Listener,
GenerateReviewFragment.ListenerWithWallet,
GenerateReviewFragment.Listener,
@@ -397,28 +400,6 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
Timber.d("onResume()");
}
- private PowerManager.WakeLock wl = null;
-
- void acquireWakeLock() {
- if ((wl != null) && wl.isHeld()) return;
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- this.wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getString(R.string.app_name));
- try {
- wl.acquire();
- Timber.d("WakeLock acquired");
- } catch (SecurityException ex) {
- Timber.w("WakeLock NOT acquired: %s", ex.getLocalizedMessage());
- wl = null;
- }
- }
-
- public void releaseWakeLock() {
- if ((wl == null) || !wl.isHeld()) return;
- wl.release();
- wl = null;
- Timber.d("WakeLock released");
- }
-
public void saveWallet() {
if (mIsBound) { // no point in talking to unbound service
Intent intent = new Intent(getApplicationContext(), WalletService.class);
@@ -503,7 +484,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
if (wallet.isSynchronized()) {
Timber.d("onRefreshed() synced");
- releaseWakeLock(); // the idea is to stay awake until synced
+ releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); // the idea is to stay awake until synced
if (!synced) { // first sync
onProgress(-1);
saveWallet(); // save on first sync
@@ -544,10 +525,21 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
boolean haveWallet = false;
+ @Override
+ public void onWalletOpen(final int hardware) {
+ if (hardware > 0)
+ runOnUiThread(new Runnable() {
+ public void run() {
+ showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
+ }
+ });
+ }
+
@Override
public void onWalletStarted(final boolean success) {
runOnUiThread(new Runnable() {
public void run() {
+ dismissProgressDialog();
if (!success) {
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_failed), Toast.LENGTH_LONG).show();
}
@@ -578,6 +570,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
runOnUiThread(new Runnable() {
public void run() {
+ dismissProgressDialog();
PendingTransaction.Status status = pendingTransaction.getStatus();
if (status != PendingTransaction.Status.Status_Ok) {
String errorText = pendingTransaction.getErrorString();
@@ -733,6 +726,8 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
intent.putExtra(WalletService.REQUEST_CMD_TX_TAG, tag);
startService(intent);
Timber.d("CREATE TX request sent");
+ if (getWallet().isKeyOnDevice())
+ showLedgerProgressDialog(LedgerProgressDialog.TYPE_SEND);
} else {
Timber.e("Service not bound");
}
@@ -1049,12 +1044,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
final int id = item.getItemId();
switch (id) {
case R.id.account_new:
- getWallet().addAccount();
- int newIdx = getWallet().getNumAccounts() - 1;
- getWallet().setAccountIndex(newIdx);
- Toast.makeText(this,
- getString(R.string.accounts_new, newIdx),
- Toast.LENGTH_SHORT).show();
+ addAccount();
break;
default:
Timber.d("NavigationDrawer ID=%d", id);
@@ -1063,9 +1053,49 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
Timber.d("found @%d", accountIdx);
getWallet().setAccountIndex(accountIdx);
}
+ forceUpdate();
+ drawer.closeDrawer(GravityCompat.START);
}
- forceUpdate();
- drawer.closeDrawer(GravityCompat.START);
return true;
}
+
+ private void addAccount() {
+ new AsyncAddAccount().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR);
+ }
+
+ private class AsyncAddAccount extends AsyncTask {
+ boolean dialogOpened = false;
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ if (getWallet().isKeyOnDevice()) {
+ showLedgerProgressDialog(LedgerProgressDialog.TYPE_ACCOUNT);
+ dialogOpened = true;
+ } else {
+ showProgressDialog(R.string.accounts_progress_new);
+ dialogOpened = true;
+ }
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ if (params.length != 0) return false;
+ getWallet().addAccount();
+ getWallet().setAccountIndex(getWallet().getNumAccounts() - 1);
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ super.onPostExecute(result);
+ forceUpdate();
+ drawer.closeDrawer(GravityCompat.START);
+ if (dialogOpened)
+ dismissProgressDialog();
+ Toast.makeText(WalletActivity.this,
+ getString(R.string.accounts_new, getWallet().getNumAccounts() - 1),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java
new file mode 100644
index 00000000..aa73f740
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java
@@ -0,0 +1,130 @@
+package com.m2049r.xmrwallet.dialog;
+
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ * 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.
+ */
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.m2049r.xmrwallet.R;
+
+import java.util.Locale;
+
+import timber.log.Timber;
+
+public class ProgressDialog extends AlertDialog {
+
+ private ProgressBar pbBar;
+
+ private TextView tvMessage;
+
+ private TextView tvProgress;
+
+ private View rlProgressBar, pbCircle;
+
+ static private final String PROGRESS_FORMAT = "%1d/%2d";
+
+ private CharSequence message;
+ private int maxValue, progressValue;
+ private boolean indeterminate = true;
+
+ public ProgressDialog(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ final View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_ledger_progress, null);
+ pbCircle = view.findViewById(R.id.pbCircle);
+ tvMessage = (TextView) view.findViewById(R.id.tvMessage);
+ rlProgressBar = view.findViewById(R.id.rlProgressBar);
+ pbBar = (ProgressBar) view.findViewById(R.id.pbBar);
+ tvProgress = (TextView) view.findViewById(R.id.tvProgress);
+ setView(view);
+ //setTitle("blabla");
+ //super.setMessage("bubbu");
+// view.invalidate();
+ setIndeterminate(indeterminate);
+ if (maxValue > 0) {
+ setMax(maxValue);
+ }
+ if (progressValue > 0) {
+ setProgress(progressValue);
+ }
+ if (message != null) {
+ Timber.d("msg=%s", message);
+ setMessage(message);
+ }
+
+ super.onCreate(savedInstanceState);
+ }
+
+ public void setProgress(int value, int max) {
+ progressValue = value;
+ maxValue = max;
+ if (pbBar != null) {
+ pbBar.setProgress(value);
+ pbBar.setMax(max);
+ tvProgress.setText(String.format(Locale.getDefault(), PROGRESS_FORMAT, value, maxValue));
+ }
+ }
+
+ public void setProgress(int value) {
+ progressValue = value;
+ if (pbBar != null) {
+ pbBar.setProgress(value);
+ tvProgress.setText(String.format(Locale.getDefault(), PROGRESS_FORMAT, value, maxValue));
+ }
+ }
+
+ public void setMax(int max) {
+ maxValue = max;
+ if (pbBar != null) {
+ pbBar.setMax(max);
+ }
+ }
+
+ public void setIndeterminate(boolean indeterminate) {
+ if (this.indeterminate != indeterminate) {
+ if (rlProgressBar != null) {
+ if (indeterminate) {
+ pbCircle.setVisibility(View.VISIBLE);
+ rlProgressBar.setVisibility(View.GONE);
+ } else {
+ pbCircle.setVisibility(View.GONE);
+ rlProgressBar.setVisibility(View.VISIBLE);
+ }
+ }
+ this.indeterminate = indeterminate;
+ }
+ }
+
+ @Override
+ public void setMessage(CharSequence message) {
+ this.message = message;
+ if (tvMessage != null) {
+ tvMessage.setText(message);
+ }
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/ledger/Instruction.java b/app/src/main/java/com/m2049r/xmrwallet/ledger/Instruction.java
new file mode 100644
index 00000000..512fe778
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/ledger/Instruction.java
@@ -0,0 +1,142 @@
+/*
+ * 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_PUT_KEY(0x22),
+ INS_GET_CHACHA8_PREKEY(0x24),
+ INS_VERIFY_KEY(0x26),
+
+ 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_BLIND(0x78),
+ INS_UNBLIND(0x7A),
+ INS_VALIDATE(0x7C),
+ INS_MLSAG(0x7E),
+ INS_CLOSE_TX(0x80),
+
+ 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;
+ }
+
+ private 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
new file mode 100644
index 00000000..e761b086
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/ledger/Ledger.java
@@ -0,0 +1,272 @@
+/*
+ *******************************************************************************
+ * BTChip Bitcoin Hardware Wallet Java API
+ * (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
+ * (c) 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.util.Helper;
+
+import java.io.IOException;
+
+import timber.log.Timber;
+
+public class Ledger {
+ // lookahead parameters as suggest on
+ // https://monero.stackexchange.com/a/9902/8977 (Step 8)
+ // by dEBRUYNE
+ static public final int LOOKAHEAD_ACCOUNTS = 3;
+ static public final int LOOKAHEAD_SUBADDRESSES = 100;
+ static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES;
+
+ 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 UsbDevice findDevice(UsbManager usbManager) {
+ 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;
+ }
+ }
+
+ 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.i("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 = ((int) (response[response.length - 2] & 0xff) << 8) |
+ (int) (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[] exchangeApdu(byte cla, byte ins, byte p1, byte p2, byte[] data, int acceptedSW[]) throws BTChipException {
+ byte[] apdu = new byte[data.length + 5];
+ apdu[0] = cla;
+ apdu[1] = ins;
+ apdu[2] = p1;
+ apdu[3] = p2;
+ apdu[4] = (byte) (data.length);
+ System.arraycopy(data, 0, apdu, 5, data.length);
+ return exchangeCheck(apdu, acceptedSW);
+ }
+
+ private byte[] exchangeApdu(byte cla, byte ins, byte p1, byte p2, int length, int acceptedSW[]) throws BTChipException {
+ byte[] apdu = new byte[5];
+ apdu[0] = cla;
+ apdu[1] = ins;
+ apdu[2] = p1;
+ apdu[3] = p2;
+ apdu[4] = (byte) (length);
+ return exchangeCheck(apdu, acceptedSW);
+ }
+
+ private byte[] exchangeApduSplit(byte cla, byte ins, byte p1, byte p2, byte[] data, int acceptedSW[]) throws BTChipException {
+ int offset = 0;
+ byte[] result = null;
+ while (offset < data.length) {
+ int blockLength = ((data.length - offset) > 255 ? 255 : data.length - offset);
+ byte[] apdu = new byte[blockLength + 5];
+ apdu[0] = cla;
+ apdu[1] = ins;
+ apdu[2] = p1;
+ apdu[3] = p2;
+ apdu[4] = (byte) (blockLength);
+ System.arraycopy(data, offset, apdu, 5, blockLength);
+ result = exchangeCheck(apdu, acceptedSW);
+ offset += blockLength;
+ }
+ return result;
+ }
+
+ private byte[] exchangeApduSplit2(byte cla, byte ins, byte p1, byte p2, byte[] data, byte[] data2, int acceptedSW[]) throws BTChipException {
+ int offset = 0;
+ byte[] result = null;
+ int maxBlockSize = 255 - data2.length;
+ while (offset < data.length) {
+ int blockLength = ((data.length - offset) > maxBlockSize ? maxBlockSize : data.length - offset);
+ boolean lastBlock = ((offset + blockLength) == data.length);
+ byte[] apdu = new byte[blockLength + 5 + (lastBlock ? data2.length : 0)];
+ apdu[0] = cla;
+ apdu[1] = ins;
+ apdu[2] = p1;
+ apdu[3] = p2;
+ apdu[4] = (byte) (blockLength + (lastBlock ? data2.length : 0));
+ System.arraycopy(data, offset, apdu, 5, blockLength);
+ if (lastBlock) {
+ System.arraycopy(data2, 0, apdu, 5 + blockLength, data2.length);
+ }
+ result = exchangeCheck(apdu, acceptedSW);
+ offset += blockLength;
+ }
+ return result;
+ }
+
+ 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/ledger/LedgerProgressDialog.java b/app/src/main/java/com/m2049r/xmrwallet/ledger/LedgerProgressDialog.java
new file mode 100644
index 00000000..6730d3ea
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/ledger/LedgerProgressDialog.java
@@ -0,0 +1,162 @@
+package com.m2049r.xmrwallet.ledger;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.m2049r.xmrwallet.R;
+import com.m2049r.xmrwallet.dialog.ProgressDialog;
+import com.m2049r.xmrwallet.model.WalletManager;
+
+import timber.log.Timber;
+
+public class LedgerProgressDialog extends ProgressDialog implements Ledger.Listener {
+
+ static public final int TYPE_DEBUG = 0;
+ static public final int TYPE_RESTORE = 1;
+ static public final int TYPE_SUBADDRESS = 2;
+ static public final int TYPE_ACCOUNT = 3;
+ static public final int TYPE_SEND = 4;
+
+ private final int type;
+ private Handler uiHandler = new Handler(Looper.getMainLooper());
+
+ public LedgerProgressDialog(Context context, int type) {
+ super(context);
+ this.type = type;
+ setCancelable(false);
+ if (type == TYPE_SEND)
+ setMessage(context.getString(R.string.info_prepare_tx));
+ else
+ setMessage(context.getString(R.string.progress_ledger_progress));
+ }
+
+ @Override
+ public void onBackPressed() {
+ // prevent back button
+ }
+
+ private int firstSubaddress = Integer.MAX_VALUE;
+
+ private boolean validate = false;
+ private boolean validated = false;
+
+ @Override
+ public void onInstructionSend(final Instruction ins, final byte[] apdu) {
+ Timber.d("LedgerProgressDialog SEND %s", ins);
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (type > TYPE_DEBUG) {
+ validate = false;
+ switch (ins) {
+ case INS_RESET: // ledger may ask for confirmation - maybe a bug?
+ case INS_GET_KEY: // ledger asks for confirmation to send keys
+ setIndeterminate(true);
+ setMessage(getContext().getString(R.string.progress_ledger_confirm));
+ break;
+ case INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY: // lookahead
+ //00 4a 00 00 09 00 01000000 30000000
+ // 0 1 2 3 4 5 6 7 8 9 a b c d
+ int account = bytesToInteger(apdu, 6);
+ int subaddress = bytesToInteger(apdu, 10);
+ Timber.d("fetching subaddress (%d, %d)", account, subaddress);
+ switch (type) {
+ case TYPE_RESTORE:
+ setProgress(account * Ledger.LOOKAHEAD_SUBADDRESSES + subaddress + 1,
+ Ledger.LOOKAHEAD_ACCOUNTS * Ledger.LOOKAHEAD_SUBADDRESSES);
+ setIndeterminate(false);
+ break;
+ case TYPE_ACCOUNT:
+ final int requestedSubaddress = account * Ledger.LOOKAHEAD_SUBADDRESSES + subaddress;
+ if (firstSubaddress > requestedSubaddress) {
+ firstSubaddress = requestedSubaddress;
+ }
+ setProgress(requestedSubaddress - firstSubaddress + 1,
+ Ledger.LOOKAHEAD_ACCOUNTS * Ledger.LOOKAHEAD_SUBADDRESSES);
+ setIndeterminate(false);
+ break;
+ case TYPE_SUBADDRESS:
+ if (firstSubaddress > subaddress) {
+ firstSubaddress = subaddress;
+ }
+ setProgress(subaddress - firstSubaddress + 1, Ledger.LOOKAHEAD_SUBADDRESSES);
+ setIndeterminate(false);
+ break;
+ default:
+ setIndeterminate(true);
+ break;
+ }
+ setMessage(getContext().getString(R.string.progress_ledger_lookahead));
+ break;
+ case INS_VERIFY_KEY:
+ setIndeterminate(true);
+ setMessage(getContext().getString(R.string.progress_ledger_verify));
+ break;
+ case INS_OPEN_TX:
+ setIndeterminate(true);
+ setMessage(getContext().getString(R.string.progress_ledger_opentx));
+ break;
+ case INS_MLSAG:
+ if (validated) {
+ setIndeterminate(true);
+ setMessage(getContext().getString(R.string.progress_ledger_mlsag));
+ }
+ break;
+ case INS_VALIDATE:
+ if ((apdu[2] != 1) || (apdu[3] != 1)) break;
+ validate = true;
+ uiHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ if (validate) {
+ setIndeterminate(true);
+ setMessage(getContext().getString(R.string.progress_ledger_confirm));
+ validated = true;
+ }
+ }
+ }, 250);
+ break;
+ default:
+ // ignore others and maintain state
+ }
+ } else {
+ setMessage(ins.name());
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onInstructionReceive(final Instruction ins, final byte[] data) {
+ Timber.d("LedgerProgressDialog RECV %s", ins);
+ uiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (type > TYPE_DEBUG) {
+ switch (ins) {
+ case INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY: // lookahead
+ case INS_VERIFY_KEY:
+ case INS_GET_CHACHA8_PREKEY:
+ break;
+ default:
+ if (type != TYPE_SEND)
+ setMessage(getContext().getString(R.string.progress_ledger_progress));
+ }
+ } else {
+ setMessage("Returned from " + ins.name());
+ }
+ }
+ });
+ }
+
+ // TODO: we use ints in Java but the are signed; accounts & subaddresses are unsigned ...
+ private int bytesToInteger(byte[] bytes, int offset) {
+ int result = 0;
+ for (int i = 3; i >= 0; i--) {
+ result <<= 8;
+ result |= (bytes[offset + i] & 0xFF);
+ }
+ return result;
+ }
+}
\ No newline at end of file
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 cdc0f8ff..ad0feed8 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java
@@ -389,4 +389,5 @@ public class Wallet {
return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1);
}
+ public native boolean isKeyOnDevice();
}
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 2ee5a7f9..41bda7fe 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java
@@ -17,6 +17,7 @@
package com.m2049r.xmrwallet.model;
import com.m2049r.xmrwallet.data.WalletNode;
+import com.m2049r.xmrwallet.ledger.Ledger;
import java.io.BufferedReader;
import java.io.File;
@@ -129,6 +130,23 @@ public class WalletManager {
String viewKeyString,
String spendKeyString);
+ public Wallet createWalletFromDevice(File aFile, String password, long restoreHeight,
+ String deviceName) {
+ long walletHandle = createWalletFromDeviceJ(aFile.getAbsolutePath(), password,
+ getNetworkType().getValue(), deviceName, restoreHeight,
+ Ledger.SUBADDRESS_LOOKAHEAD);
+ Wallet wallet = new Wallet(walletHandle);
+ manageWallet(wallet);
+ return wallet;
+ }
+
+ private native long createWalletFromDeviceJ(String path, String password,
+ int networkType,
+ String deviceName,
+ long restoreHeight,
+ String subaddressLookahead);
+
+
public native boolean closeJ(Wallet wallet);
public boolean close(Wallet wallet) {
@@ -150,6 +168,12 @@ public class WalletManager {
public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only);
+ public boolean verifyWalletPasswordOnly(String keys_file_name, String password) {
+ return queryWalletHardware(keys_file_name, password) >= 0;
+ }
+
+ public native int queryWalletHardware(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 {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java
index adc08c6d..e06f9e66 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java
@@ -221,6 +221,8 @@ public class WalletService extends Service {
void onSetNotes(boolean success);
void onWalletStarted(boolean success);
+
+ void onWalletOpen(int hardware);
}
String progressText = null;
@@ -535,6 +537,8 @@ public class WalletService extends Service {
showProgress(30);
if (walletMgr.walletExists(path)) {
Timber.d("open wallet %s", path);
+ int hw = WalletManager.getInstance().queryWalletHardware(path + ".keys", walletPassword);
+ if (observer != null) observer.onWalletOpen(hw);
wallet = walletMgr.openWallet(path, walletPassword);
showProgress(60);
Timber.d("wallet opened");
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 cbfb526a..48b25705 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
@@ -349,33 +349,33 @@ public class Helper {
String walletPath = new File(getWalletRoot(context), walletName + ".keys").getAbsolutePath();
// try with entered password (which could be a legacy password or a CrAzYpass)
- if (WalletManager.getInstance().verifyWalletPassword(walletPath, password, true)) {
+ if (WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, password)) {
return password;
}
// maybe this is a malformed CrAzYpass?
String possibleCrazyPass = CrazyPassEncoder.reformat(password);
if (possibleCrazyPass != null) { // looks like a CrAzYpass
- if (WalletManager.getInstance().verifyWalletPassword(walletPath, possibleCrazyPass, true)) {
+ if (WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, possibleCrazyPass)) {
return possibleCrazyPass;
}
}
// generate & try with CrAzYpass
String crazyPass = KeyStoreHelper.getCrazyPass(context, password);
- if (WalletManager.getInstance().verifyWalletPassword(walletPath, crazyPass, true)) {
+ if (WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, crazyPass)) {
return crazyPass;
}
// or maybe it is a broken CrAzYpass? (of which we have two variants)
String brokenCrazyPass2 = KeyStoreHelper.getBrokenCrazyPass(context, password, 2);
if ((brokenCrazyPass2 != null)
- && WalletManager.getInstance().verifyWalletPassword(walletPath, brokenCrazyPass2, true)) {
+ && WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, brokenCrazyPass2)) {
return brokenCrazyPass2;
}
String brokenCrazyPass1 = KeyStoreHelper.getBrokenCrazyPass(context, password, 1);
if ((brokenCrazyPass1 != null)
- && WalletManager.getInstance().verifyWalletPassword(walletPath, brokenCrazyPass1, true)) {
+ && WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, brokenCrazyPass1)) {
return brokenCrazyPass1;
}
@@ -407,6 +407,7 @@ public class Helper {
final CancellationSignal cancelSignal = new CancellationSignal();
final AtomicBoolean incorrectSavedPass = new AtomicBoolean(false);
+
class LoginWalletTask extends AsyncTask {
private String pass;
private boolean fingerprintUsed;
@@ -594,6 +595,5 @@ public class Helper {
static public ExchangeApi getExchangeApi() {
return new com.m2049r.xmrwallet.service.exchange.coinmarketcap.ExchangeApiImpl(OkHttpClientSingleton.getOkHttpClient());
-
}
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Notice.java b/app/src/main/java/com/m2049r/xmrwallet/util/Notice.java
index 1b0b1c83..1fa09e62 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/util/Notice.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/Notice.java
@@ -33,6 +33,8 @@ import com.m2049r.xmrwallet.dialog.HelpFragment;
import java.util.ArrayList;
import java.util.List;
+import timber.log.Timber;
+
public class Notice {
private static final String PREFS_NAME = "notice";
private static List notices = null;
@@ -40,6 +42,7 @@ public class Notice {
private static final String NOTICE_SHOW_XMRTO_ENABLED_LOGIN = "notice_xmrto_enabled_login";
private static final String NOTICE_SHOW_XMRTO_ENABLED_SEND = "notice_xmrto_enabled_send";
private static final String NOTICE_SHOW_CRAZYPASS = "notice_crazypass_enabled_login";
+ private static final String NOTICE_SHOW_LEDGER = "notice_ledger_enabled_login";
private static void init() {
synchronized (Notice.class) {
@@ -63,6 +66,12 @@ public class Notice {
R.string.help_details,
2)
);
+ notices.add(
+ new Notice(NOTICE_SHOW_LEDGER,
+ R.string.info_ledger_enabled,
+ R.string.help_create_ledger,
+ 1)
+ );
}
}
diff --git a/app/src/main/res/anim/fab_close.xml b/app/src/main/res/anim/fab_close.xml
index 9dd7e780..7a5c735a 100644
--- a/app/src/main/res/anim/fab_close.xml
+++ b/app/src/main/res/anim/fab_close.xml
@@ -1,6 +1,6 @@
+ android:fillAfter="false">
+ android:fillAfter="false">
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_ledger_progress.xml b/app/src/main/res/layout/dialog_ledger_progress.xml
new file mode 100644
index 00000000..f6bff98e
--- /dev/null
+++ b/app/src/main/res/layout/dialog_ledger_progress.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_review.xml b/app/src/main/res/layout/fragment_review.xml
index 569a665b..15925c01 100644
--- a/app/src/main/res/layout/fragment_review.xml
+++ b/app/src/main/res/layout/fragment_review.xml
@@ -51,10 +51,13 @@
android:textAlignment="center"
tools:text="49RBjxQ2zgf7t17w7So9ngcEY9obKzsrr6Dsah24MNSMiMBEeiYPP5CCTBq4GpZcEYN5Zf3upsLiwd5PezePE1i4Tf3rryY" />
-
+ android:layout_marginTop="@dimen/header_top"
+ android:orientation="vertical"
+ android:visibility="gone">
-
-
+
+
-
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/header_top"
+ android:orientation="vertical"
+ android:visibility="gone">
+
+
+
+
+
+
+
+
+
+
+
+
+ android:text="@string/generate_spendkey_label" />
-
-
-
-
-
-
-
-
-
+ android:layout_marginTop="@dimen/data_top"
+ android:textAlignment="center"
+ tools:text="2bb6afd03f1a4b91e619236b9d342aeb6d232fa50e5b9b91a00db26d1cafaa08" />
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_fabmenu.xml b/app/src/main/res/layout/layout_fabmenu.xml
index 826c4d9c..534fe3da 100644
--- a/app/src/main/res/layout/layout_fabmenu.xml
+++ b/app/src/main/res/layout/layout_fabmenu.xml
@@ -139,6 +139,39 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-de/help.xml b/app/src/main/res/values-de/help.xml
index bf6ad075..1580bec2 100644
--- a/app/src/main/res/values-de/help.xml
+++ b/app/src/main/res/values-de/help.xml
@@ -204,7 +204,7 @@
BTC senden
XMR.TO
- XXMR.TO ist ein Drittanbieter-Service, der als Wechselservice von Monero zu Bitcoin fungiert.
+
XMR.TO ist ein Drittanbieter-Service, der als Wechselservice von Monero zu Bitcoin fungiert.
Wir verwenden die XMR.TO Schnittstelle, um Bitcoin-Zahlungen in Monerujo zu integrieren. Bitte sieh dir
https://xmr.to an und entscheide selbst, ob es etwas ist, was du verwenden möchtest. Das Monerujo
Team ist nicht mit XMR.TO verbunden und kann dir bei deren Service nicht helfen.
@@ -227,4 +227,15 @@
indem du zum vorherigen Schritt zurückgehst und dann zum Bildschirm "Bestätigen" zurückkehrst.
]]>
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 5d120ffe..3986943f 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -34,6 +34,7 @@
BTC Zahlung aktiviert - Tippe für mehr Infos.
CrAzYpass aktiviert - Tippe für mehr Infos.
+ Ledger aktiviert - Tippe für mehr Infos.
Du hast eine BTC Adresse eingegeben.
@@ -321,4 +322,20 @@
Sprache
Benutze Systemsprache
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values-el/help.xml b/app/src/main/res/values-el/help.xml
index 9f45ffc8..1a8312c3 100644
--- a/app/src/main/res/values-el/help.xml
+++ b/app/src/main/res/values-el/help.xml
@@ -213,4 +213,15 @@
πίσω στο προηγούμενο βήμα και μετά επιστρέφοντας στην οθόνη \"Επιβεβαίωση\".
]]>
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 831a1ee3..8fdd6195 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -32,6 +32,8 @@
Υψηλότερη προτεραιότητα = Υψηλότερα Κόμιστρα
Συναλλαγή BTC ενεργοποιήθηκε, πάτα για περισσότερες πληροφορείες.
+ CrAzYpass ενεργοποιήθηκε, πάτα για περισσότερες πληροφορείες.
+ Ledger ενεργοποιήθηκε, πάτα για περισσότερες πληροφορείες.
Έβαλες μια διεύθυνση bitcoin.
@@ -284,7 +286,6 @@
Επαναφορά πορτοφολιού από σπόρο 25-λέξεων
Change Passphrase
- CrAzYpass enabled, tap for more info.
Change Password in progress
Change Password failed!
Password changed
@@ -320,4 +321,20 @@
Language
Use System Language
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values-es/help.xml b/app/src/main/res/values-es/help.xml
index d0d1c51e..fe483b7c 100644
--- a/app/src/main/res/values-es/help.xml
+++ b/app/src/main/res/values-es/help.xml
@@ -251,4 +251,16 @@
parte de XMR.TO, esto se logra dando un paso atrás y luego volviendo a la pantalla de
\"Confirmar\".
]]>
+
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index d331f2c1..8195dde6 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -292,8 +292,10 @@
Monto\n(BTC)
Soy monerujo
Orden XMR.TO
+
Pago en BTC activado, toca para más info.
CrAzYpass activado, toca para más info.
+ Ledger activado, toca para más info.
Crear Cuenta
Cuentas
@@ -306,4 +308,20 @@
Language
Use System Language
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values-fr/help.xml b/app/src/main/res/values-fr/help.xml
index 386e5117..bdb1f3cc 100644
--- a/app/src/main/res/values-fr/help.xml
+++ b/app/src/main/res/values-fr/help.xml
@@ -244,4 +244,15 @@
XMR.TO en retournant à l’étape précédente puis en revenant à l’écran \"Confirmation\".
]]>
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index c6ab669f..5635e4c5 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -33,8 +33,8 @@
Plus Prioritaire = Plus de Frais
Paiement BTC activé, tapez pour plus d\'infos.
-
CrAzYpass activé, tapez pour plus d\'infos.
+ Ledger activé, tapez pour plus d\'infos.
Vous avez entré une adresse Bitcoin.
@@ -324,4 +324,20 @@
Language
Use System Language
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values-hu/help.xml b/app/src/main/res/values-hu/help.xml
index fe1fece1..cf4b767b 100644
--- a/app/src/main/res/values-hu/help.xml
+++ b/app/src/main/res/values-hu/help.xml
@@ -234,4 +234,15 @@
visszamész az előző lépésre, majd visszajössz a Megerősítés oldalra.
]]>
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index adf6303c..3a38acea 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -34,6 +34,7 @@
BTC fizetés engedélyezve, koppints ide a részletekért.
CrAzYpass engedélyezve, koppints ide a részletekért
+ Ledger engedélyezve, koppints ide a részletekért
Bitcoin-címet adtál meg.
@@ -44,7 +45,6 @@
%1$s BTC
-
Megerősítés folyamatban
Kifizetés folyamatban
XMR.TO-hiba (%1$s)
@@ -322,4 +322,20 @@
Nyelv
Rendszernyelv használata
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values-it/help.xml b/app/src/main/res/values-it/help.xml
index f446dfaf..42af9886 100644
--- a/app/src/main/res/values-it/help.xml
+++ b/app/src/main/res/values-it/help.xml
@@ -177,4 +177,15 @@
Non appena il conto alla rovescia arriva a zero, è necessario richiedere una nuova quotazione a XMR.TO tornando indietro al passo precedente e tornando poi di nuovo alla schermata \"Conferma\".
]]>
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 695dabba..b7b03f85 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -34,6 +34,7 @@
Pagamento BTC abilitato, tocca per maggiori informazioni.
CrAzYpass abilitato, tocca per maggiori informazioni.
+ Ledger abilitato, tocca per maggiori informazioni.
Hai inserito un indirizzo Bitcoin.
@@ -322,4 +323,20 @@
Language
Use System Language
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values-nb/help.xml b/app/src/main/res/values-nb/help.xml
index 226efa94..a1410d8d 100644
--- a/app/src/main/res/values-nb/help.xml
+++ b/app/src/main/res/values-nb/help.xml
@@ -233,4 +233,15 @@
det tidligere steget og komme tilbake til \"Bekreft\" skjermen.
]]>
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index 6eb5cccb..2ba58903 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -34,6 +34,7 @@
BTC betaling tilgjengelig, trykk for mer info.
CrAzYpass tilgjengelig, trykk for mer info.
+ Ledger tilgjengelig, trykk for mer info.
Du skrev inn en Bitcoin addresse.
@@ -320,4 +321,20 @@
Language
Use System Language
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values-pt/help.xml b/app/src/main/res/values-pt/help.xml
index 5e0533b6..8583f5ec 100644
--- a/app/src/main/res/values-pt/help.xml
+++ b/app/src/main/res/values-pt/help.xml
@@ -236,4 +236,15 @@
indo ao passo anterior e depois voltando ao ecrã de \"Confirmar\".
]]>
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index d60b3328..aeed5bc6 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -33,8 +33,8 @@
Prioridade Alta = Taxas Altas
Pagamento em BTC activado, toca para mais informação.
-
passLoUCa activa, toca para mais informação.
+ Ledger activa, toca para mais informação.
Introduziu um endereço Bitcoin.
@@ -324,4 +324,20 @@
Language
Use System Language
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values-ro/help.xml b/app/src/main/res/values-ro/help.xml
index b6c503f3..d7b3dbbd 100644
--- a/app/src/main/res/values-ro/help.xml
+++ b/app/src/main/res/values-ro/help.xml
@@ -221,4 +221,15 @@
înapoi la pasul anterior apoi revenind la ecranul \"Confirm\".
]]>
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 351e1f5d..2d190188 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -32,6 +32,8 @@
Prioritate mare = Comision mare
Plată BTC activată, apasă pentru mai multe informații.
+ CrAzYpass activată, apasă pentru mai multe informații.
+ Ledger activată, apasă pentru mai multe informații.
Ai introdus o adresă de Bitcoin.
@@ -284,7 +286,6 @@
Restaurează portofel folosind cele 25 de cuvinte mnemonice
Change Passphrase
- CrAzYpass enabled, tap for more info.
Change Password in progress
Change Password failed!
Password changed
@@ -320,4 +321,20 @@
Language
Use System Language
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values-ru/help.xml b/app/src/main/res/values-ru/help.xml
index 64a3d96e..6c01f12a 100644
--- a/app/src/main/res/values-ru/help.xml
+++ b/app/src/main/res/values-ru/help.xml
@@ -239,4 +239,15 @@
предложение, вернувшись к предыдущему шагу, а затем к экрану \"Подтверждение\".
]]>
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index cdb37f53..a45a9137 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -33,8 +33,8 @@
Высокий приоритет = Высокие комиссии
Доступны переводы в BTC, нажмите для доп. информации
-
Доступен CrAzYpass, нажмите для доп. информации
+ Доступен Ledger, нажмите для доп. информации
Вы ввели Bitcoin адрес.
@@ -323,4 +323,20 @@
Language
Use System Language
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values-sv/help.xml b/app/src/main/res/values-sv/help.xml
index f96d32ea..c1cc146f 100644
--- a/app/src/main/res/values-sv/help.xml
+++ b/app/src/main/res/values-sv/help.xml
@@ -214,4 +214,15 @@
tillbaka till den tidigare skärmen och sedan gå till \"Bekräfta\"skärmen.
]]>
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 7fd0c1d4..d0651b24 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -34,8 +34,8 @@
Högre prioritet = Högre avgift
BTC-betalning aktiverad, tryck för mer info.
-
CrAzYpass aktiverat, tryck för mer info.
+ Ledger aktiverat, tryck för mer info.
Du har angivit en Bitcoin-adress.
Du kommer att skicka XMR och mottagaren får BTC via tjänsten XMR.TO.]]>
@@ -305,4 +305,20 @@
Language
Use System Language
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values-zh-rCN/help.xml b/app/src/main/res/values-zh-rCN/help.xml
index efbe703a..17b22e41 100644
--- a/app/src/main/res/values-zh-rCN/help.xml
+++ b/app/src/main/res/values-zh-rCN/help.xml
@@ -189,4 +189,15 @@
当倒数计时归零的时候,你将会需要回到上一步再回到\"确认\"页面重新向XMR.TO寻求汇率报价
]]>
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 3a49451c..a0d3beca 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -34,6 +34,7 @@
BTC付款已启用, 点选了解更多
CrAzYpass已启用, 点选了解更多
+ Ledger已启用, 点选了解更多
你输入了Bitcoin地址
@@ -318,4 +319,20 @@
语言
使用系统语言
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values-zh-rTW/help.xml b/app/src/main/res/values-zh-rTW/help.xml
index 17ffad64..3d13f50d 100644
--- a/app/src/main/res/values-zh-rTW/help.xml
+++ b/app/src/main/res/values-zh-rTW/help.xml
@@ -189,4 +189,15 @@
當倒數計時歸零的時候,你將會需要回到上一步再回到\"確認\"頁面重新向XMR.TO尋求匯率報價
]]>
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 5a331c45..b71d37fa 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -34,6 +34,7 @@
BTC付款已啟用, 點選了解更多
CrAzYpass已啟用, 點選了解更多
+ Ledger已啟用, 點選了解更多
你輸入了Bitcoin地址
@@ -319,4 +320,20 @@
語言
使用系統語言
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values/help.xml b/app/src/main/res/values/help.xml
index 0ab73955..f74b610f 100644
--- a/app/src/main/res/values/help.xml
+++ b/app/src/main/res/values/help.xml
@@ -26,9 +26,21 @@
Enter a unique wallet name and password. The password is used for securing your wallet data on the device.
Use a strong password - even better use a passphrase.
Enter your Seed in the field \"Mnemonic Seed\".
-
If you know the block number of the first transaction used for this address, enter it in the
- field \"Restore Height\" - leaving it blank will scan the entire blockchain for
- transactions belonging to your address. This takes a long time.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
+ ]]>
+
+ Create Wallet - Ledger
+ You want to recover your wallet from your Ledger Nano S device.
+ Your secret keys never leave the Ledger device, so you need it plugged in every
+ time you want to access your wallet.
+ Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
+ device. Use a strong password - even better use a passphrase.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
]]>
Enter a unique wallet name and password. The password is used for securing your wallet data on the device.
Use a strong password - even better use a passphrase.
Enter your Monero Address in the field \"Public Address\" and fill out \"View Key\" and \"Spend Key\".
- If you know the block number of the first transaction used for this address, enter it in the
- field \"Restore Height\" - leaving it blank will scan the entire blockchain for
- transactions belonging to your address. This takes a long time.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
]]>
Enter a unique wallet name and password. The password is used for securing your wallet data on the device.
Use a strong password - even better use a passphrase.
Enter your Monero Address in the field \"Public Address\" and fill out the \"View Key\".
- If you know the block number of the first transaction used for this address, enter it in the
- field \"Restore Height\" - leaving it blank will scan the entire blockchain for
- transactions belonging to your address. This takes a long time.
+ Enter the block number of the first transaction used for this address in the
+ field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
+ enter an approximate date/blockheight before you first used this wallet address.
]]>
+ extremely difficult to hack!
This feature is mandatory for all newly created wallets.
Legacy Password
If you see your passphrase here, your wallet files are not as secure as when using
@@ -232,5 +244,4 @@
Once the countdown reaches zero, you need to get a new quote from XMR.TO by going back to the
previous step and then coming back to the \"Confirm\" screen.
]]>
-
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d50423c5..364ebb42 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -35,8 +35,8 @@
Higher Priority = Higher Fees
BTC payment enabled, tap for more info.
-
CrAzYpass enabled, tap for more info.
+ Ledger enabled, tap for more info.
You entered a Bitcoin address.
@@ -47,7 +47,6 @@
%1$s BTC
-
Confirmation Pending
Payment Pending
XMR.TO Error (%1$s)
@@ -209,6 +208,7 @@
New
Seed
View
+ Ledger
Public Address
View Key
@@ -368,4 +368,20 @@
Language
Use System Language
+
+ Restore from Ledger Nano S
+
+ Communicating with Ledger
+ Confirmation on Ledger required!
+ Retrieving subaddresses
+ Verifying keys
+ Doing crazy maths
+ Hashing stuff
+ Please (re)connect Ledger device
+
+ Creating account
+ Updating wallet
+
+ %1$s attached
+ %1$s detached
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 989031d2..9aa7aeb7 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -329,5 +329,4 @@
-
diff --git a/app/src/main/res/xml/usb_device_filter.xml b/app/src/main/res/xml/usb_device_filter.xml
new file mode 100644
index 00000000..2fc6025f
--- /dev/null
+++ b/app/src/main/res/xml/usb_device_filter.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/external-libs/monero/include/wallet2_api.h b/external-libs/monero/include/wallet2_api.h
index 546ce16a..e47a8908 100644
--- a/external-libs/monero/include/wallet2_api.h
+++ b/external-libs/monero/include/wallet2_api.h
@@ -802,6 +802,12 @@ struct Wallet
//! Initiates a light wallet import wallet request
virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status) = 0;
+
+ /*!
+ * \brief Queries if the wallet keys are on a hardware device
+ * \return true if they are
+ */
+ virtual bool isKeyOnDevice() const = 0;
};
/**
@@ -975,6 +981,17 @@ struct WalletManager
*/
virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const = 0;
+ /*!
+ * \brief determine the key storage for the specified wallet file
+ * \param keys_file_name Keys file to verify password for
+ * \param password Password to verify
+ * \return -1: incorrect password, 0 = default hw, 1 ledger hw
+ *
+ * for verification only - determines key storage hardware
+ *
+ */
+ virtual int queryWalletHardware(const std::string &keys_file_name, const std::string &password) const = 0;
+
/*!
* \brief findWallets - searches for the wallet files by given path name recursively
* \param path - starting point to search