1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-08 20:40:51 +02:00

Compare commits

...

13 Commits

Author SHA1 Message Date
m2049r
b1d91e2671 new version 2018-05-27 10:59:10 +02:00
m2049r
271cd2d4a8 deal with all broken variants (#292)
* remove variant code for arm32

* deal with all broken variants
2018-05-25 23:44:37 +02:00
m2049r
22c5a543db upgrade to v0.12.1.0 (#291) 2018-05-25 22:38:05 +02:00
m2049r
cd986860c5 remove variant code for arm32 (#290) 2018-05-25 22:37:50 +02:00
m2049r
0cf5981eae Fixes "Invalid Password" although password correct (#289)
* don't log warning

* fix cn_slow_hash variant&prehash
cn_slow_hash signature was changed in monero-core but the linker didn't
notice - also added code to support wallets created with variant &
prehash enabled
2018-05-25 18:20:48 +02:00
m2049r
e109df34f0 remove if save fails (#281) 2018-05-25 18:20:27 +02:00
m2049r
5a7aa6cc77 testnet => stagenet (#288) 2018-05-25 18:19:29 +02:00
m2049r
f50629ff81 check for encoded pw (#280) 2018-05-10 15:26:44 +02:00
m2049r
cb12d64e5f Various Fixes (#279)
* load password only if it's passed

* cancel fingerprint if password entered

* rework fingerprint code

* cleanup unused params

* new version code
2018-05-10 13:48:11 +02:00
m2049r
a8f08fb9b9 new version 2018-05-06 17:46:48 +02:00
m2049r
3e9be418a8 removed removed strings (#276) 2018-05-06 12:30:01 +02:00
KillASIC.com
fa5dc9988d Monero translation to simplified chinese. (#263) 2018-05-06 12:17:09 +02:00
m2049r
857cf8d6d8 Random fixes (#275)
* reduce label length

* api fix

* show correct password after change
2018-05-06 12:12:53 +02:00
27 changed files with 869 additions and 210 deletions

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 25
versionCode 91
versionName "1.5.1 'CrAzY Nacho'"
versionCode 94
versionName "1.5.4 'CrAzY Nacho'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {

View File

@@ -697,16 +697,26 @@ Java_com_m2049r_xmrwallet_model_Wallet_isSynchronized(JNIEnv *env, jobject insta
//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_cnSlowHash(JNIEnv *env, jobject clazz,
jbyteArray data) {
Java_com_m2049r_xmrwallet_util_KeyStoreHelper_slowHash(JNIEnv *env, jobject clazz,
jbyteArray data, jint brokenVariant) {
char hash[HASH_SIZE];
jsize size = env->GetArrayLength(data);
if ((brokenVariant > 0) && (size < 200 /*sizeof(union hash_state)*/)) {
return nullptr;
}
jbyte *buffer = env->GetByteArrayElements(data, NULL);
jsize size = env->GetArrayLength(data);
char hash[HASH_SIZE];
cn_slow_hash(buffer, (size_t) size, hash);
switch (brokenVariant) {
case 1:
slow_hash_broken(buffer, hash, 1);
break;
case 2:
slow_hash_broken(buffer, hash, 0);
break;
default: // not broken
slow_hash(buffer, (size_t) size, hash);
}
env->ReleaseByteArrayElements(data, buffer, JNI_ABORT); // do not update java byte[]
jbyteArray result = env->NewByteArray(HASH_SIZE);
env->SetByteArrayRegion(result, 0, HASH_SIZE, (jbyte *) hash);
return result;
@@ -757,8 +767,6 @@ Java_com_m2049r_xmrwallet_model_Wallet_isAddressValid(JNIEnv *env, jobject clazz
return static_cast<jboolean>(isValid);
}
//TODO static static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error);
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getPaymentIdFromAddress(JNIEnv *env, jobject clazz,
jstring address,

View File

@@ -60,7 +60,15 @@ enum {
HASH_DATA_AREA = 136
};
void cn_slow_hash(const void *data, size_t length, char *hash);
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed);
inline void slow_hash(const void *data, const size_t length, char *hash) {
cn_slow_hash(data, length, hash, 0 /* variant */, 0/*prehashed*/);
}
inline void slow_hash_broken(const void *data, char *hash, int variant) {
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/);
}
#ifdef __cplusplus
}

View File

@@ -53,7 +53,6 @@ import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.io.File;
import java.security.KeyStoreException;
import timber.log.Timber;
@@ -62,27 +61,26 @@ public class GenerateReviewFragment extends Fragment {
static final public String VIEW_TYPE_ACCEPT = "accept";
static final public String VIEW_TYPE_WALLET = "wallet";
ScrollView scrollview;
public static final String REQUEST_TYPE = "type";
public static final String REQUEST_PATH = "path";
public static final String REQUEST_PASSWORD = "password";
ProgressBar pbProgress;
TextView tvWalletPassword;
TextView tvWalletAddress;
TextView tvWalletMnemonic;
TextView tvWalletViewKey;
TextView tvWalletSpendKey;
ImageButton bCopyAddress;
LinearLayout llAdvancedInfo;
LinearLayout llPassword;
Button bAdvancedInfo;
Button bAccept;
private ScrollView scrollview;
// TODO fix visibility of variables
String walletPath;
String walletName;
// we need to keep the password so the user is not asked again if they want to change it
// note they can only enter this fragment immediately after entering the password
// so asking them to enter it a couple of seconds later seems silly
String walletPassword = null;
private ProgressBar pbProgress;
private TextView tvWalletPassword;
private TextView tvWalletAddress;
private TextView tvWalletMnemonic;
private TextView tvWalletViewKey;
private TextView tvWalletSpendKey;
private ImageButton bCopyAddress;
private LinearLayout llAdvancedInfo;
private LinearLayout llPassword;
private Button bAdvancedInfo;
private Button bAccept;
private String walletPath;
private String walletName;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -104,10 +102,10 @@ public class GenerateReviewFragment extends Fragment {
bAccept = (Button) view.findViewById(R.id.bAccept);
boolean testnet = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
tvWalletMnemonic.setTextIsSelectable(testnet);
tvWalletSpendKey.setTextIsSelectable(testnet);
tvWalletPassword.setTextIsSelectable(testnet);
boolean allowCopy = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
tvWalletMnemonic.setTextIsSelectable(allowCopy);
tvWalletSpendKey.setTextIsSelectable(allowCopy);
tvWalletPassword.setTextIsSelectable(allowCopy);
bAccept.setOnClickListener(new View.OnClickListener() {
@Override
@@ -136,14 +134,14 @@ public class GenerateReviewFragment extends Fragment {
});
Bundle args = getArguments();
type = args.getString("type");
walletPath = args.getString("path");
showDetails(args.getString("password"));
type = args.getString(REQUEST_TYPE);
walletPath = args.getString(REQUEST_PATH);
localPassword = args.getString(REQUEST_PASSWORD);
showDetails();
return view;
}
void showDetails(String password) {
walletPassword = password;
void showDetails() {
showProgress();
tvWalletPassword.setText(null);
new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, walletPath);
@@ -178,7 +176,7 @@ public class GenerateReviewFragment extends Fragment {
private void acceptWallet() {
bAccept.setEnabled(false);
acceptCallback.onAccept(walletName, walletPassword);
acceptCallback.onAccept(walletName, getPassword());
}
private class AsyncShow extends AsyncTask<String, Void, Boolean> {
@@ -201,7 +199,7 @@ public class GenerateReviewFragment extends Fragment {
wallet = GenerateReviewFragment.this.walletCallback.getWallet();
closeWallet = false;
} else {
wallet = WalletManager.getInstance().openWallet(walletPath, walletPassword);
wallet = WalletManager.getInstance().openWallet(walletPath, getPassword());
closeWallet = true;
}
name = wallet.getName();
@@ -231,10 +229,8 @@ public class GenerateReviewFragment extends Fragment {
bAccept.setVisibility(View.VISIBLE);
bAccept.setEnabled(true);
}
if (walletPassword != null) {
llPassword.setVisibility(View.VISIBLE);
tvWalletPassword.setText(walletPassword);
}
llPassword.setVisibility(View.VISIBLE);
tvWalletPassword.setText(getPassword());
tvWalletAddress.setText(address);
tvWalletMnemonic.setText(seed);
tvWalletViewKey.setText(viewKey);
@@ -260,6 +256,7 @@ public class GenerateReviewFragment extends Fragment {
ProgressListener progressCallback = null;
AcceptListener acceptCallback = null;
ListenerWithWallet walletCallback = null;
PasswordChangedListener passwordCallback = null;
public interface Listener {
void setTitle(String title, String subtitle);
@@ -282,6 +279,24 @@ public class GenerateReviewFragment extends Fragment {
Wallet getWallet();
}
public interface PasswordChangedListener {
void onPasswordChanged(String newPassword);
String getPassword();
}
private String localPassword = null;
private String getPassword() {
if (passwordCallback != null) return passwordCallback.getPassword();
return localPassword;
}
private void setPassword(String password) {
if (passwordCallback != null) passwordCallback.onPasswordChanged(password);
else localPassword = password;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
@@ -297,6 +312,9 @@ public class GenerateReviewFragment extends Fragment {
if (context instanceof ListenerWithWallet) {
this.walletCallback = (ListenerWithWallet) context;
}
if (context instanceof PasswordChangedListener) {
this.passwordCallback = (PasswordChangedListener) context;
}
}
@Override
@@ -328,7 +346,7 @@ public class GenerateReviewFragment extends Fragment {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
String type = getArguments().getString("type");
String type = getArguments().getString(REQUEST_TYPE); // intance variable <type> not set yet
if (GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type)) {
inflater.inflate(R.menu.wallet_details_help_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
@@ -345,7 +363,7 @@ public class GenerateReviewFragment extends Fragment {
wallet = GenerateReviewFragment.this.walletCallback.getWallet();
closeWallet = false;
} else {
wallet = WalletManager.getInstance().openWallet(walletPath, walletPassword);
wallet = WalletManager.getInstance().openWallet(walletPath, getPassword());
closeWallet = true;
}
@@ -373,19 +391,19 @@ public class GenerateReviewFragment extends Fragment {
@Override
protected Boolean doInBackground(String... params) {
if (params.length != 4) return false;
File walletFile = Helper.getWalletFile(getActivity(), params[0]);
String oldPassword = params[1];
String userPassword = params[2];
boolean fingerprintAuthAllowed = Boolean.valueOf(params[3]);
if (params.length != 2) return false;
final String userPassword = params[0];
final boolean fingerPassValid = Boolean.valueOf(params[1]);
newPassword = KeyStoreHelper.getCrazyPass(getActivity(), userPassword);
boolean success = changeWalletPassword(newPassword);
final boolean success = changeWalletPassword(newPassword);
if (success) {
if (fingerprintAuthAllowed) {
KeyStoreHelper.saveWalletUserPass(getActivity(), walletName, userPassword);
} else {
KeyStoreHelper.removeWalletUserPass(getActivity(), walletName);
}
Context ctx = getActivity();
if (ctx != null)
if (fingerPassValid) {
KeyStoreHelper.saveWalletUserPass(ctx, walletName, userPassword);
} else {
KeyStoreHelper.removeWalletUserPass(ctx, walletName);
}
}
return success;
}
@@ -393,14 +411,15 @@ public class GenerateReviewFragment extends Fragment {
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (getActivity().isDestroyed()) {
if ((getActivity() == null) || getActivity().isDestroyed()) {
return;
}
if (progressCallback != null)
progressCallback.dismissProgressDialog();
if (result) {
Toast.makeText(getActivity(), getString(R.string.changepw_success), Toast.LENGTH_SHORT).show();
showDetails(newPassword);
setPassword(newPassword);
showDetails();
} else {
Toast.makeText(getActivity(), getString(R.string.changepw_failed), Toast.LENGTH_LONG).show();
}
@@ -447,11 +466,7 @@ public class GenerateReviewFragment extends Fragment {
}
});
try {
swFingerprintAllowed.setChecked(FingerprintHelper.isFingerprintAuthAllowed(walletName));
} catch (KeyStoreException ex) {
ex.printStackTrace();
}
swFingerprintAllowed.setChecked(FingerprintHelper.isFingerPassValid(getActivity(), walletName));
}
etPasswordA.getEditText().addTextChangedListener(new TextWatcher() {
@@ -527,7 +542,7 @@ public class GenerateReviewFragment extends Fragment {
} else if (!newPasswordA.equals(newPasswordB)) {
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
} else if (newPasswordA.equals(newPasswordB)) {
new AsyncChangePassword().execute(walletName, walletPassword, newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
Helper.hideKeyboardAlways(getActivity());
openDialog.dismiss();
openDialog = null;
@@ -549,7 +564,7 @@ public class GenerateReviewFragment extends Fragment {
} else if (!newPasswordA.equals(newPasswordB)) {
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
} else if (newPasswordA.equals(newPasswordB)) {
new AsyncChangePassword().execute(walletName, walletPassword, newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
Helper.hideKeyboardAlways(getActivity());
openDialog.dismiss();
openDialog = null;

View File

@@ -50,7 +50,6 @@ import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.FingerprintHelper;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
@@ -63,7 +62,6 @@ import java.io.IOException;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.channels.FileChannel;
import java.security.KeyStoreException;
import java.util.Date;
import timber.log.Timber;
@@ -229,17 +227,20 @@ public class LoginActivity extends SecureActivity
@Override
protected Boolean doInBackground(String... params) {
if (params.length != 2) return false;
File walletFile = Helper.getWalletFile(LoginActivity.this, params[0]);
String oldName = params[0];
String newName = params[1];
File walletFile = Helper.getWalletFile(LoginActivity.this, oldName);
boolean success = renameWallet(walletFile, newName);
try {
if (success && FingerprintHelper.isFingerprintAuthAllowed(params[0])) {
String savedPass = KeyStoreHelper.loadWalletUserPass(LoginActivity.this, params[0]);
if (success) {
String savedPass = KeyStoreHelper.loadWalletUserPass(LoginActivity.this, oldName);
KeyStoreHelper.saveWalletUserPass(LoginActivity.this, newName, savedPass);
KeyStoreHelper.removeWalletUserPass(LoginActivity.this, params[0]);
}
} catch (KeyStoreException ex) {
ex.printStackTrace();
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
Timber.w(ex);
} finally {
// we have either set a new password or it is broken - kill the old one either way
KeyStoreHelper.removeWalletUserPass(LoginActivity.this, oldName);
}
return success;
}
@@ -1006,11 +1007,11 @@ public class LoginActivity extends SecureActivity
case R.id.action_privacy_policy:
PrivacyFragment.display(getSupportFragmentManager());
return true;
case R.id.action_testnet:
case R.id.action_stagenet:
try {
LoginFragment loginFragment = (LoginFragment)
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
item.setChecked(loginFragment.onTestnetMenuItem());
item.setChecked(loginFragment.onStagenetMenuItem());
} catch (ClassCastException ex) {
// never mind then
}

View File

@@ -18,15 +18,12 @@ package com.m2049r.xmrwallet;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -41,14 +38,11 @@ import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.WalletManager;
@@ -343,69 +337,65 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.list_menu, menu);
menu.findItem(R.id.action_testnet).setChecked(testnetCheckMenu);
menu.findItem(R.id.action_stagenet).setChecked(stagenetCheckMenu);
super.onCreateOptionsMenu(menu, inflater);
}
private boolean testnetCheckMenu = BuildConfig.DEBUG;
private boolean stagenetCheckMenu = BuildConfig.DEBUG;
//boolean isTestnet() {
// return testnet;
//}
public boolean onTestnetMenuItem() {
boolean lastState = testnetCheckMenu;
public boolean onStagenetMenuItem() {
boolean lastState = stagenetCheckMenu;
setNet(!lastState, true); // set and save
return !lastState;
}
public void setNet(boolean testnetChecked, boolean save) {
this.testnetCheckMenu = testnetChecked;
NetworkType net = testnetChecked ? NetworkType.NetworkType_Testnet : NetworkType.NetworkType_Mainnet;
public void setNet(boolean stagenetChecked, boolean save) {
this.stagenetCheckMenu = stagenetChecked;
NetworkType net = stagenetChecked ? NetworkType.NetworkType_Stagenet : NetworkType.NetworkType_Mainnet;
activityCallback.setNetworkType(net);
activityCallback.showNet();
if (save) {
savePrefs(true); // use previous state as we just clicked it
}
if (testnetChecked) {
setDaemon(daemonTestNet);
if (stagenetChecked) {
setDaemon(daemonStageNet);
} else {
setDaemon(daemonMainNet);
}
loadList();
}
private static final String PREF_DAEMON_TESTNET = "daemon_testnet";
private static final String PREF_DAEMON_STAGENET = "daemon_stagenet";
private static final String PREF_DAEMON_MAINNET = "daemon_mainnet";
private static final String PREF_DAEMONLIST_MAINNET =
"node.moneroworld.com:18089;node.xmrbackb.one;node.xmr.be";
private static final String PREF_DAEMONLIST_TESTNET =
"testnet.xmrchain.net";
private static final String PREF_DAEMONLIST_STAGENET =
"stagenet.xmr-tw.org";
private NodeList daemonTestNet;
private NodeList daemonStageNet;
private NodeList daemonMainNet;
void loadPrefs() {
SharedPreferences sharedPref = activityCallback.getPrefs();
daemonMainNet = new NodeList(sharedPref.getString(PREF_DAEMON_MAINNET, PREF_DAEMONLIST_MAINNET));
daemonTestNet = new NodeList(sharedPref.getString(PREF_DAEMON_TESTNET, PREF_DAEMONLIST_TESTNET));
setNet(testnetCheckMenu, false);
daemonStageNet = new NodeList(sharedPref.getString(PREF_DAEMON_STAGENET, PREF_DAEMONLIST_STAGENET));
setNet(stagenetCheckMenu, false);
}
void savePrefs() {
savePrefs(false);
}
void savePrefs(boolean usePreviousTestnetState) {
Timber.d("SAVE / %s", usePreviousTestnetState);
void savePrefs(boolean usePreviousNetState) {
Timber.d("SAVE / %s", usePreviousNetState);
// save the daemon address for the net
boolean testnet = testnetCheckMenu ^ usePreviousTestnetState;
boolean stagenet = stagenetCheckMenu ^ usePreviousNetState;
String daemon = getDaemon();
if (testnet) {
daemonTestNet.setRecent(daemon);
if (stagenet) {
daemonStageNet.setRecent(daemon);
} else {
daemonMainNet.setRecent(daemon);
}
@@ -413,7 +403,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
SharedPreferences sharedPref = activityCallback.getPrefs();
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(PREF_DAEMON_MAINNET, daemonMainNet.toString());
editor.putString(PREF_DAEMON_TESTNET, daemonTestNet.toString());
editor.putString(PREF_DAEMON_STAGENET, daemonStageNet.toString());
editor.apply();
}

View File

@@ -55,6 +55,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
WalletService.Observer, SendFragment.Listener, TxFragment.Listener,
GenerateReviewFragment.ListenerWithWallet,
GenerateReviewFragment.Listener,
GenerateReviewFragment.PasswordChangedListener,
ScannerFragment.OnScannedListener, ReceiveFragment.Listener,
SendAddressWizardFragment.OnScanListener {
@@ -65,6 +66,18 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
private Toolbar toolbar;
private boolean needVerifyIdentity;
private String password;
@Override
public void onPasswordChanged(String newPassword) {
password = newPassword;
}
@Override
public String getPassword() {
return password;
}
@Override
public void setToolbarButton(int type) {
toolbar.setButton(type);
@@ -119,9 +132,9 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
if (extras != null) {
acquireWakeLock();
String walletId = extras.getString(REQUEST_ID);
String walletPassword = extras.getString(REQUEST_PW);
needVerifyIdentity = extras.getBoolean(REQUEST_FINGERPRINT_USED);
connectWalletService(walletId, walletPassword);
password = extras.getString(REQUEST_PW);
connectWalletService(walletId, password);
} else {
finish();
//throw new IllegalStateException("No extras passed! Panic!");
@@ -186,7 +199,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
}
}
public void onWalletChangePassword() {//final String walletName, final String walletPassword) {
public void onWalletChangePassword() {
try {
GenerateReviewFragment detailsFragment = (GenerateReviewFragment)
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
@@ -709,8 +722,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
final Bundle extras = new Bundle();
extras.putString("type", GenerateReviewFragment.VIEW_TYPE_WALLET);
extras.putString("password", getIntent().getExtras().getString(REQUEST_PW));
extras.putString(GenerateReviewFragment.REQUEST_TYPE, GenerateReviewFragment.VIEW_TYPE_WALLET);
if (needVerifyIdentity) {
Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() {

View File

@@ -233,7 +233,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private boolean isBitcoinAddress() {
String address = etAddress.getEditText().getText().toString();
if ((address.length() >= 27) && (address.length() <= 34))
if ((address.length() >= 27) && (address.length() <= 35))
return BitcoinAddressValidator.validate(address);
else
return false;

View File

@@ -247,7 +247,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
private XmrToApi xmrToApi = null;
private final XmrToApi getXmrToApi() {
private XmrToApi getXmrToApi() {
if (xmrToApi == null) {
synchronized (this) {
if (xmrToApi == null) {

View File

@@ -164,9 +164,7 @@ public class Wallet {
public static native boolean isAddressValid(String address, int networkType);
//TODO static static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error);
public static native String getPaymentIdFromAddress(String address, boolean isTestNet);
public static native String getPaymentIdFromAddress(String address, int networkType);
public static native long getMaximumAllowedAmount();

View File

@@ -228,7 +228,6 @@ public class WalletManager {
public String getDaemonAddress() {
if (daemonAddress == null) {
// assume testnet not explicitly initialised
throw new IllegalStateException("use setDaemon() to initialise daemon and net first!");
}
return this.daemonAddress;
@@ -236,13 +235,13 @@ public class WalletManager {
private native void setDaemonAddressJ(String address);
String daemonUsername = "";
private String daemonUsername = "";
public String getDaemonUsername() {
return daemonUsername;
}
String daemonPassword = "";
private String daemonPassword = "";
public String getDaemonPassword() {
return daemonPassword;

View File

@@ -5,8 +5,7 @@ import android.content.Context;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v4.os.CancellationSignal;
import java.security.KeyStore;
import java.security.KeyStoreException;
import timber.log.Timber;
public class FingerprintHelper {
@@ -20,15 +19,13 @@ public class FingerprintHelper {
fingerprintManager.hasEnrolledFingerprints();
}
public static boolean isFingerprintAuthAllowed(String wallet) throws KeyStoreException {
KeyStore keyStore = KeyStore.getInstance(KeyStoreHelper.SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
public static boolean isFingerPassValid(Context context, String wallet) {
try {
keyStore.load(null);
} catch (Exception ex) {
throw new IllegalStateException("Could not load KeyStore", ex);
KeyStoreHelper.loadWalletUserPass(context, wallet);
return true;
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
return false;
}
return keyStore.containsAlias(KeyStoreHelper.SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet);
}
public static void authenticate(Context context, CancellationSignal cancelSignal,
@@ -36,5 +33,4 @@ public class FingerprintHelper {
FingerprintManagerCompat manager = FingerprintManagerCompat.from(context);
manager.authenticate(null, 0, cancelSignal, callback, null);
}
}

View File

@@ -64,7 +64,6 @@ import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.security.KeyStoreException;
import java.util.Locale;
import javax.net.ssl.HttpsURLConnection;
@@ -303,6 +302,16 @@ public class Helper {
else return "";
}
public static byte[] hexToBytes(String hex) {
final int len = hex.length();
final byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16));
}
return data;
}
static public void setMoneroHome(Context context) {
try {
String home = getStorage(context, HOME_DIR).getAbsolutePath();
@@ -352,6 +361,18 @@ public class Helper {
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)) {
return brokenCrazyPass2;
}
String brokenCrazyPass1 = KeyStoreHelper.getBrokenCrazyPass(context, password, 1);
if ((brokenCrazyPass1 != null)
&& WalletManager.getInstance().verifyWalletPassword(walletPath, brokenCrazyPass1, true)) {
return brokenCrazyPass1;
}
return null;
}
@@ -368,12 +389,7 @@ public class Helper {
final TextInputLayout etPassword = (TextInputLayout) promptsView.findViewById(R.id.etPassword);
etPassword.setHint(context.getString(R.string.prompt_password, wallet));
boolean fingerprintAuthCheck;
try {
fingerprintAuthCheck = FingerprintHelper.isFingerprintAuthAllowed(wallet);
} catch (KeyStoreException ex) {
fingerprintAuthCheck = false;
}
final boolean fingerprintAuthCheck = FingerprintHelper.isFingerPassValid(context, wallet);
final boolean fingerprintAuthAllowed = !fingerprintDisabled && fingerprintAuthCheck;
final CancellationSignal cancelSignal = new CancellationSignal();
@@ -425,13 +441,18 @@ public class Helper {
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
String userPass = KeyStoreHelper.loadWalletUserPass(context, wallet);
if (Helper.processPasswordEntry(context, wallet, userPass, true, action)) {
Helper.hideKeyboardAlways((Activity) context);
openDialog.dismiss();
openDialog = null;
} else {
try {
String userPass = KeyStoreHelper.loadWalletUserPass(context, wallet);
if (Helper.processPasswordEntry(context, wallet, userPass, true, action)) {
Helper.hideKeyboardAlways((Activity) context);
openDialog.dismiss();
openDialog = null;
} else {
etPassword.setError(context.getString(R.string.bad_password));
}
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
etPassword.setError(context.getString(R.string.bad_password));
// TODO: better errror message here - what would it be?
}
}
@@ -455,6 +476,7 @@ public class Helper {
String pass = etPassword.getEditText().getText().toString();
if (processPasswordEntry(context, wallet, pass, false, action)) {
Helper.hideKeyboardAlways((Activity) context);
cancelSignal.cancel();
openDialog.dismiss();
openDialog = null;
} else {
@@ -472,6 +494,7 @@ public class Helper {
String pass = etPassword.getEditText().getText().toString();
if (processPasswordEntry(context, wallet, pass, false, action)) {
Helper.hideKeyboardAlways((Activity) context);
cancelSignal.cancel();
openDialog.dismiss();
openDialog = null;
} else {

View File

@@ -3,10 +3,10 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_testnet"
android:id="@+id/action_stagenet"
android:checkable="true"
android:orderInCategory="100"
android:title="@string/menu_testnet"
android:title="@string/menu_stagenet"
app:showAsAction="never" />
<item

View File

@@ -1,7 +1,6 @@
<resources>
<string name="wallet_activity_name">Wallet</string>
<string name="menu_testnet">Testnet</string>
<string name="menu_about">Über</string>
<string name="menu_privacy">Datenschutzerklärung</string>
@@ -224,7 +223,7 @@
<string name="send_paymentid_hint">Zahlungs-ID (optional)</string>
<string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">Private Notizen (optional)</string>
<string name="send_generate_paymentid_hint">Generieren</string>
<string name="send_generate_paymentid_hint">Erzeuge</string>
<string name="send_qr_hint">Scannen</string>
<string name="send_send_label">Gib meine wertvollen Moneroj aus</string>
<string name="send_send_timed_label">Gib meine wertvollen Moneroj aus (%1$s)</string>

View File

@@ -1,7 +1,6 @@
<resources>
<string name="wallet_activity_name">Monedero</string>
<string name="menu_testnet">Testnet</string>
<string name="menu_about">Acerca De</string>
<string name="menu_privacy">Política de Privacidad</string>

View File

@@ -1,7 +1,6 @@
<resources>
<string name="wallet_activity_name">Portefeuille</string>
<string name="menu_testnet">Testnet</string>
<string name="menu_about">À Propos</string>
<string name="menu_privacy">Politique de Confidentialité</string>

View File

@@ -1,7 +1,6 @@
<resources>
<string name="wallet_activity_name">Portafoglio</string>
<string name="menu_testnet">Testnet</string>
<string name="menu_about">Informazioni</string>
<string name="menu_privacy">Politiche Privacy</string>

View File

@@ -1,7 +1,6 @@
<resources>
<string name="wallet_activity_name">Lommebok</string>
<string name="menu_testnet">Testnett</string>
<string name="menu_about">Om</string>
<string name="menu_privacy">Personvernserklæring</string>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="about_close">关闭</string>
<string name="about_whoami">我是 monerujo</string>
<string name="about_version">版本 %1$s (%2$d)</string>
<string name="credits_text"><![CDATA[
<b>感谢</b>
<br/>
m2049r, baltsar777, anhdres, keejef,
rehrar, EarlOfEgo et al.
<br/><br/>
<a href="https://monerujo.io/">monerujo.io</a>
]]></string>
<string name="privacy_policy"><![CDATA[
<h1>隐私政策</h1>
<p>本页将帮助你了解 monerujo: Monero Wallet 是如何收集、使用以及披露用户信息。</p>
<p>当你使用本 App 时,代表您已经明确同意本隐私政策。</p>
<h2>信息收集方式</h2>
<p>个人信息资料指:可以辨认个人信息的任意资料。</p>
<p>Monerujo 只会在本地端使用您的 Monero 私钥以及公开地址执行必要的处理,并在加密后发送至
Monero 网络进行交易。</p>
<p>其他个人信息都不会被 monerujo 收集。</p>
<p>如果你使用 USD/EUR 外汇的功能(可选用)monerujo 將通过 kraken.com 的公开
API 抓取当前汇率。如果你想了解自己被收集的信息如何被使用,请访问 https://www.kraken.com/legal/privacy
查看他们的隐私政策特別是「Information We Collect Automatically」章节。</p>
<p>如果你想使用本 App 支付款项至比特币地址,您将使用 XMR.TO 所提供的服务。Monerujo
将发送比特币的目标地址以及金额至 XMR.TO您的 IP 在此时也可能会被收集。详情请至 https://xmr.to/
查看他们的隐私政策。</p>
<h2>应用程序权限</h2>
<ul>
<li>INTERNET : 通过 Monero 节点连接至 Monero 网络。</li>
<li>READ_EXTERNAL_STORAGE : 读取手机上的钱包信息。</li>
<li>WRITE_EXTERNAL_STORAGE : 在手机上存储钱包信息。</li>
<li>WAKE_LOCK : 与远程节点同步时,防止手机进入待机状态。</li>
<li>CAMERA : 扫描二维码以接收 Monero。</li>
</ul>
<h2>政策更新</h2>
<p>本隐私政策可能会不定时更新。在更新的同时,我們也会在本 App 以及网站 (www.monerujo.io)
上公告新的隐私政策或改动条款。但我们仍建议您定时查看本隐私政策以及时了解任何的修改。
<p>本隐私政策最后更新日期为 2017 年 11 月 10 日。
</p>
<h2>联系我们</h2>
<p>如果您对隐私政策或个人资料处理仍有疑问,欢迎发送电子邮件至 privacy@monerujo.io。</p>
]]></string>
</resources>

View File

@@ -0,0 +1,192 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="help_create_new"><![CDATA[
<h1>建立全新的钱包</h1>
<p>如果你需要一个新的Monero钱包地址!</p>
<p>请输入一个独特的钱包名称和钱包密码。
密码用于保护你在该设备上的钱包资料,请使用足够强度的密码。</p>
<h2>请抄写你的种子密语!</h2>
<p>在下一个步骤将会显示你的25个字 \"种子密语\"。
这是你在未来恢复钱包并取得完整权限时所需的唯一信息。
妥善保护好这份种子密语是相当重要的事情,因为它可以给予<em>任何人</em>
对你的钱包操作的权限!</p>
<p>如果你忘记钱包密码,你依然能够使用种子密语来恢复你的钱包。</p>
<p>沒有任何方法能够恢复你的种子密语,如果你弄丟了种子密语将会失去你的所有资金!
种子密语是无法更改的,若种子密语被窃取或泄露,你将必须立即将资金转移到新的钱包中(对应一份新种子密语)。
因此,最好的方式是将种子密语写下来,存储在<em>多个</em>安全的地方。</p>
]]></string>
<string name="help_create_seed"><![CDATA[
<h1>用种子密语恢复钱包</h1>
<p>如果你已经有一个钱包地址并想要在区块链上恢复它!</p>
<p>请输入钱包名称(不得重复)和密码。
这个密码是用来保护钱包在该设备上的信息,请使用足够强度的密码。</p>
<p>在对应位置输入你的25字种子密语。<p>
<p>若你知道你的第一笔交易是发生在哪个区块高度的话,请在\"恢复高度\"中
输入数值 - 若此处留空不输入,则将扫描<em>整个</em>区块链以寻找属于你钱包的交易与
余额,将会花费<em>较长</em>的时间。</p>
]]></string>
<string name="help_create_keys"><![CDATA[
<h1>用密钥恢复钱包</h1>
<p>如果你已经有一个钱包地址,并想用密钥恢复它。</p>
<p>请输入钱包名称(不得重复)和密码。
这个密码是用来保护钱包在该设备上的信息,请使用足够强度的密码。</p>
<p>在栏位\"公开地址\"中输入你的Monero地址并填妥\"查看密钥\"与\"支付密钥\"。</p>
<p>若你知道你的第一笔交易是发生在哪个区块高度的話,请在栏位\"恢复高度\"中
输入数值 - 若此栏留空则将会扫描<em>整个</em>区块链以寻找属于你钱包的交易与
余额,将会支付<em>较长</em>的时间。</p>
]]></string>
<string name="help_create_view"><![CDATA[
<h1>建立唯读钱包</h1>
<p>如果你只是想查看一个钱包的存入金额!</p>
<p>请输入不得重复的钱包名称与钱包密码。
这个密码是用来保护钱包在该设备上的信息,请使用足够强度的密码。</p>
<p>在栏位\"公开地址\"中输入你的Monero地址并填妥\"查看密钥\"。</p>
<p>若你知道你的第一笔交易是发生在哪个区块高度的話,请在栏位\"恢复高度\"中
输入数值 - 若此栏留空则将会扫描<em>整个</em>区块链以寻找属于你钱包的交易与
余额,将会支付<em>较长</em>的时间。</p>
]]></string>
<string name="help_details"><![CDATA[
<h1>钱包详细信息</h1>
<h2>公开地址</h2>
你的公开地址就像是银行账号一样可以与他人分享而不需要担心
会失去你的Monero其他人可以透过这个地址发送Monero到你的钱包。
<h2>记忆种子码</h2>
这是你唯一需要用来恢复钱包的信息并且可以用来取用钱包的完全权限。妥善保护好这份种子码是相当重要的事
情,因为它可以给予<em>任何人</em>对你的钱包操作的权限!如果你还尚未在安全的地方抄写保存这份种子码,请尽速进行。
<h2>钱包档恢复密码</h2>
请务必抄写下此份密码。当你重置手机或重新安裝APP时你将会需要用它来重新存取钱包档。<br/>
<h3>CrAzYpass加密</h3>
若你的密码显示为4组的52英数字元恭喜你!
你的钱包档正由256位元的密钥所保护着这是本设备基于你自己设定的密码所产生(新建或更改密码时皆会产生)。
如此可以大幅提高钱包档被骇客破解的难度。<br/>
所有新建立的钱包预设都会启用这个加密功能。
<h3>旧式密码</h3>
如果你在此看到你的密码代表着你的钱包并沒有启用CrAzYpass加密以提高安全性。
若要启用,只需要在选单中点选\"更改密码\",在输入新的密码后(即使是与先前的密码相同)APP将会对
你的钱包档进行CrAzYpass加密请将密码抄写下来!
<h3>CrAzYpass加密的钱包</h3>
如果你需要重新安裝 Monerujo (譬如将手机重置或换了新的手机),或是你想要将钱包档在別的设备或电脑上使用,你将必须
使用钱包恢复密码才能重新存取钱包。<br/>
当你在选单中点选 \"更改密码\"时,可以更换新的密码,但是请注意这同时会产生一组新的钱包恢复密码,请抄写下来!
<h2>查看密钥</h2>
查看密钥可以被用在查看钱包的入账金额而不包含支付钱包资金的权限。
<h2>支付密钥</h2>
这个支付公钥可以给予任何人支付你的钱包资金的权力,因此不要向任何人泄漏这份密钥,就如同种子码需要被妥善保存。
]]></string>
<string name="help_list"><![CDATA[
<h1>钱包列表</h1>
<h2>节点</h2>
<p>Monerujo使用远端节点连接至Monero网络而不需要下载与储存整个区块链。
你可以在这个网站找到几个热门的公共远端节点或是学习如何自行架设节点 https://moneroworld.com/</p>
<p>Monerujo 会预设数个远端节点可供使用。并且自动记忆最近五个使用过的节点</p>
<h2>钱包</h2>
<p>在这你可以看到你的钱包们。他们被储存在位于设备內部储存空间的<tt>monerujo</tt>资料夹中。
你可以利用档案浏览器APP找到他们。你应该在设备以外的地方备份他们以免你的设备爆炸或失窃后的损失。</p>
<p>选择一个钱包开启或点选\"+\"符号以建立一个新的。或是选择以下的操作:</p>
<h3>详细信息</h3>
<p>显示钱包详细信息如种子码 &amp; 密钥。</p>
<h3>接收</h3>
<p>产生一个QR码以接收Monero。</p>
<h3>重新命名</h3>
<p>重新命名钱包名称,已备份的钱包不会被更改名称</p>
<h3>备份</h3>
<p>建立一份钱包副本在<tt>monerujo</tt>中的<tt>backups</tt>资料夹內并覆写上一次的备份。</p>
<h3>封存</h3>
<p>将建立一份备份后刪除钱包,钱包副本将会留存于<tt>backups</tt>资料夹中。
若你再也不需要这钱包则可利用档案浏览器APP或可安全刪除档案APP将之刪除。</p>
]]></string>
<string name="help_wallet"><![CDATA[
<h1>钱包介面</h1>
<h2>扫描</h2>
由于Monero的交易是隐私保护的因此每当你开启Monerujo钱包时我们必须
在区块链上扫描是否有最新的Monero被传送到你的钱包中将会储存仅与你钱包相关的信息在设备中。
若你有好一段时间沒有使用钱包,那这个步骤将会支付你一些时间。
<h2>余额</h2>
<p><b>救命啊! 我的钱包余额不见了/未确认!</b><br/>
別紧张,当你发送交易出去时,你的部分余额可能会暂时显示未确认。
这只是Monero在区块链上进行找零的过程。
想了解更多关于找零的过程请参阅 https://getmonero.org/resources/moneropedia/change.html
<h2>交易列表</h2>
<p>钱包交易的列表。在唯读钱包中,仅会显示接收到的交易。</p>
]]></string>
<string name="help_tx_details"><![CDATA[
<h1>交易明細</h1>
<h2>收款地址</h2>
这是你发送Monero过去的钱包地址。
<h2>付款ID</h2>
你可以使用付款ID以帮助识別双方的款项交易。这是选填项目并且完全为隐私保护的。
举例来說这可以让公司行号辨別你的这笔款项是用来支付某项购买的商品。
<h2>交易ID</h2>
这是可以用来在Monero区块链浏览器上查询的被隐蔽交易ID。在这里可查询:
<a href="https://xmrchain.net/">https://xmrchain.net/</a>
<h2>TX KEY (交易密钥)</h2>
这是该笔交易的私钥。请妥善保护这组密钥,因为这密钥将可揭露你该笔交易的环状签名
而使得该笔交易透明化。
<h2>区块</h2>
这是包裹该笔交易的区块編号。
]]></string>
<string name="help_send"><![CDATA[
<h1>发送</h1>
<h2>收款者地址</h2>
<p>这是你欲发送Monero过去的钱包地址。你可以从剪贴簿贴上, 扫描QR码或是手动输入。
请务必再三确认地址正确以免发送至错误的钱包地址</p>
<p>除了发送XMR你还可透过 XMR.TO 服务来发送BTC (请至 https://xmr.to
了解更多)或在下面阅读发送BTC的說明</p>
<h2>付款ID</h2>
<p>你可以使用付款ID以帮助识別双方的款项交易。这是选填项目并且完全为隐私保护的。
举例来說这可以让公司行号辨別你的这笔款项是用来支付某项购买的商品。<p>
<h2>Ring size</h2>
<p>在Monerujo中你有数种Ring size可以选择如果你是入门使用者我们建議你使用ring size 7。
增加ring size的数量可以增加环状签名的数量理論上可以增加隐私保护強度(plausible deniability)。
但设定高ring size亦会让你的交易网络中更为引人注意。</p>
<h2>优先权</h2>
<p>这个设定将会影响你的交易被纳入区块的速度。越高优先权的交易将会支付更多的手续费,反之亦然。
请注意若你选择低手续费则可能会让你的交易等待数小时才能被纳入区块。预设优先权为\"中等\"。</p>
<h1>发送BTC</h1>
<h2>XMR.TO</h2>
<p>XMR.TO 是一个可以将XMR转换为BTC的第三方服务。
我们利用XMR.TO的API将其服务整合至Monerujo中。请参阅 https://xmr.to 后自行决定是否使用。
Monerujo开发团队与XMR.TO并无相关无法为你解决其服务上的问題</p>
<h2>XMR.TO 汇率<h2>
<p>在 \"金额\" 的页面中你将看到 XMR.TO 服务的参数。包含了汇率以及BTC金额的上下限。
请注意这个阶段显示的汇率并不是确定的。你也可以看到在多少金额之下可以直接发送BTC而不用等待XMR的确认。
(详见 XMR.TO的FAQ以了解更多)。请注意XMR.TO并不额外收取手续费用是不是很棒呢?</p>
<h2>XMR.TO 订单<h2>
<p>在 \"确认\" 的页面中你将可以看到正式的XMR.TO订单。这订单仅在一段时间內有效。
你可以在 \"发送\" 的按鈕上看到倒数计时。这时的汇率可能与前一个页面显示的不同</p>
<h2>XMR.TO 私钥<h2>
<p>由于Monerujo仅处理Monero的部分你的XMR.TO密钥将可用于再XMR.TO的首页上查询追踪BTC部分的交易状況。</p>
<p>请注意此密钥仅在交易发起的24小时內有效。</p>
<h2>XMR.TO 倒数计时!</h2>
<p>当倒数计时归零的时候,你将会需要回到上一步再回到\"确认\"页面重新向XMR.TO寻求汇率报价</p>
]]></string>
<string name="help_xmrto"><![CDATA[
<h1>发送 BTC</h1>
<h2>XMR.TO</h2>
<p>XMR.TO 是一个可以将XMR转换为BTC的第三方服务。
我们利用XMR.TO的API将其服务整合至Monerujo中。请参阅 https://xmr.to 后自行决定是否使用。
Monerujo开发团队与XMR.TO并无相关无法为你解决其服务上的问題</p>
<h2>XMR.TO 汇率<h2>
<p>在 \"金额\" 的页面中你将看到 XMR.TO 服务的参数。包含了汇率以及BTC金额的上下限。
请注意这个阶段显示的汇率并不是确定的。你也可以看到多少金额的可以进行零确认XMR的BTC发送
(详见 XMR.TO的FAQ以了解更多)。请注意XMR.TO并不额外收取手续费用是不是很棒呢?</p>
<h2>XMR.TO 订单<h2>
<p>在 \"确认\" 的页面中你将可以看到正式的XMR.TO订单。这订单仅在一段时间內有效。
你可以在 \"发送\" 的按鈕上看到倒数计时。这时的汇率可能与前一个页面显示的不同</p>
<h2>XMR.TO 私钥<h2>
<p>由于Monerujo仅处理Monero的部分你的XMR.TO密钥将可用于再XMR.TO的首页上查询追踪BTC部分的交易状況。</p>
<p>请注意此密钥仅在交易发起的24小时內有效。</p>
<h2>XMR.TO 倒数计时!</h2>
<p>当倒数计时归零的时候,你将会需要回到上一步再回到\"确认\"页面重新向XMR.TO寻求汇率报价</p>
]]></string>
</resources>

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
<resources>
<string name="wallet_activity_name">錢包</string>
<string name="menu_testnet">Testnet</string>
<string name="menu_about">關於</string>
<string name="menu_privacy">隱私權政策</string>

View File

@@ -2,7 +2,7 @@
<string name="app_name" translatable="false">monerujo</string>
<string name="wallet_activity_name">Wallet</string>
<string name="menu_testnet">Testnet</string>
<string name="menu_stagenet" translatable="false">Stagenet</string>
<string name="menu_about">About</string>
<string name="menu_privacy">Privacy Policy</string>

View File

@@ -25,6 +25,7 @@ public class BitcoinAddressValidatorTest {
@Test
public void validateBTC_shouldValidate() {
assertTrue(BitcoinAddressValidator.validate("2NBMEXXS4v8ubajzfQUjYvh2ptLkxzH8uTC", true));
assertTrue(BitcoinAddressValidator.validate("2N9fzq66uZYQXp7uqrPBH6jKBhjrgTzpGCy", true));
assertTrue(BitcoinAddressValidator.validate("1AGNa15ZQXAZUgFiqJ2i7Z2DPU2J6hW62i", false));
assertTrue(BitcoinAddressValidator.validate("1Q1pE5vPGEEMqRcVRMbtBK842Y6Pzo6nK9", false));

View File

@@ -556,7 +556,8 @@ struct Wallet
}
static uint64_t maximumAllowedAmount();
// Easylogger wrapper
static void init(const char *argv0, const char *default_log_base_name);
static void init(const char *argv0, const char *default_log_base_name) { init(argv0, default_log_base_name, "", true); }
static void init(const char *argv0, const char *default_log_base_name, const std::string &log_path, bool console);
static void debug(const std::string &category, const std::string &str);
static void info(const std::string &category, const std::string &str);
static void warning(const std::string &category, const std::string &str);