mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-03 08:23:04 +02:00
Compare commits
50 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2c3e73b540 | ||
![]() |
830d9dadb9 | ||
![]() |
331d88ebba | ||
![]() |
7cc2f6fafb | ||
![]() |
9a3ee0eda8 | ||
![]() |
6e898939a3 | ||
![]() |
40ae39d647 | ||
![]() |
ca81e652e5 | ||
![]() |
796048be4e | ||
![]() |
441bf995c8 | ||
![]() |
e8860ab8eb | ||
![]() |
525b38ff53 | ||
![]() |
ba79bf87aa | ||
![]() |
3fe6571e7d | ||
![]() |
364e6a8137 | ||
![]() |
cb69ce99d6 | ||
![]() |
1f976872fc | ||
![]() |
27f266b6f7 | ||
![]() |
168928d54a | ||
![]() |
b3f61072aa | ||
![]() |
ccb64aded0 | ||
![]() |
e98fa089f2 | ||
![]() |
884878b7a7 | ||
![]() |
4e23f0ef3a | ||
![]() |
6ea4e3d998 | ||
![]() |
971c90f35b | ||
![]() |
f0523c403c | ||
![]() |
966ed23b87 | ||
![]() |
95f2ca74a6 | ||
![]() |
81d94478f2 | ||
![]() |
16ff779ebc | ||
![]() |
6b7bb164f4 | ||
![]() |
da1d4ea1bf | ||
![]() |
d5a967f690 | ||
![]() |
de8de02f9f | ||
![]() |
06456e33e4 | ||
![]() |
d97b36aa44 | ||
![]() |
8fa06e5b37 | ||
![]() |
8d95de828b | ||
![]() |
93a7be0452 | ||
![]() |
25c8ec1229 | ||
![]() |
0bf0444dce | ||
![]() |
b74f9c6bd7 | ||
![]() |
f843bb1685 | ||
![]() |
7d9d49c29e | ||
![]() |
4ca9328949 | ||
![]() |
08b5a87f19 | ||
![]() |
445d8acc38 | ||
![]() |
9385ac8c31 | ||
![]() |
4c7ebd8402 |
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 165
|
||||
versionName "1.10.15 'Node-O-matiC'"
|
||||
versionCode 177
|
||||
versionName "1.11.7 'Chernushka'"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
@@ -115,6 +115,10 @@ dependencies {
|
||||
implementation 'org.slf4j:slf4j-nop:1.7.25'
|
||||
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.github.aelstad/keccakj
|
||||
implementation 'com.github.aelstad:keccakj:1.1.0'
|
||||
|
||||
|
||||
testImplementation "junit:junit:$rootProject.ext.junitVersion"
|
||||
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
|
||||
|
@@ -26,14 +26,14 @@
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/wallet_activity_name"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="portrait" />
|
||||
android:screenOrientation="behind" />
|
||||
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="portrait">
|
||||
android:screenOrientation="locked">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -42,6 +42,22 @@
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="@string/app_name">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="monero" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="@string/app_name">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="bitcoin" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
android:resource="@xml/usb_device_filter" />
|
||||
@@ -52,5 +68,15 @@
|
||||
android:description="@string/service_description"
|
||||
android:exported="false"
|
||||
android:label="Monero Wallet Service" />
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
@@ -60,14 +60,14 @@ enum {
|
||||
HASH_DATA_AREA = 136
|
||||
};
|
||||
|
||||
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed);
|
||||
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height);
|
||||
|
||||
inline void slow_hash(const void *data, const size_t length, char *hash) {
|
||||
cn_slow_hash(data, length, hash, 0 /* variant */, 0/*prehashed*/);
|
||||
cn_slow_hash(data, length, hash, 0 /*variant*/, 0 /*prehashed*/, 0 /*height*/);
|
||||
}
|
||||
|
||||
inline void slow_hash_broken(const void *data, char *hash, int variant) {
|
||||
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/);
|
||||
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/, 0 /*height*/);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@@ -173,10 +173,10 @@ public class LevinReader {
|
||||
|
||||
// this should be in LittleEndianDataInputStream because it has little
|
||||
// endian logic
|
||||
private long readRest(int firstByte, int bytes) throws IOException {
|
||||
private long readRest(final int firstByte, final int bytes) throws IOException {
|
||||
long result = firstByte;
|
||||
for (int i = 0; i < bytes; i++) {
|
||||
result = result + (in.readUnsignedByte() << 8);
|
||||
for (int i = 1; i < bytes + 1; i++) {
|
||||
result = result + (((long) in.readUnsignedByte()) << (8 * i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@@ -33,6 +34,7 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
@@ -45,6 +47,7 @@ import com.m2049r.xmrwallet.util.FingerprintHelper;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||
import com.m2049r.xmrwallet.util.ledger.Monero;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
import com.nulabinc.zxcvbn.Strength;
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
@@ -95,7 +98,6 @@ public class GenerateFragment extends Fragment {
|
||||
etWalletRestoreHeight = view.findViewById(R.id.etWalletRestoreHeight);
|
||||
bGenerate = view.findViewById(R.id.bGenerate);
|
||||
|
||||
etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etWalletViewKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etWalletSpendKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
@@ -241,9 +243,7 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
etWalletAddress.setVisibility(View.VISIBLE);
|
||||
etWalletAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener()
|
||||
|
||||
{
|
||||
etWalletAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||
@@ -275,9 +275,7 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
if (type.equals(TYPE_KEY)) {
|
||||
etWalletSpendKey.setVisibility(View.VISIBLE);
|
||||
etWalletSpendKey.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener()
|
||||
|
||||
{
|
||||
etWalletSpendKey.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||
@@ -304,9 +302,7 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
}
|
||||
bGenerate.setOnClickListener(new View.OnClickListener()
|
||||
|
||||
{
|
||||
bGenerate.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Helper.hideKeyboard(getActivity());
|
||||
@@ -617,4 +613,80 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
AlertDialog ledgerDialog = null;
|
||||
|
||||
public void convertLedgerSeed() {
|
||||
if (ledgerDialog != null) return;
|
||||
final Activity activity = getActivity();
|
||||
View promptsView = getLayoutInflater().inflate(R.layout.prompt_ledger_seed, null);
|
||||
android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity);
|
||||
alertDialogBuilder.setView(promptsView);
|
||||
|
||||
final TextInputLayout etSeed = promptsView.findViewById(R.id.etSeed);
|
||||
final TextInputLayout etPassphrase = promptsView.findViewById(R.id.etPassphrase);
|
||||
|
||||
etSeed.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (etSeed.getError() != null) {
|
||||
etSeed.setError(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start,
|
||||
int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start,
|
||||
int before, int count) {
|
||||
}
|
||||
});
|
||||
|
||||
alertDialogBuilder
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getString(R.string.label_ok), null)
|
||||
.setNegativeButton(getString(R.string.label_cancel),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
etWalletMnemonic.getEditText().getText().clear();
|
||||
dialog.cancel();
|
||||
ledgerDialog = null;
|
||||
}
|
||||
});
|
||||
|
||||
ledgerDialog = alertDialogBuilder.create();
|
||||
|
||||
ledgerDialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
||||
@Override
|
||||
public void onShow(DialogInterface dialog) {
|
||||
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String ledgerSeed = etSeed.getEditText().getText().toString();
|
||||
String ledgerPassphrase = etPassphrase.getEditText().getText().toString();
|
||||
String moneroSeed = Monero.convert(ledgerSeed, ledgerPassphrase);
|
||||
if (moneroSeed != null) {
|
||||
etWalletMnemonic.getEditText().setText(moneroSeed);
|
||||
ledgerDialog.dismiss();
|
||||
ledgerDialog = null;
|
||||
} else {
|
||||
etSeed.setError(getString(R.string.bad_ledger_seed));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// set FLAG_SECURE to prevent screenshots in Release Mode
|
||||
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
|
||||
ledgerDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
|
||||
ledgerDialog.show();
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,7 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
@@ -620,6 +621,11 @@ public class GenerateReviewFragment extends Fragment {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// set FLAG_SECURE to prevent screenshots in Release Mode
|
||||
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
|
||||
openDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
|
||||
return openDialog;
|
||||
}
|
||||
|
||||
|
@@ -269,7 +269,11 @@ public class LoginActivity extends BaseActivity
|
||||
} else {
|
||||
Timber.i("Waiting for permissions");
|
||||
}
|
||||
processUsbIntent(getIntent());
|
||||
|
||||
// try intents
|
||||
Intent intent = getIntent();
|
||||
if (!processUsbIntent(intent))
|
||||
processUriIntent(intent);
|
||||
}
|
||||
|
||||
boolean checkServiceRunning() {
|
||||
@@ -716,6 +720,10 @@ public class LoginActivity extends BaseActivity
|
||||
intent.putExtra(WalletActivity.REQUEST_PW, walletPassword);
|
||||
intent.putExtra(WalletActivity.REQUEST_FINGERPRINT_USED, fingerprintUsed);
|
||||
intent.putExtra(WalletActivity.REQUEST_STREETMODE, streetmode);
|
||||
if (uri != null) {
|
||||
intent.putExtra(WalletActivity.REQUEST_URI, uri);
|
||||
uri = null; // use only once
|
||||
}
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -921,8 +929,11 @@ public class LoginActivity extends BaseActivity
|
||||
|
||||
@Override
|
||||
public boolean createWallet(File aFile, String password) {
|
||||
NodeInfo currentNode = getNode();
|
||||
final long restoreHeight =
|
||||
(currentNode != null) ? currentNode.getHeight() - 20 : -1;
|
||||
Wallet newWallet = WalletManager.getInstance()
|
||||
.createWallet(aFile, password, MNEMONIC_LANGUAGE);
|
||||
.createWallet(aFile, password, MNEMONIC_LANGUAGE, restoreHeight);
|
||||
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
|
||||
if (!success) {
|
||||
Timber.e(newWallet.getErrorString());
|
||||
@@ -1221,6 +1232,12 @@ public class LoginActivity extends BaseActivity
|
||||
case R.id.action_language:
|
||||
onChangeLocale();
|
||||
return true;
|
||||
case R.id.action_ledger_seed:
|
||||
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
if (f instanceof GenerateFragment) {
|
||||
((GenerateFragment) f).convertLedgerSeed();
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@@ -1353,30 +1370,31 @@ public class LoginActivity extends BaseActivity
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
if (Ledger.ENABLED)
|
||||
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
|
||||
@@ -1385,7 +1403,7 @@ public class LoginActivity extends BaseActivity
|
||||
processUsbIntent(intent);
|
||||
}
|
||||
|
||||
private void processUsbIntent(Intent intent) {
|
||||
private boolean processUsbIntent(Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
|
||||
synchronized (this) {
|
||||
@@ -1398,6 +1416,21 @@ public class LoginActivity extends BaseActivity
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String uri = null;
|
||||
|
||||
private void processUriIntent(Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (Intent.ACTION_VIEW.equals(action)) {
|
||||
synchronized (this) {
|
||||
uri = intent.getDataString();
|
||||
Timber.d("URI Intent %s", uri);
|
||||
HelpFragment.display(getSupportFragmentManager(), R.string.help_uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -440,7 +440,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
Collections.sort(nodesToTest, NodeInfo.BestNodeComparator);
|
||||
NodeInfo bestNode = nodesToTest.get(0);
|
||||
if (bestNode.isValid())
|
||||
return nodesToTest.get(0);
|
||||
return bestNode;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
@@ -32,6 +32,7 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
@@ -505,6 +506,10 @@ public class NodeFragment extends Fragment
|
||||
});
|
||||
}
|
||||
});
|
||||
// set FLAG_SECURE to prevent screenshots in Release Mode
|
||||
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
|
||||
editDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
|
||||
etNodePass.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
|
@@ -17,19 +17,28 @@
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.net.Uri;
|
||||
import android.nfc.NfcManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.widget.ShareActionProvider;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
@@ -56,6 +65,9 @@ import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||
import com.m2049r.xmrwallet.widget.ExchangeView;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -69,8 +81,8 @@ public class ReceiveFragment extends Fragment {
|
||||
private TextInputLayout etNotes;
|
||||
private ExchangeView evAmount;
|
||||
private TextView tvQrCode;
|
||||
private ImageView qrCode;
|
||||
private ImageView qrCodeFull;
|
||||
private ImageView ivQrCode;
|
||||
private ImageView ivQrCodeFull;
|
||||
private EditText etDummy;
|
||||
private ImageButton bCopyAddress;
|
||||
private Button bSubaddress;
|
||||
@@ -97,9 +109,9 @@ public class ReceiveFragment extends Fragment {
|
||||
tvAddress = view.findViewById(R.id.tvAddress);
|
||||
etNotes = view.findViewById(R.id.etNotes);
|
||||
evAmount = view.findViewById(R.id.evAmount);
|
||||
qrCode = view.findViewById(R.id.qrCode);
|
||||
ivQrCode = view.findViewById(R.id.qrCode);
|
||||
tvQrCode = view.findViewById(R.id.tvQrCode);
|
||||
qrCodeFull = view.findViewById(R.id.qrCodeFull);
|
||||
ivQrCodeFull = view.findViewById(R.id.qrCodeFull);
|
||||
etDummy = view.findViewById(R.id.etDummy);
|
||||
bCopyAddress = view.findViewById(R.id.bCopyAddress);
|
||||
bSubaddress = view.findViewById(R.id.bSubaddress);
|
||||
@@ -178,25 +190,25 @@ public class ReceiveFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
qrCode.setOnClickListener(new View.OnClickListener() {
|
||||
ivQrCode.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Helper.hideKeyboard(getActivity());
|
||||
etDummy.requestFocus();
|
||||
if (qrValid) {
|
||||
qrCodeFull.setImageBitmap(((BitmapDrawable) qrCode.getDrawable()).getBitmap());
|
||||
qrCodeFull.setVisibility(View.VISIBLE);
|
||||
ivQrCodeFull.setImageBitmap(((BitmapDrawable) ivQrCode.getDrawable()).getBitmap());
|
||||
ivQrCodeFull.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
evAmount.doExchange();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
qrCodeFull.setOnClickListener(new View.OnClickListener() {
|
||||
ivQrCodeFull.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
qrCodeFull.setImageBitmap(null);
|
||||
qrCodeFull.setVisibility(View.GONE);
|
||||
ivQrCodeFull.setImageBitmap(null);
|
||||
ivQrCodeFull.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -228,6 +240,81 @@ public class ReceiveFragment extends Fragment {
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
private ShareActionProvider shareActionProvider;
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.receive_menu, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
|
||||
// Locate MenuItem with ShareActionProvider
|
||||
MenuItem item = menu.findItem(R.id.menu_item_share);
|
||||
|
||||
// Fetch and store ShareActionProvider
|
||||
shareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);
|
||||
|
||||
shareActionProvider.setOnShareTargetSelectedListener(new ShareActionProvider.OnShareTargetSelectedListener() {
|
||||
@Override
|
||||
public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
|
||||
saveQrCode(); // save it only if we need it
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setShareIntent() {
|
||||
if (shareActionProvider != null) {
|
||||
if (qrValid) {
|
||||
shareActionProvider.setShareIntent(getShareIntent());
|
||||
} else {
|
||||
shareActionProvider.setShareIntent(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveQrCode() {
|
||||
if (!qrValid) throw new IllegalStateException("trying to save null qr code!");
|
||||
|
||||
File cachePath = new File(getActivity().getCacheDir(), "images");
|
||||
if (!cachePath.exists())
|
||||
if (!cachePath.mkdirs()) throw new IllegalStateException("cannot create images folder");
|
||||
File png = new File(cachePath, "QR.png");
|
||||
try {
|
||||
FileOutputStream stream = new FileOutputStream(png);
|
||||
Bitmap qrBitmap = ((BitmapDrawable) ivQrCode.getDrawable()).getBitmap();
|
||||
qrBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
|
||||
stream.close();
|
||||
} catch (IOException ex) {
|
||||
Timber.e(ex);
|
||||
// make sure we don't share an old qr code
|
||||
if (!png.delete()) throw new IllegalStateException("cannot delete old qr code");
|
||||
// if we manage to delete it, the URI points to nothing and the user gets a toast with the error
|
||||
}
|
||||
}
|
||||
|
||||
private Intent getShareIntent() {
|
||||
File imagePath = new File(getActivity().getCacheDir(), "images");
|
||||
File png = new File(imagePath, "QR.png");
|
||||
Uri contentUri = FileProvider.getUriForFile(getActivity(),
|
||||
BuildConfig.APPLICATION_ID + ".fileprovider", png);
|
||||
if (contentUri != null) {
|
||||
Intent shareIntent = new Intent();
|
||||
shareIntent.setAction(Intent.ACTION_SEND);
|
||||
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file
|
||||
shareIntent.setDataAndType(contentUri, getActivity().getContentResolver().getType(contentUri));
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, bcData.getUriString());
|
||||
return shareIntent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void enableSubaddressButton(boolean enable) {
|
||||
bSubaddress.setEnabled(enable);
|
||||
if (enable) {
|
||||
@@ -242,23 +329,23 @@ public class ReceiveFragment extends Fragment {
|
||||
Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
boolean qrValid = true;
|
||||
private boolean qrValid = false;
|
||||
|
||||
void clearQR() {
|
||||
if (qrValid) {
|
||||
qrCode.setImageBitmap(null);
|
||||
ivQrCode.setImageBitmap(null);
|
||||
qrValid = false;
|
||||
setShareIntent();
|
||||
if (isLoaded)
|
||||
tvQrCode.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
void setQR(Bitmap qr) {
|
||||
qrCode.setImageBitmap(qr);
|
||||
ivQrCode.setImageBitmap(qr);
|
||||
qrValid = true;
|
||||
setShareIntent();
|
||||
tvQrCode.setVisibility(View.GONE);
|
||||
Helper.hideKeyboard(getActivity());
|
||||
etDummy.requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -392,7 +479,7 @@ public class ReceiveFragment extends Fragment {
|
||||
return;
|
||||
}
|
||||
bcData = new BarcodeData(BarcodeData.Asset.XMR, address, null, notes, xmrAmount);
|
||||
int size = Math.min(qrCode.getHeight(), qrCode.getWidth());
|
||||
int size = Math.max(ivQrCode.getWidth(), ivQrCode.getHeight());
|
||||
Bitmap qr = generate(bcData.getUriString(), size, size);
|
||||
if (qr != null) {
|
||||
setQR(qr);
|
||||
@@ -422,8 +509,8 @@ public class ReceiveFragment extends Fragment {
|
||||
Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.RGB_565);
|
||||
bitmap = addLogo(bitmap);
|
||||
return bitmap;
|
||||
} catch (WriterException e) {
|
||||
e.printStackTrace();
|
||||
} catch (WriterException ex) {
|
||||
Timber.e(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -487,6 +574,7 @@ public class ReceiveFragment extends Fragment {
|
||||
@Override
|
||||
public void onPause() {
|
||||
Timber.d("onPause()");
|
||||
Helper.hideKeyboard(getActivity());
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
|
@@ -31,7 +31,7 @@ public abstract class SecureActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// set FLAG_SECURE to prevent screenshots in Release Mode
|
||||
if (!BuildConfig.DEBUG && !BuildConfig.FLAVOR_type.equals("alpha")) {
|
||||
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
|
||||
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
}
|
||||
|
@@ -71,7 +71,6 @@ public class TxFragment extends Fragment {
|
||||
private TextView tvTxFee;
|
||||
private TextView tvTxTransfers;
|
||||
private TextView etTxNotes;
|
||||
private Button bTxNotes;
|
||||
|
||||
// XMRTO stuff
|
||||
private View cvXmrTo;
|
||||
@@ -102,21 +101,9 @@ public class TxFragment extends Fragment {
|
||||
tvTxFee = view.findViewById(R.id.tvTxFee);
|
||||
tvTxTransfers = view.findViewById(R.id.tvTxTransfers);
|
||||
etTxNotes = view.findViewById(R.id.etTxNotes);
|
||||
bTxNotes = view.findViewById(R.id.bTxNotes);
|
||||
|
||||
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
|
||||
bTxNotes.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
info.notes = null; // force reload on next view
|
||||
bTxNotes.setEnabled(false);
|
||||
etTxNotes.setEnabled(false);
|
||||
userNotes.setNote(etTxNotes.getText().toString());
|
||||
activityCallback.onSetNote(info.hash, userNotes.txNotes);
|
||||
}
|
||||
});
|
||||
|
||||
tvTxXmrToKey.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -131,14 +118,6 @@ public class TxFragment extends Fragment {
|
||||
return view;
|
||||
}
|
||||
|
||||
public void onNotesSet(boolean reload) {
|
||||
bTxNotes.setEnabled(true);
|
||||
etTxNotes.setEnabled(true);
|
||||
if (reload) {
|
||||
loadNotes(this.info);
|
||||
}
|
||||
}
|
||||
|
||||
void shareTxInfo() {
|
||||
if (this.info == null) return;
|
||||
StringBuffer sb = new StringBuffer();
|
||||
@@ -315,7 +294,6 @@ public class TxFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -337,9 +315,9 @@ public class TxFragment extends Fragment {
|
||||
|
||||
String getTxNotes(String hash);
|
||||
|
||||
String getTxAddress(int major, int minor);
|
||||
boolean setTxNotes(String txId, String txNotes);
|
||||
|
||||
void onSetNote(String txId, String notes);
|
||||
String getTxAddress(int major, int minor);
|
||||
|
||||
void setToolbarButton(int type);
|
||||
|
||||
@@ -357,4 +335,16 @@ public class TxFragment extends Fragment {
|
||||
+ " must implement Listener");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
if (!etTxNotes.getText().toString().equals(userNotes.note)) { // notes have changed
|
||||
// save them
|
||||
userNotes.setNote(etTxNotes.getText().toString());
|
||||
info.notes = userNotes.txNotes;
|
||||
activityCallback.setTxNotes(info.hash, info.notes);
|
||||
}
|
||||
Helper.hideKeyboard(getActivity());
|
||||
super.onPause();
|
||||
}
|
||||
}
|
@@ -47,6 +47,7 @@ import android.widget.Toast;
|
||||
|
||||
import com.m2049r.xmrwallet.data.BarcodeData;
|
||||
import com.m2049r.xmrwallet.data.TxData;
|
||||
import com.m2049r.xmrwallet.data.UserNotes;
|
||||
import com.m2049r.xmrwallet.dialog.CreditsFragment;
|
||||
import com.m2049r.xmrwallet.dialog.HelpFragment;
|
||||
import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment;
|
||||
@@ -59,7 +60,6 @@ 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.data.UserNotes;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -81,6 +81,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
public static final String REQUEST_PW = "pw";
|
||||
public static final String REQUEST_FINGERPRINT_USED = "fingerprint";
|
||||
public static final String REQUEST_STREETMODE = "streetmode";
|
||||
public static final String REQUEST_URI = "uri";
|
||||
|
||||
private NavigationView accountsView;
|
||||
private DrawerLayout drawer;
|
||||
@@ -92,6 +93,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
|
||||
private String password;
|
||||
|
||||
private String uri = null;
|
||||
|
||||
private long streetMode = 0;
|
||||
|
||||
@Override
|
||||
@@ -147,8 +150,13 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
final WalletFragment walletFragment = (WalletFragment)
|
||||
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
|
||||
if (walletFragment != null) walletFragment.resetDismissedTransactions();
|
||||
updateAccountsBalance();
|
||||
forceUpdate();
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateAccountsBalance();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -171,6 +179,11 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
return getWallet().getUserNote(txId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setTxNotes(String txId, String txNotes) {
|
||||
return getWallet().setUserNote(txId, txNotes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTxAddress(int major, int minor) {
|
||||
return getWallet().getSubaddress(major, minor);
|
||||
@@ -191,6 +204,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
// we can set the streetmode height AFTER opening the wallet
|
||||
requestStreetMode = extras.getBoolean(REQUEST_STREETMODE);
|
||||
password = extras.getString(REQUEST_PW);
|
||||
uri = extras.getString(REQUEST_URI);
|
||||
connectWalletService(walletId, password);
|
||||
} else {
|
||||
finish();
|
||||
@@ -512,7 +526,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
|
||||
@Override
|
||||
public void onSendRequest() {
|
||||
replaceFragment(new SendFragment(), null, null);
|
||||
replaceFragment(SendFragment.newInstance(uri), null, null);
|
||||
uri = null; // only use uri once
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -708,26 +723,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetNotes(final boolean success) {
|
||||
try {
|
||||
final TxFragment txFragment = (TxFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (!success) {
|
||||
Toast.makeText(WalletActivity.this, getString(R.string.tx_notes_set_failed), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
txFragment.onNotesSet(success);
|
||||
}
|
||||
});
|
||||
} catch (ClassCastException ex) {
|
||||
// not in tx fragment
|
||||
Timber.d(ex.getLocalizedMessage());
|
||||
// never mind
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(final String text) {
|
||||
try {
|
||||
@@ -789,21 +784,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetNote(String txId, String notes) {
|
||||
if (mIsBound) { // no point in talking to unbound service
|
||||
Intent intent = new Intent(getApplicationContext(), WalletService.class);
|
||||
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_SETNOTE);
|
||||
intent.putExtra(WalletService.REQUEST_CMD_SETNOTE_TX, txId);
|
||||
intent.putExtra(WalletService.REQUEST_CMD_SETNOTE_NOTES, notes);
|
||||
startService(intent);
|
||||
Timber.d("SET NOTE request sent");
|
||||
} else {
|
||||
Timber.e("Service not bound");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareSend(final String tag, final TxData txData) {
|
||||
if (mIsBound) { // no point in talking to unbound service
|
||||
@@ -1057,6 +1037,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
} else {
|
||||
tvBalance.setText(null);
|
||||
}
|
||||
updateAccountsList();
|
||||
}
|
||||
|
||||
void updateAccountsHeader() {
|
||||
@@ -1070,8 +1051,11 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
Menu menu = accountsView.getMenu();
|
||||
menu.removeGroup(R.id.accounts_list);
|
||||
final int n = wallet.getNumAccounts();
|
||||
final boolean showBalances = (n > 1) && !isStreetMode();
|
||||
for (int i = 0; i < n; i++) {
|
||||
final String label = wallet.getAccountLabel(i);
|
||||
final String label = (showBalances ?
|
||||
getString(R.string.label_account, wallet.getAccountLabel(i), Helper.getDisplayAmount(wallet.getBalance(i), 2))
|
||||
: wallet.getAccountLabel(i));
|
||||
final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label);
|
||||
item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp);
|
||||
if (i == wallet.getAccountIndex())
|
||||
|
@@ -219,7 +219,7 @@ public class WalletFragment extends Fragment
|
||||
if (isExchanging) return; // wait for exchange to finish - it will fire this itself then.
|
||||
// at this point selection is XMR in case of error
|
||||
String displayB;
|
||||
double amountA = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // crash if this fails!
|
||||
double amountA = Helper.getDecimalAmount(unlockedBalance).doubleValue();
|
||||
if (!Helper.CRYPTO.equals(balanceCurrency)) { // not XMR
|
||||
double amountB = amountA * balanceRate;
|
||||
displayB = Helper.getFormattedAmount(amountB, false);
|
||||
@@ -235,10 +235,10 @@ public class WalletFragment extends Fragment
|
||||
private final ExchangeApi exchangeApi = Helper.getExchangeApi();
|
||||
|
||||
void refreshBalance() {
|
||||
double unconfirmedXmr = Double.parseDouble(Helper.getDisplayAmount(balance - unlockedBalance));
|
||||
double unconfirmedXmr = Helper.getDecimalAmount(balance - unlockedBalance).doubleValue();
|
||||
showUnconfirmed(unconfirmedXmr);
|
||||
if (sCurrency.getSelectedItemPosition() == 0) { // XMR
|
||||
double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail!
|
||||
double amountXmr = Helper.getDecimalAmount(unlockedBalance).doubleValue();
|
||||
showBalance(Helper.getFormattedAmount(amountXmr, true));
|
||||
} else { // not XMR
|
||||
String currency = (String) sCurrency.getSelectedItem();
|
||||
@@ -294,7 +294,7 @@ public class WalletFragment extends Fragment
|
||||
|
||||
public void exchangeFailed() {
|
||||
sCurrency.setSelection(0, true); // default to XMR
|
||||
double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail!
|
||||
double amountXmr = Helper.getDecimalAmount(unlockedBalance).doubleValue();
|
||||
showBalance(Helper.getFormattedAmount(amountXmr, true));
|
||||
hideExchanging();
|
||||
}
|
||||
|
@@ -46,7 +46,8 @@ import okhttp3.ResponseBody;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class NodeInfo extends Node {
|
||||
final static public int MIN_MAJOR_VERSION = 9;
|
||||
final static public int MIN_MAJOR_VERSION = 11;
|
||||
final static public String RPC_VERSION = "2.0";
|
||||
|
||||
private long height = 0;
|
||||
private long timestamp = 0;
|
||||
@@ -228,9 +229,12 @@ public class NodeInfo extends Node {
|
||||
responseCode = response.code();
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody respBody = response.body(); // closed through Response object
|
||||
if ((respBody != null) && (respBody.contentLength() < 1000)) { // sanity check
|
||||
if ((respBody != null) && (respBody.contentLength() < 2000)) { // sanity check
|
||||
final JSONObject json = new JSONObject(
|
||||
respBody.string());
|
||||
String rpcVersion = json.getString("jsonrpc");
|
||||
if (!RPC_VERSION.equals(rpcVersion))
|
||||
return false;
|
||||
final JSONObject header = json.getJSONObject(
|
||||
"result").getJSONObject("block_header");
|
||||
height = header.getLong("height");
|
||||
|
@@ -67,7 +67,7 @@ public class HelpFragment extends DialogFragment {
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setView(view);
|
||||
builder.setNegativeButton(R.string.about_close,
|
||||
builder.setNegativeButton(R.string.help_ok,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
|
@@ -22,11 +22,11 @@ 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.view.WindowManager;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
|
||||
import java.util.Locale;
|
||||
@@ -62,9 +62,6 @@ public class ProgressDialog extends AlertDialog {
|
||||
pbBar = view.findViewById(R.id.pbBar);
|
||||
tvProgress = view.findViewById(R.id.tvProgress);
|
||||
setView(view);
|
||||
//setTitle("blabla");
|
||||
//super.setMessage("bubbu");
|
||||
// view.invalidate();
|
||||
setIndeterminate(indeterminate);
|
||||
if (maxValue > 0) {
|
||||
setMax(maxValue);
|
||||
@@ -78,6 +75,11 @@ public class ProgressDialog extends AlertDialog {
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// set FLAG_SECURE to prevent screenshots in Release Mode
|
||||
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(int value, int max) {
|
||||
|
@@ -33,7 +33,9 @@ import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.data.BarcodeData;
|
||||
@@ -92,6 +94,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
private View llPaymentId;
|
||||
private TextView tvXmrTo;
|
||||
private View llXmrTo;
|
||||
private ImageButton bPasteAddress;
|
||||
|
||||
private boolean resolvingOA = false;
|
||||
private boolean resolvingPP = false;
|
||||
@@ -197,6 +200,21 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
});
|
||||
|
||||
bPasteAddress = view.findViewById(R.id.bPasteAddress);
|
||||
bPasteAddress.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
final String clip = Helper.getClipBoardText(getActivity());
|
||||
if (clip == null) return;
|
||||
// clean it up
|
||||
final String address = clip.replaceAll("[^0-9A-Z-a-z]", "");
|
||||
if (Wallet.isAddressValid(address) || BitcoinAddressValidator.validate(address))
|
||||
etAddress.getEditText().setText(address);
|
||||
else
|
||||
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
etPaymentId = view.findViewById(R.id.etPaymentId);
|
||||
etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
|
@@ -27,11 +27,13 @@ import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.data.TxData;
|
||||
import com.m2049r.xmrwallet.data.TxDataBtc;
|
||||
@@ -435,6 +437,11 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// set FLAG_SECURE to prevent screenshots in Release Mode
|
||||
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
|
||||
passwordDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
|
||||
passwordDialog.show();
|
||||
}
|
||||
|
||||
|
@@ -27,10 +27,12 @@ import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.data.TxData;
|
||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||
@@ -322,6 +324,11 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// set FLAG_SECURE to prevent screenshots in Release Mode
|
||||
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
|
||||
passwordDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
|
||||
passwordDialog.show();
|
||||
}
|
||||
|
||||
|
@@ -38,15 +38,16 @@ import android.widget.EditText;
|
||||
import com.m2049r.xmrwallet.OnBackPressedListener;
|
||||
import com.m2049r.xmrwallet.OnUriScannedListener;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.WalletActivity;
|
||||
import com.m2049r.xmrwallet.data.BarcodeData;
|
||||
import com.m2049r.xmrwallet.data.PendingTx;
|
||||
import com.m2049r.xmrwallet.data.TxData;
|
||||
import com.m2049r.xmrwallet.data.TxDataBtc;
|
||||
import com.m2049r.xmrwallet.data.UserNotes;
|
||||
import com.m2049r.xmrwallet.layout.SpendViewPager;
|
||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.Notice;
|
||||
import com.m2049r.xmrwallet.data.UserNotes;
|
||||
import com.m2049r.xmrwallet.widget.DotBar;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
@@ -104,6 +105,14 @@ public class SendFragment extends Fragment
|
||||
|
||||
static private int MAX_FALLBACK = Integer.MAX_VALUE;
|
||||
|
||||
public static SendFragment newInstance(String uri) {
|
||||
SendFragment f = new SendFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(WalletActivity.REQUEST_URI, uri);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -187,6 +196,16 @@ public class SendFragment extends Fragment
|
||||
etDummy.requestFocus();
|
||||
Helper.hideKeyboard(getActivity());
|
||||
|
||||
Bundle args = getArguments();
|
||||
if (args != null) {
|
||||
String uri = args.getString(WalletActivity.REQUEST_URI);
|
||||
Timber.d("URI: %s", uri);
|
||||
if (uri != null) {
|
||||
barcodeData = BarcodeData.fromQrCode(uri);
|
||||
Timber.d("barcodeData: %s", barcodeData != null ? barcodeData.toString() : "null");
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
@@ -30,6 +30,7 @@ import com.m2049r.xmrwallet.model.TransactionInfo;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.data.UserNotes;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
@@ -154,7 +155,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
}
|
||||
|
||||
if ((infoItem.fee > 0)) {
|
||||
String fee = Helper.getDisplayAmount(infoItem.fee, 5);
|
||||
String fee = Helper.getDisplayAmount(infoItem.fee, Helper.DISPLAY_DIGITS_INFO);
|
||||
tvFee.setText(context.getString(R.string.tx_list_fee, fee));
|
||||
tvFee.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
|
@@ -34,6 +34,7 @@ import java.io.IOException;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class Ledger {
|
||||
static final public boolean ENABLED = true;
|
||||
// 5:20 is same as wallet2.cpp::restore()
|
||||
static public final int LOOKAHEAD_ACCOUNTS = 5;
|
||||
static public final int LOOKAHEAD_SUBADDRESSES = 20;
|
||||
@@ -44,6 +45,7 @@ public class Ledger {
|
||||
public static final int OK[] = {SW_OK};
|
||||
|
||||
public static UsbDevice findDevice(UsbManager usbManager) {
|
||||
if (!ENABLED) return null;
|
||||
return BTChipTransportAndroidHID.getDevice(usbManager);
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,6 @@ 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;
|
||||
|
||||
|
@@ -173,5 +173,4 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
||||
return this.hash.compareTo(another.hash);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -353,9 +353,10 @@ public class Wallet {
|
||||
if (label.equals(NEW_ACCOUNT_NAME)) {
|
||||
String address = getAddress(accountIndex);
|
||||
int len = address.length();
|
||||
return address.substring(0, 6) +
|
||||
label = address.substring(0, 6) +
|
||||
"\u2026" + address.substring(len - 6, len);
|
||||
} else return label;
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getSubaddressLabel(int addressIndex) {
|
||||
|
@@ -91,14 +91,16 @@ public class WalletManager {
|
||||
managedWallet = null;
|
||||
}
|
||||
|
||||
public Wallet createWallet(File aFile, String password, String language) {
|
||||
public Wallet createWallet(File aFile, String password, String language, long height) {
|
||||
long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, getNetworkType().getValue());
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet);
|
||||
if (wallet.getStatus() == Wallet.Status.Status_Ok) {
|
||||
// (Re-)Estimate restore height based on what we know
|
||||
long oldHeight = wallet.getRestoreHeight();
|
||||
wallet.setRestoreHeight(RestoreHeight.getInstance().getHeight(new Date()));
|
||||
final long oldHeight = wallet.getRestoreHeight();
|
||||
final long restoreHeight =
|
||||
(height > -1) ? height : RestoreHeight.getInstance().getHeight(new Date());
|
||||
wallet.setRestoreHeight(restoreHeight);
|
||||
Timber.d("Changed Restore Height from %d to %d", oldHeight, wallet.getRestoreHeight());
|
||||
wallet.setPassword(password); // this rewrites the keys file (which contains the restore height)
|
||||
}
|
||||
|
@@ -69,10 +69,6 @@ public class WalletService extends Service {
|
||||
public static final String REQUEST_CMD_SEND = "send";
|
||||
public static final String REQUEST_CMD_SEND_NOTES = "notes";
|
||||
|
||||
public static final String REQUEST_CMD_SETNOTE = "setnote";
|
||||
public static final String REQUEST_CMD_SETNOTE_TX = "tx";
|
||||
public static final String REQUEST_CMD_SETNOTE_NOTES = "notes";
|
||||
|
||||
public static final int START_SERVICE = 1;
|
||||
public static final int STOP_SERVICE = 2;
|
||||
|
||||
@@ -224,8 +220,6 @@ public class WalletService extends Service {
|
||||
|
||||
void onSendTransactionFailed(String error);
|
||||
|
||||
void onSetNotes(boolean success);
|
||||
|
||||
void onWalletStarted(Wallet.ConnectionStatus walletStatus);
|
||||
|
||||
void onWalletOpen(Wallet.Device device);
|
||||
@@ -378,26 +372,6 @@ public class WalletService extends Service {
|
||||
if (observer != null) observer.onSendTransactionFailed(error);
|
||||
return;
|
||||
}
|
||||
} else if (cmd.equals(REQUEST_CMD_SETNOTE)) {
|
||||
Wallet myWallet = getWallet();
|
||||
Timber.d("SET NOTE for wallet: %s", myWallet.getName());
|
||||
String txId = extras.getString(REQUEST_CMD_SETNOTE_TX);
|
||||
String notes = extras.getString(REQUEST_CMD_SETNOTE_NOTES);
|
||||
if ((txId != null) && (notes != null)) {
|
||||
boolean success = myWallet.setUserNote(txId, notes);
|
||||
if (!success) {
|
||||
Timber.e(myWallet.getErrorString());
|
||||
}
|
||||
if (observer != null) observer.onSetNotes(success);
|
||||
if (success) {
|
||||
boolean rc = myWallet.store();
|
||||
Timber.d("wallet stored: %s with rc=%b", myWallet.getName(), rc);
|
||||
if (!rc) {
|
||||
Timber.w("Wallet store failed: %s", myWallet.getErrorString());
|
||||
}
|
||||
if (observer != null) observer.onWalletStored(rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@@ -21,6 +21,7 @@ import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@@ -64,6 +65,7 @@ import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.SocketTimeoutException;
|
||||
@@ -186,28 +188,23 @@ public class Helper {
|
||||
act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||
}
|
||||
|
||||
static public BigDecimal getDecimalAmount(long amount) {
|
||||
return new BigDecimal(amount).scaleByPowerOfTen(-12);
|
||||
}
|
||||
|
||||
static public String getDisplayAmount(long amount) {
|
||||
return getDisplayAmount(amount, 12);
|
||||
}
|
||||
|
||||
static public String getDisplayAmount(long amount, int maxDecimals) {
|
||||
return getDisplayAmount(Wallet.getDisplayAmount(amount), maxDecimals);
|
||||
}
|
||||
|
||||
// amountString must have '.' as decimal point
|
||||
private static String getDisplayAmount(String amountString, int maxDecimals) {
|
||||
int lastZero = 0;
|
||||
int decimal = 0;
|
||||
for (int i = amountString.length() - 1; i >= 0; i--) {
|
||||
if ((lastZero == 0) && (amountString.charAt(i) != '0')) lastZero = i + 1;
|
||||
// TODO i18n
|
||||
if (amountString.charAt(i) == '.') {
|
||||
decimal = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int cutoff = Math.min(Math.max(lastZero, decimal + 2), decimal + maxDecimals);
|
||||
return amountString.substring(0, cutoff);
|
||||
// a Java bug does not strip zeros properly if the value is 0
|
||||
if (amount == 0) return "0.00";
|
||||
BigDecimal d = getDecimalAmount(amount)
|
||||
.setScale(maxDecimals, BigDecimal.ROUND_HALF_UP)
|
||||
.stripTrailingZeros();
|
||||
if (d.scale() < 2)
|
||||
d = d.setScale(2, BigDecimal.ROUND_UNNECESSARY);
|
||||
return d.toPlainString();
|
||||
}
|
||||
|
||||
static public String getFormattedAmount(double amount, boolean isXmr) {
|
||||
@@ -285,6 +282,16 @@ public class Helper {
|
||||
clipboardManager.setPrimaryClip(clip);
|
||||
}
|
||||
|
||||
static public String getClipBoardText(Context context) {
|
||||
final ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
if (clipboardManager.hasPrimaryClip()
|
||||
&& clipboardManager.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
|
||||
final ClipData.Item item = clipboardManager.getPrimaryClip().getItemAt(0);
|
||||
return item.getText().toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static private Animation ShakeAnimation;
|
||||
|
||||
static public Animation getShakeAnimation(Context context) {
|
||||
@@ -556,6 +563,8 @@ public class Helper {
|
||||
tvOpenPrompt.setText(context.getText(R.string.prompt_fingerprint_auth));
|
||||
tvOpenPrompt.setVisibility(View.VISIBLE);
|
||||
FingerprintHelper.authenticate(context, cancelSignal, fingerprintAuthCallback);
|
||||
} else {
|
||||
etPassword.requestFocus();
|
||||
}
|
||||
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@@ -587,6 +596,11 @@ public class Helper {
|
||||
}
|
||||
});
|
||||
|
||||
// set FLAG_SECURE to prevent screenshots in Release Mode
|
||||
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
|
||||
openDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
|
||||
Helper.showKeyboard(openDialog);
|
||||
openDialog.show();
|
||||
}
|
||||
|
@@ -244,32 +244,48 @@ public class KeyStoreHelper {
|
||||
Timber.d("M Keys created");
|
||||
}
|
||||
|
||||
private static KeyStore.PrivateKeyEntry getPrivateKeyEntry(String alias) {
|
||||
private static PrivateKey getPrivateKey(String alias) {
|
||||
try {
|
||||
KeyStore ks = KeyStore
|
||||
.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
||||
ks.load(null);
|
||||
KeyStore.Entry entry = ks.getEntry(alias, null);
|
||||
//KeyStore.Entry entry = ks.getEntry(alias, null);
|
||||
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, null);
|
||||
|
||||
if (entry == null) {
|
||||
if (privateKey == null) {
|
||||
Timber.w("No key found under alias: %s", alias);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
|
||||
Timber.w("Not an instance of a PrivateKeyEntry");
|
||||
return null;
|
||||
}
|
||||
return (KeyStore.PrivateKeyEntry) entry;
|
||||
return privateKey;
|
||||
} catch (IOException | NoSuchAlgorithmException | CertificateException
|
||||
| UnrecoverableEntryException | KeyStoreException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static PublicKey getPublicKey(String alias) {
|
||||
try {
|
||||
KeyStore ks = KeyStore
|
||||
.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
||||
ks.load(null);
|
||||
|
||||
PublicKey publicKey = ks.getCertificate(alias).getPublicKey();
|
||||
|
||||
if (publicKey == null) {
|
||||
Timber.w("No public key");
|
||||
return null;
|
||||
}
|
||||
return publicKey;
|
||||
} catch (IOException | NoSuchAlgorithmException | CertificateException
|
||||
| KeyStoreException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] encrypt(String alias, byte[] data) {
|
||||
try {
|
||||
PublicKey publicKey = getPrivateKeyEntry(alias).getCertificate().getPublicKey();
|
||||
PublicKey publicKey = getPublicKey(alias);
|
||||
Cipher cipher = Cipher.getInstance(SecurityConstants.CIPHER_RSA_ECB_PKCS1);
|
||||
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
@@ -283,9 +299,8 @@ public class KeyStoreHelper {
|
||||
|
||||
private static byte[] decrypt(String alias, byte[] data) {
|
||||
try {
|
||||
KeyStore.PrivateKeyEntry pke = getPrivateKeyEntry(alias);
|
||||
if (pke == null) return null;
|
||||
PrivateKey privateKey = pke.getPrivateKey();
|
||||
PrivateKey privateKey = getPrivateKey(alias);
|
||||
if (privateKey == null) return null;
|
||||
Cipher cipher = Cipher.getInstance(SecurityConstants.CIPHER_RSA_ECB_PKCS1);
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
@@ -306,41 +321,14 @@ public class KeyStoreHelper {
|
||||
*/
|
||||
private static byte[] signData(String alias, byte[] data) throws NoSuchAlgorithmException,
|
||||
InvalidKeyException, SignatureException {
|
||||
KeyStore.PrivateKeyEntry pke = getPrivateKeyEntry(alias);
|
||||
if (pke == null) return null;
|
||||
PrivateKey privateKey = getPrivateKeyEntry(alias).getPrivateKey();
|
||||
PrivateKey privateKey = getPrivateKey(alias);
|
||||
if (privateKey == null) return null;
|
||||
Signature s = Signature.getInstance(SecurityConstants.SIGNATURE_SHA256withRSA);
|
||||
s.initSign(privateKey);
|
||||
s.update(data);
|
||||
return s.sign();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given some data and a signature, uses the key pair stored in the Android
|
||||
* Key Store to verify that the data was signed by this application, using
|
||||
* that key pair.
|
||||
*
|
||||
* @param data The data to be verified.
|
||||
* @param signature The signature provided for the data.
|
||||
* @return A boolean value telling you whether the signature is valid or
|
||||
* not.
|
||||
*/
|
||||
private static boolean verifyData(String alias, byte[] data, byte[] signature)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
||||
|
||||
// Make sure the signature string exists
|
||||
if (signature == null) {
|
||||
Timber.w("Invalid signature.");
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyStore.PrivateKeyEntry keyEntry = getPrivateKeyEntry(alias);
|
||||
Signature s = Signature.getInstance(SecurityConstants.SIGNATURE_SHA256withRSA);
|
||||
s.initVerify(keyEntry.getCertificate());
|
||||
s.update(data);
|
||||
return s.verify(signature);
|
||||
}
|
||||
|
||||
public interface SecurityConstants {
|
||||
String KEYSTORE_PROVIDER_ANDROID_KEYSTORE = "AndroidKeyStore";
|
||||
String TYPE_RSA = "RSA";
|
||||
|
@@ -100,6 +100,9 @@ public class RestoreHeight {
|
||||
blockheight.put("2018-12-01", 1716687L);
|
||||
blockheight.put("2019-01-01", 1738923L);
|
||||
blockheight.put("2019-02-01", 1761435L);
|
||||
blockheight.put("2019-03-01", 1781681L);
|
||||
blockheight.put("2019-04-01", 1803081L);
|
||||
blockheight.put("2019-05-01", 1824671L);
|
||||
}
|
||||
|
||||
public long getHeight(String date) {
|
||||
|
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Based on
|
||||
* https://stackoverflow.com/a/19943894
|
||||
*
|
||||
* Curve parameters from
|
||||
* https://en.bitcoin.it/wiki/Secp256k1
|
||||
*
|
||||
* Copyright (c) 2019 m2049r
|
||||
* Copyright (c) 2013 ChiaraHsieh
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet.util.ledger;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.spec.ECPoint;
|
||||
|
||||
public class ECsecp256k1 {
|
||||
static private final BigInteger TWO = new BigInteger("2");
|
||||
static private final BigInteger THREE = new BigInteger("3");
|
||||
static public final BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
|
||||
static public final BigInteger a = new BigInteger("0000000000000000000000000000000000000000000000000000000000000000", 16);
|
||||
static public final BigInteger b = new BigInteger("0000000000000000000000000000000000000000000000000000000000000007", 16);
|
||||
static public final BigInteger n = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);
|
||||
static public final ECPoint G = new ECPoint(
|
||||
new BigInteger("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16),
|
||||
new BigInteger("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16));
|
||||
|
||||
public static ECPoint scalmult(BigInteger kin, ECPoint P) {
|
||||
ECPoint R = ECPoint.POINT_INFINITY, S = P;
|
||||
BigInteger k = kin.mod(n); // not necessary b/c that's how curves work
|
||||
int length = k.bitLength();
|
||||
byte[] binarray = new byte[length];
|
||||
for (int i = 0; i <= length - 1; i++) {
|
||||
binarray[i] = k.mod(TWO).byteValue();
|
||||
k = k.divide(TWO);
|
||||
}
|
||||
for (int i = length - 1; i >= 0; i--) {
|
||||
// i should start at length-1 not -2 because the MSB of binary may not be 1
|
||||
R = doublePoint(R);
|
||||
if (binarray[i] == 1)
|
||||
R = addPoint(R, S);
|
||||
}
|
||||
return R;
|
||||
}
|
||||
|
||||
public static ECPoint addPoint(ECPoint r, ECPoint s) {
|
||||
if (r.equals(s))
|
||||
return doublePoint(r);
|
||||
else if (r.equals(ECPoint.POINT_INFINITY))
|
||||
return s;
|
||||
else if (s.equals(ECPoint.POINT_INFINITY))
|
||||
return r;
|
||||
BigInteger slope = (r.getAffineY().subtract(s.getAffineY()))
|
||||
.multiply(r.getAffineX().subtract(s.getAffineX()).modInverse(p));
|
||||
BigInteger Xout = (slope.modPow(TWO, p).subtract(r.getAffineX())).subtract(s.getAffineX()).mod(p);
|
||||
BigInteger Yout = s.getAffineY().negate().add(slope.multiply(s.getAffineX().subtract(Xout))).mod(p);
|
||||
return new ECPoint(Xout, Yout);
|
||||
}
|
||||
|
||||
public static ECPoint doublePoint(ECPoint r) {
|
||||
if (r.equals(ECPoint.POINT_INFINITY))
|
||||
return r;
|
||||
BigInteger slope = (r.getAffineX().pow(2)).multiply(THREE).add(a)
|
||||
.multiply((r.getAffineY().multiply(TWO)).modInverse(p));
|
||||
BigInteger Xout = slope.pow(2).subtract(r.getAffineX().multiply(TWO)).mod(p);
|
||||
BigInteger Yout = (r.getAffineY().negate()).add(slope.multiply(r.getAffineX().subtract(Xout))).mod(p);
|
||||
return new ECPoint(Xout, Yout);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user