Merge branch 'feat_ui_improvements' into feat_ui_improvements
This commit is contained in:
commit
0514e3984e
41 changed files with 3359 additions and 13 deletions
|
@ -101,4 +101,11 @@ dependencies {
|
||||||
implementation 'id.zelory:compressor:2.1.0'
|
implementation 'id.zelory:compressor:2.1.0'
|
||||||
implementation 'com.vincent.filepicker:MultiTypeFilePicker:1.0.7'
|
implementation 'com.vincent.filepicker:MultiTypeFilePicker:1.0.7'
|
||||||
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
||||||
|
implementation 'commons-codec:commons-codec:1.11'
|
||||||
|
|
||||||
|
implementation ('io.socket:socket.io-client:0.8.3') {
|
||||||
|
// excluding org.json which is provided by Android
|
||||||
|
exclude group: 'org.json', module: 'json'
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,21 @@
|
||||||
<activity android:name=".activities.PatternRequestActivity"
|
<activity android:name=".activities.PatternRequestActivity"
|
||||||
android:noHistory="true">
|
android:noHistory="true">
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name=".activities.PocketRequestActivity"
|
||||||
|
android:noHistory="true">
|
||||||
|
<!--<meta-data
|
||||||
|
android:name="android.nfc.action.TECH_DISCOVERED"
|
||||||
|
android:resource="@xml/tech" />-->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<data
|
||||||
|
android:scheme="https"
|
||||||
|
android:host="my.yubico.com"
|
||||||
|
android:pathPrefix="/neo"/>
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
<activity android:name=".activities.CryptoNetAccountSettingsActivity"
|
<activity android:name=".activities.CryptoNetAccountSettingsActivity"
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
android:windowSoftInputMode="adjustPan">
|
android:windowSoftInputMode="adjustPan">
|
||||||
|
|
|
@ -108,6 +108,7 @@ public class IntroActivity extends CustomActivity {
|
||||||
} else {
|
} else {
|
||||||
//Intent intent = new Intent(this, CreateSeedActivity.class);
|
//Intent intent = new Intent(this, CreateSeedActivity.class);
|
||||||
Intent intent = new Intent(this, BoardActivity.class);
|
Intent intent = new Intent(this, BoardActivity.class);
|
||||||
|
//Intent intent = new Intent(this, PocketRequestActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnTextChanged;
|
import butterknife.OnTextChanged;
|
||||||
import cy.agorise.crystalwallet.R;
|
import cy.agorise.crystalwallet.R;
|
||||||
|
import cy.agorise.crystalwallet.application.CrystalSecurityMonitor;
|
||||||
import cy.agorise.crystalwallet.models.GeneralSetting;
|
import cy.agorise.crystalwallet.models.GeneralSetting;
|
||||||
import cy.agorise.crystalwallet.util.PasswordManager;
|
import cy.agorise.crystalwallet.util.PasswordManager;
|
||||||
import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel;
|
import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel;
|
||||||
|
@ -69,7 +70,11 @@ public class PatternRequestActivity extends AppCompatActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onComplete(List<PatternLockView.Dot> pattern) {
|
public void onComplete(List<PatternLockView.Dot> pattern) {
|
||||||
if (PasswordManager.checkPassword(patternEncrypted,patternToString(pattern))){
|
if (PasswordManager.checkPassword(patternEncrypted,patternToString(pattern))){
|
||||||
thisActivity.finish();
|
if (CrystalSecurityMonitor.getInstance(null).is2ndFactorSet()) {
|
||||||
|
CrystalSecurityMonitor.getInstance(null).call2ndFactor(thisActivity);
|
||||||
|
} else {
|
||||||
|
thisActivity.finish();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
patternLockView.clearPattern();
|
patternLockView.clearPattern();
|
||||||
patternLockView.requestFocus();
|
patternLockView.requestFocus();
|
||||||
|
|
|
@ -15,6 +15,8 @@ import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnTextChanged;
|
import butterknife.OnTextChanged;
|
||||||
import cy.agorise.crystalwallet.R;
|
import cy.agorise.crystalwallet.R;
|
||||||
|
import cy.agorise.crystalwallet.application.CrystalSecurityMonitor;
|
||||||
|
import cy.agorise.crystalwallet.models.AccountSeed;
|
||||||
import cy.agorise.crystalwallet.models.GeneralSetting;
|
import cy.agorise.crystalwallet.models.GeneralSetting;
|
||||||
import cy.agorise.crystalwallet.util.PasswordManager;
|
import cy.agorise.crystalwallet.util.PasswordManager;
|
||||||
import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel;
|
import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel;
|
||||||
|
@ -61,7 +63,11 @@ public class PinRequestActivity extends AppCompatActivity {
|
||||||
callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED)
|
callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED)
|
||||||
void afterPasswordChanged(Editable editable) {
|
void afterPasswordChanged(Editable editable) {
|
||||||
if (PasswordManager.checkPassword(passwordEncrypted, etPassword.getText().toString())) {
|
if (PasswordManager.checkPassword(passwordEncrypted, etPassword.getText().toString())) {
|
||||||
this.finish();
|
if (CrystalSecurityMonitor.getInstance(null).is2ndFactorSet()) {
|
||||||
|
CrystalSecurityMonitor.getInstance(null).call2ndFactor(this);
|
||||||
|
} else {
|
||||||
|
this.finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
package cy.agorise.crystalwallet.activities;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
import android.arch.lifecycle.Observer;
|
||||||
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.nfc.NfcAdapter;
|
||||||
|
import android.nfc.Tag;
|
||||||
|
import android.nfc.tech.IsoDep;
|
||||||
|
import android.nfc.tech.MifareClassic;
|
||||||
|
import android.nfc.tech.NdefFormatable;
|
||||||
|
import android.nfc.tech.NfcA;
|
||||||
|
import android.nfc.tech.NfcF;
|
||||||
|
import android.nfc.tech.NfcV;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.andrognito.patternlockview.PatternLockView;
|
||||||
|
import com.andrognito.patternlockview.listener.PatternLockViewListener;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base32;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import cy.agorise.crystalwallet.R;
|
||||||
|
import cy.agorise.crystalwallet.application.CrystalSecurityMonitor;
|
||||||
|
import cy.agorise.crystalwallet.models.GeneralSetting;
|
||||||
|
import cy.agorise.crystalwallet.util.PasswordManager;
|
||||||
|
import cy.agorise.crystalwallet.util.yubikey.Algorithm;
|
||||||
|
import cy.agorise.crystalwallet.util.yubikey.OathType;
|
||||||
|
import cy.agorise.crystalwallet.util.yubikey.TOTP;
|
||||||
|
import cy.agorise.crystalwallet.util.yubikey.YkOathApi;
|
||||||
|
import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel;
|
||||||
|
|
||||||
|
public class PocketRequestActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private NfcAdapter mNfcAdapter;
|
||||||
|
private PendingIntent pendingIntent;
|
||||||
|
private IntentFilter ndef;
|
||||||
|
IntentFilter[] intentFiltersArray;
|
||||||
|
String[][] techList;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
//Do nothing to prevent the user to use the back button
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_pocket_request);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
|
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||||
|
|
||||||
|
this.configureForegroundDispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void configureForegroundDispatch(){
|
||||||
|
if (mNfcAdapter != null) {
|
||||||
|
pendingIntent = PendingIntent.getActivity(
|
||||||
|
this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
|
||||||
|
|
||||||
|
ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
|
||||||
|
try {
|
||||||
|
ndef.addDataType("*/*"); /* Handles all MIME based dispatches.
|
||||||
|
You should specify only the ones that you need. */
|
||||||
|
} catch (IntentFilter.MalformedMimeTypeException e) {
|
||||||
|
throw new RuntimeException("fail", e);
|
||||||
|
}
|
||||||
|
intentFiltersArray = new IntentFilter[]{ndef,};
|
||||||
|
techList = new String[][]{ new String[] {IsoDep.class.getName(), NfcA.class.getName(), MifareClassic.class.getName(), NdefFormatable.class.getName()} };
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "This device doesn't support NFC.", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
if (mNfcAdapter != null) {
|
||||||
|
mNfcAdapter.disableForegroundDispatch(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (mNfcAdapter != null) {
|
||||||
|
mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNewIntent(Intent intent) {
|
||||||
|
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
||||||
|
IsoDep tagIsoDep = IsoDep.get(tagFromIntent);
|
||||||
|
Log.i("Tag from nfc","New Intent");
|
||||||
|
String yubikeySecret = CrystalSecurityMonitor.getInstance(null).get2ndFactorValue();
|
||||||
|
|
||||||
|
try {
|
||||||
|
tagIsoDep.connect();
|
||||||
|
YkOathApi ykOathApi = new YkOathApi(tagIsoDep);
|
||||||
|
|
||||||
|
long unixTime = System.currentTimeMillis() / 1000L;
|
||||||
|
byte[] timeStep = ByteBuffer.allocate(8).putLong(unixTime / 30L).array();
|
||||||
|
byte[] response;
|
||||||
|
response = ykOathApi.calculate("cy.agorise.crystalwallet",timeStep,true);
|
||||||
|
ByteBuffer responseBB = ByteBuffer.wrap(response);
|
||||||
|
int digits = (int)responseBB.get();
|
||||||
|
String challengeString = ""+(responseBB.getInt());
|
||||||
|
String challenge = challengeString.substring(challengeString.length()-digits);
|
||||||
|
while (challenge.length() < digits){
|
||||||
|
challenge = '0'+challenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
String storedChallenge = PasswordManager.totpd(yubikeySecret, unixTime, ykOathApi.getDeviceSalt());
|
||||||
|
|
||||||
|
Toast.makeText(this, "Secret:"+yubikeySecret+" StoredChallenge:"+storedChallenge+" Yubikey:"+challenge , Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
|
Log.i("TOTP","Secret: "+yubikeySecret);
|
||||||
|
Log.i("TOTP", "Unixtime: "+unixTime);
|
||||||
|
Log.i("TOTP", "Step: "+unixTime/30L);
|
||||||
|
Log.i("TOTP", "StoredChallenge: "+storedChallenge);
|
||||||
|
Log.i("TOTP", "Yubikey: "+challenge);
|
||||||
|
|
||||||
|
tagIsoDep.close();
|
||||||
|
//ykOathApi.
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package cy.agorise.crystalwallet.activities;
|
package cy.agorise.crystalwallet.activities;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.media.MediaPlayer;
|
import android.media.MediaPlayer;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
@ -47,6 +48,8 @@ public class SettingsActivity extends AppCompatActivity{
|
||||||
@BindView(R.id.tvBuildVersion)
|
@BindView(R.id.tvBuildVersion)
|
||||||
public TextView tvBuildVersion;
|
public TextView tvBuildVersion;
|
||||||
|
|
||||||
|
private SecuritySettingsFragment securitySettingsFragment;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -97,7 +100,8 @@ public class SettingsActivity extends AppCompatActivity{
|
||||||
case 0:
|
case 0:
|
||||||
return new GeneralSettingsFragment();
|
return new GeneralSettingsFragment();
|
||||||
case 1:
|
case 1:
|
||||||
return new SecuritySettingsFragment();
|
securitySettingsFragment = new SecuritySettingsFragment();
|
||||||
|
return securitySettingsFragment;
|
||||||
case 2:
|
case 2:
|
||||||
return new BackupsSettingsFragment();
|
return new BackupsSettingsFragment();
|
||||||
//case 3:
|
//case 3:
|
||||||
|
@ -123,4 +127,12 @@ public class SettingsActivity extends AppCompatActivity{
|
||||||
public void goBack(){
|
public void goBack(){
|
||||||
onBackPressed();
|
onBackPressed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
if (this.securitySettingsFragment != null){
|
||||||
|
this.securitySettingsFragment.onNewIntent(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
import okhttp3.Interceptor;
|
import okhttp3.Interceptor;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.logging.HttpLoggingInterceptor;
|
import okhttp3.logging.HttpLoggingInterceptor;
|
||||||
|
@ -217,8 +218,9 @@ public abstract class BitsharesFaucetApiGenerator {
|
||||||
|
|
||||||
public interface IWebService {
|
public interface IWebService {
|
||||||
@Headers({"Content-Type: application/json"})
|
@Headers({"Content-Type: application/json"})
|
||||||
@POST("/api/v1/accounts")
|
@POST("/faucet/api/v1/accounts")
|
||||||
Call<RegisterAccountResponse> getReg(@Body Map<String, HashMap> params);
|
Call<RegisterAccountResponse> getReg(@Body Map<String, HashMap> params);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RegisterAccountResponse {
|
public class RegisterAccountResponse {
|
||||||
|
|
|
@ -610,7 +610,7 @@ public abstract class GrapheneApiGenerator {
|
||||||
public void onError(BaseResponse.Error error) {
|
public void onError(BaseResponse.Error error) {
|
||||||
request.getListener().fail(request.getId());
|
request.getListener().fail(request.getId());
|
||||||
}
|
}
|
||||||
}), BitsharesConstant.EQUIVALENT_URL); //todo change equivalent url for current server url
|
}), CryptoNetManager.getURL(CryptoNet.BITSHARES)); //todo change equivalent url for current server url
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -626,7 +626,7 @@ public abstract class GrapheneApiGenerator {
|
||||||
for (BitsharesAsset quoteAsset : quoteAssets) {
|
for (BitsharesAsset quoteAsset : quoteAssets) {
|
||||||
WebSocketThread thread = new WebSocketThread(new GetLimitOrders(baseAsset.getBitsharesId(),
|
WebSocketThread thread = new WebSocketThread(new GetLimitOrders(baseAsset.getBitsharesId(),
|
||||||
quoteAsset.getBitsharesId(), 10, new EquivalentValueListener(baseAsset,
|
quoteAsset.getBitsharesId(), 10, new EquivalentValueListener(baseAsset,
|
||||||
quoteAsset, context)), BitsharesConstant.EQUIVALENT_URL); //todo change equivalent url for current server url
|
quoteAsset, context)), CryptoNetManager.getURL(CryptoNet.BITSHARES)); //todo change equivalent url for current server url
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import cy.agorise.crystalwallet.models.GeneralCoinAccount;
|
||||||
|
import io.socket.client.IO;
|
||||||
|
import io.socket.client.Socket;
|
||||||
|
import io.socket.emitter.Emitter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all the calls for the Socket.IO of the insight api
|
||||||
|
*
|
||||||
|
* Only gets new transaction in real time for each address of an Account
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class AccountActivityWatcher {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mAccount to be monitor
|
||||||
|
*/
|
||||||
|
private final GeneralCoinAccount mAccount;
|
||||||
|
/**
|
||||||
|
* The list of address to monitor
|
||||||
|
*/
|
||||||
|
private List<String> mWatchAddress = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* the Socket.IO
|
||||||
|
*/
|
||||||
|
private Socket mSocket;
|
||||||
|
/**
|
||||||
|
* This app mContext, used to save on the DB
|
||||||
|
*/
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the address/transaction notification.
|
||||||
|
* Then calls the GetTransactionData to get the info of the new transaction
|
||||||
|
*/
|
||||||
|
private final Emitter.Listener onAddressTransaction = new Emitter.Listener() {
|
||||||
|
@Override
|
||||||
|
public void call(Object... os) {
|
||||||
|
try {
|
||||||
|
System.out.println("Receive accountActivtyWatcher " + os[0].toString() );
|
||||||
|
String txid = ((JSONObject) os[0]).getString(InsightApiConstants.sTxTag);
|
||||||
|
new GetTransactionData(txid, mAccount, mContext).start();
|
||||||
|
} catch (JSONException ex) {
|
||||||
|
Logger.getLogger(AccountActivityWatcher.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the connect of the Socket.IO
|
||||||
|
*/
|
||||||
|
private final Emitter.Listener onConnect = new Emitter.Listener() {
|
||||||
|
@Override
|
||||||
|
public void call(Object... os) {
|
||||||
|
System.out.println("Connected to accountActivityWatcher");
|
||||||
|
JSONArray array = new JSONArray();
|
||||||
|
for(String addr : mWatchAddress) {
|
||||||
|
array.put(addr);
|
||||||
|
}
|
||||||
|
mSocket.emit(InsightApiConstants.sSubscribeEmmit, InsightApiConstants.sChangeAddressRoom, array);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the disconnect of the Socket.Io
|
||||||
|
* Reconcects the mSocket
|
||||||
|
*/
|
||||||
|
private final Emitter.Listener onDisconnect = new Emitter.Listener() {
|
||||||
|
@Override
|
||||||
|
public void call(Object... os) {
|
||||||
|
System.out.println("Disconnected to accountActivityWatcher");
|
||||||
|
mSocket.connect();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error handler, doesn't need reconnect, the mSocket.io do that by default
|
||||||
|
*/
|
||||||
|
private final Emitter.Listener onError = new Emitter.Listener() {
|
||||||
|
@Override
|
||||||
|
public void call(Object... os) {
|
||||||
|
System.out.println("Error to accountActivityWatcher ");
|
||||||
|
for(Object ob : os) {
|
||||||
|
System.out.println("accountActivityWatcher " + ob.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic constructor
|
||||||
|
*
|
||||||
|
* @param mAccount The mAccount to be monitor
|
||||||
|
* @param mContext This app mContext
|
||||||
|
*/
|
||||||
|
public AccountActivityWatcher(GeneralCoinAccount mAccount, Context mContext) {
|
||||||
|
//String serverUrl = InsightApiConstants.protocol + "://" + InsightApiConstants.getAddress(mAccount.getCoin()) + ":" + InsightApiConstants.getPort(mAccount.getCoin()) + "/"+InsightApiConstants.getRawPath(mAccount.getCoin())+"/mSocket.io/";
|
||||||
|
String serverUrl = InsightApiConstants.sProtocolSocketIO + "://" + InsightApiConstants.getAddress(mAccount.getCryptoCoin()) + ":" + InsightApiConstants.getPort(mAccount.getCryptoCoin()) + "/";
|
||||||
|
this.mAccount = mAccount;
|
||||||
|
this.mContext = mContext;
|
||||||
|
System.out.println("accountActivityWatcher " + serverUrl);
|
||||||
|
try {
|
||||||
|
IO.Options opts = new IO.Options();
|
||||||
|
System.out.println("accountActivityWatcher default path " + opts.path);
|
||||||
|
this.mSocket = IO.socket(serverUrl);
|
||||||
|
this.mSocket.on(Socket.EVENT_CONNECT, onConnect);
|
||||||
|
this.mSocket.on(Socket.EVENT_DISCONNECT, onDisconnect);
|
||||||
|
this.mSocket.on(Socket.EVENT_ERROR, onError);
|
||||||
|
this.mSocket.on(Socket.EVENT_CONNECT_ERROR, onError);
|
||||||
|
this.mSocket.on(Socket.EVENT_CONNECT_TIMEOUT, onError);
|
||||||
|
this.mSocket.on(InsightApiConstants.sChangeAddressRoom, onAddressTransaction);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
//TODO change exception handler
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an address to be monitored, it can be used after the connect
|
||||||
|
* @param address The String address to monitor
|
||||||
|
*/
|
||||||
|
public void addAddress(String address) {
|
||||||
|
mWatchAddress.add(address);
|
||||||
|
if (this.mSocket.connected()) {
|
||||||
|
mSocket.emit(InsightApiConstants.sSubscribeEmmit, InsightApiConstants.sChangeAddressRoom, new String[]{address});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects the Socket
|
||||||
|
*/
|
||||||
|
public void connect() {
|
||||||
|
//TODO change to use log
|
||||||
|
System.out.println("accountActivityWatcher connecting");
|
||||||
|
try{
|
||||||
|
this.mSocket.connect();
|
||||||
|
}catch(Exception e){
|
||||||
|
//TODO change exception handler
|
||||||
|
System.out.println("accountActivityWatcher exception " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects the Socket
|
||||||
|
*/
|
||||||
|
public void disconnect() {this.mSocket.disconnect();}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi;
|
||||||
|
import cy.agorise.crystalwallet.models.GeneralCoinAccount;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast a transaction, using the InsightApi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class BroadcastTransaction extends Thread implements Callback<Txi> {
|
||||||
|
/**
|
||||||
|
* The rawTX as Hex String
|
||||||
|
*/
|
||||||
|
private String mRawTx;
|
||||||
|
/**
|
||||||
|
* The serviceGenerator to call
|
||||||
|
*/
|
||||||
|
private InsightApiServiceGenerator mServiceGenerator;
|
||||||
|
/**
|
||||||
|
* This app context, used to save on the DB
|
||||||
|
*/
|
||||||
|
private Context mContext;
|
||||||
|
/**
|
||||||
|
* The account who sign the transaction
|
||||||
|
*/
|
||||||
|
private GeneralCoinAccount mAccount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic Consturctor
|
||||||
|
* @param RawTx The RawTX in Hex String
|
||||||
|
* @param account The account who signs the transaction
|
||||||
|
* @param context This app context
|
||||||
|
*/
|
||||||
|
public BroadcastTransaction(String RawTx, GeneralCoinAccount account, Context context){
|
||||||
|
String serverUrl = InsightApiConstants.sProtocol + "://" + InsightApiConstants.getAddress(account.getCryptoCoin()) +"/";
|
||||||
|
this.mServiceGenerator = new InsightApiServiceGenerator(serverUrl);
|
||||||
|
this.mContext = context;
|
||||||
|
this.mRawTx = RawTx;
|
||||||
|
this.mAccount = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the response of the call
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<Txi> call, Response<Txi> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
//TODO invalidated send
|
||||||
|
//TODO call getTransactionData
|
||||||
|
GetTransactionData trData = new GetTransactionData(response.body().txid,this.mAccount,this.mContext);
|
||||||
|
trData.start();
|
||||||
|
} else {
|
||||||
|
System.out.println("SENDTEST: not succesful " + response.message());
|
||||||
|
//TODO change how to handle invalid transaction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the failures of the call
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Txi> call, Throwable t) {
|
||||||
|
//TODO change how to handle invalid transaction
|
||||||
|
System.out.println("SENDTEST: sendError " + t.getMessage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the call of the service
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
InsightApiService service = this.mServiceGenerator.getService(InsightApiService.class);
|
||||||
|
Call<Txi> broadcastTransaction = service.broadcastTransaction(InsightApiConstants.getPath(this.mAccount.getCryptoCoin()),this.mRawTx);
|
||||||
|
broadcastTransaction.enqueue(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import cy.agorise.crystalwallet.enums.CryptoCoin;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the estimete fee amount from an insight api server.
|
||||||
|
* This class gets the rate of the fee for a giving coin in about to block for a transaction to be
|
||||||
|
* confirmated.
|
||||||
|
*
|
||||||
|
* This ammount is giving as amount of currency / kbytes, as example btc / kbytes
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public abstract class GetEstimateFee {
|
||||||
|
|
||||||
|
//TODO add a funciton to get the rate of a specific port
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The funciton to get the rate for the transaction be included in the next 2 blocks
|
||||||
|
* @param coin The coin to get the rate
|
||||||
|
* @return The rate number (coin/kbytes)
|
||||||
|
* @throws IOException If the server answer null, or the rate couldn't be calculated
|
||||||
|
*/
|
||||||
|
public static long getEstimateFee(final CryptoCoin coin) throws IOException {
|
||||||
|
String serverUrl = InsightApiConstants.sProtocol + "://"
|
||||||
|
+ InsightApiConstants.getAddress(coin) + "/";
|
||||||
|
InsightApiServiceGenerator serviceGenerator = new InsightApiServiceGenerator(serverUrl);
|
||||||
|
InsightApiService service = serviceGenerator.getService(InsightApiService.class);
|
||||||
|
Call<JsonObject> call = service.estimateFee(InsightApiConstants.getPath(coin));
|
||||||
|
final Object SYNC = new Object();
|
||||||
|
final JsonObject answer = new JsonObject();
|
||||||
|
call.enqueue(new Callback<JsonObject>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
|
||||||
|
synchronized (SYNC) {
|
||||||
|
answer.addProperty("answer",
|
||||||
|
(long) (response.body().get("2").getAsDouble()* Math.pow(10, coin.getPrecision())));
|
||||||
|
SYNC.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<JsonObject> call, Throwable t) {
|
||||||
|
synchronized (SYNC) {
|
||||||
|
SYNC.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
synchronized (SYNC){
|
||||||
|
for(int i = 0; i < 6; i++) {
|
||||||
|
try {
|
||||||
|
SYNC.wait(5000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// this interruption never rises
|
||||||
|
}
|
||||||
|
if(answer.get("answer")!=null){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(answer.get("answer")==null){
|
||||||
|
throw new IOException("");
|
||||||
|
}
|
||||||
|
return (long) (answer.get("answer").getAsDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,207 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import cy.agorise.crystalwallet.apigenerator.insightapi.models.AddressTxi;
|
||||||
|
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi;
|
||||||
|
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vin;
|
||||||
|
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vout;
|
||||||
|
import cy.agorise.crystalwallet.models.GTxIO;
|
||||||
|
import cy.agorise.crystalwallet.models.GeneralCoinAccount;
|
||||||
|
import cy.agorise.crystalwallet.models.GeneralCoinAddress;
|
||||||
|
import cy.agorise.crystalwallet.models.GeneralTransaction;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the transaction data of the addresses of an account
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class GetTransactionByAddress extends Thread implements Callback<AddressTxi> {
|
||||||
|
/**
|
||||||
|
* The account to be query
|
||||||
|
*/
|
||||||
|
private GeneralCoinAccount mAccount;
|
||||||
|
/**
|
||||||
|
* The list of address to query
|
||||||
|
*/
|
||||||
|
private List<GeneralCoinAddress> mAddresses = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* The serviceGenerator to call
|
||||||
|
*/
|
||||||
|
private InsightApiServiceGenerator mServiceGenerator;
|
||||||
|
/**
|
||||||
|
* This app context, used to save on the DB
|
||||||
|
*/
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic consturcotr
|
||||||
|
* @param account The account to be query
|
||||||
|
* @param context This app context
|
||||||
|
*/
|
||||||
|
public GetTransactionByAddress(GeneralCoinAccount account, Context context) {
|
||||||
|
String serverUrl = InsightApiConstants.sProtocol + "://" + InsightApiConstants.getAddress(account.getCryptoCoin()) +"/";
|
||||||
|
this.mAccount = account;
|
||||||
|
this.mServiceGenerator = new InsightApiServiceGenerator(serverUrl);
|
||||||
|
this.mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add an address to be query
|
||||||
|
* @param address the address to be query
|
||||||
|
*/
|
||||||
|
public void addAddress(GeneralCoinAddress address) {
|
||||||
|
this.mAddresses.add(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the response
|
||||||
|
* @param call The call with the addresTxi object
|
||||||
|
* @param response the response status object
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<AddressTxi> call, Response<AddressTxi> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
boolean changed = false;
|
||||||
|
AddressTxi addressTxi = response.body();
|
||||||
|
|
||||||
|
for (Txi txi : addressTxi.items) {
|
||||||
|
GeneralCoinAccount tempAccount = null;
|
||||||
|
GeneralTransaction transaction = new GeneralTransaction();
|
||||||
|
transaction.setAccount(this.mAccount);
|
||||||
|
transaction.setTxid(txi.txid);
|
||||||
|
transaction.setBlock(txi.blockheight);
|
||||||
|
transaction.setDate(new Date(txi.time * 1000));
|
||||||
|
transaction.setFee((long) (txi.fee * Math.pow(10,this.mAccount.getCryptoCoin().getPrecision())));
|
||||||
|
transaction.setConfirm(txi.confirmations);
|
||||||
|
transaction.setType(this.mAccount.getCryptoCoin());
|
||||||
|
transaction.setBlockHeight(txi.blockheight);
|
||||||
|
|
||||||
|
for (Vin vin : txi.vin) {
|
||||||
|
GTxIO input = new GTxIO();
|
||||||
|
input.setAmount((long) (vin.value * Math.pow(10,this.mAccount.getCryptoCoin().getPrecision())));
|
||||||
|
input.setTransaction(transaction);
|
||||||
|
input.setOut(true);
|
||||||
|
input.setType(this.mAccount.getCryptoCoin());
|
||||||
|
String addr = vin.addr;
|
||||||
|
input.setAddressString(addr);
|
||||||
|
input.setIndex(vin.n);
|
||||||
|
input.setScriptHex(vin.scriptSig.hex);
|
||||||
|
input.setOriginalTxid(vin.txid);
|
||||||
|
for (GeneralCoinAddress address : this.mAddresses) {
|
||||||
|
if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) {
|
||||||
|
input.setAddress(address);
|
||||||
|
tempAccount = address.getAccount();
|
||||||
|
|
||||||
|
if (!address.hasTransactionOutput(input, this.mAccount.getNetworkParam())) {
|
||||||
|
address.getTransactionOutput().add(input);
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transaction.getTxInputs().add(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Vout vout : txi.vout) {
|
||||||
|
if(vout.scriptPubKey.addresses == null || vout.scriptPubKey.addresses.length <= 0){
|
||||||
|
// The address is null, this must be a memo
|
||||||
|
String hex = vout.scriptPubKey.hex;
|
||||||
|
int opReturnIndex = hex.indexOf("6a");
|
||||||
|
if(opReturnIndex >= 0) {
|
||||||
|
byte[] memoBytes = new byte[Integer.parseInt(hex.substring(opReturnIndex+2,opReturnIndex+4),16)];
|
||||||
|
for(int i = 0; i < memoBytes.length;i++){
|
||||||
|
memoBytes[i] = Byte.parseByte(hex.substring(opReturnIndex+4+(i*2),opReturnIndex+6+(i*2)),16);
|
||||||
|
}
|
||||||
|
transaction.setMemo(new String(memoBytes));
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
GTxIO output = new GTxIO();
|
||||||
|
output.setAmount((long) (vout.value * Math.pow(10, this.mAccount.getCryptoCoin().getPrecision())));
|
||||||
|
output.setTransaction(transaction);
|
||||||
|
output.setOut(false);
|
||||||
|
output.setType(this.mAccount.getCryptoCoin());
|
||||||
|
String addr = vout.scriptPubKey.addresses[0];
|
||||||
|
output.setAddressString(addr);
|
||||||
|
output.setIndex(vout.n);
|
||||||
|
output.setScriptHex(vout.scriptPubKey.hex);
|
||||||
|
for (GeneralCoinAddress address : this.mAddresses) {
|
||||||
|
if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) {
|
||||||
|
output.setAddress(address);
|
||||||
|
tempAccount = address.getAccount();
|
||||||
|
|
||||||
|
if (!address.hasTransactionInput(output, this.mAccount.getNetworkParam())) {
|
||||||
|
address.getTransactionInput().add(output);
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.getTxOutputs().add(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(txi.txlock && txi.confirmations< this.mAccount.getCryptoNet().getConfirmationsNeeded()){
|
||||||
|
transaction.setConfirm(this.mAccount.getCryptoNet().getConfirmationsNeeded());
|
||||||
|
}
|
||||||
|
//TODO database
|
||||||
|
/*SCWallDatabase db = new SCWallDatabase(this.mContext);
|
||||||
|
long idTransaction = db.getGeneralTransactionId(transaction);
|
||||||
|
if (idTransaction == -1) {
|
||||||
|
db.putGeneralTransaction(transaction);
|
||||||
|
} else {
|
||||||
|
transaction.setId(idTransaction);
|
||||||
|
db.updateGeneralTransaction(transaction);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if (tempAccount != null && transaction.getConfirm() < this.mAccount.getCryptoNet().getConfirmationsNeeded()) {
|
||||||
|
new GetTransactionData(transaction.getTxid(), tempAccount, this.mContext, true).start();
|
||||||
|
}
|
||||||
|
for (GeneralCoinAddress address : this.mAddresses) {
|
||||||
|
if (address.updateTransaction(transaction)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(changed) {
|
||||||
|
this.mAccount.balanceChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Failure of the call
|
||||||
|
* @param call The call object
|
||||||
|
* @param t The reason for the failure
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<AddressTxi> call, Throwable t) {
|
||||||
|
Log.e("GetTransactionByAddress", "Error in json format");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to start the insight api call
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (this.mAddresses.size() > 0) {
|
||||||
|
StringBuilder addressToQuery = new StringBuilder();
|
||||||
|
for (GeneralCoinAddress address : this.mAddresses) {
|
||||||
|
addressToQuery.append(address.getAddressString(this.mAccount.getNetworkParam())).append(",");
|
||||||
|
}
|
||||||
|
addressToQuery.deleteCharAt(addressToQuery.length() - 1);
|
||||||
|
InsightApiService service = this.mServiceGenerator.getService(InsightApiService.class);
|
||||||
|
Call<AddressTxi> addressTxiCall = service.getTransactionByAddress(InsightApiConstants.getPath(this.mAccount.getCryptoCoin()),addressToQuery.toString());
|
||||||
|
addressTxiCall.enqueue(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi;
|
||||||
|
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vin;
|
||||||
|
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vout;
|
||||||
|
import cy.agorise.crystalwallet.models.GTxIO;
|
||||||
|
import cy.agorise.crystalwallet.models.GeneralCoinAccount;
|
||||||
|
import cy.agorise.crystalwallet.models.GeneralCoinAddress;
|
||||||
|
import cy.agorise.crystalwallet.models.GeneralTransaction;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CThis class retrieve the data of a single transaction
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class GetTransactionData extends Thread implements Callback<Txi> {
|
||||||
|
/**
|
||||||
|
* The account to be query
|
||||||
|
*/
|
||||||
|
private final GeneralCoinAccount mAccount;
|
||||||
|
/**
|
||||||
|
* The transaction txid to be query
|
||||||
|
*/
|
||||||
|
private String mTxId;
|
||||||
|
/**
|
||||||
|
* The serviceGenerator to call
|
||||||
|
*/
|
||||||
|
private InsightApiServiceGenerator mServiceGenerator;
|
||||||
|
/**
|
||||||
|
* This app context, used to save on the DB
|
||||||
|
*/
|
||||||
|
private Context mContext;
|
||||||
|
/**
|
||||||
|
* If has to wait for another confirmation
|
||||||
|
*/
|
||||||
|
private boolean mMustWait = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor used to query for a transaction with unknown confirmations
|
||||||
|
* @param txid The txid of the transaciton to be query
|
||||||
|
* @param account The account to be query
|
||||||
|
* @param context This app Context
|
||||||
|
*/
|
||||||
|
public GetTransactionData(String txid, GeneralCoinAccount account, Context context) {
|
||||||
|
this(txid, account, context, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consturctor to be used qhen the confirmations of the transaction are known
|
||||||
|
* @param txid The txid of the transaciton to be query
|
||||||
|
* @param account The account to be query
|
||||||
|
* @param context This app Context
|
||||||
|
* @param mustWait If there is less confirmation that needed
|
||||||
|
*/
|
||||||
|
public GetTransactionData(String txid, GeneralCoinAccount account, Context context, boolean mustWait) {
|
||||||
|
String serverUrl = InsightApiConstants.sProtocol + "://" + InsightApiConstants.getAddress(account.getCryptoCoin()) +"/";
|
||||||
|
this.mAccount = account;
|
||||||
|
this.mTxId= txid;
|
||||||
|
this.mServiceGenerator = new InsightApiServiceGenerator(serverUrl);
|
||||||
|
this.mContext = context;
|
||||||
|
this.mMustWait = mustWait;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to start the insight api call
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (this.mMustWait) {
|
||||||
|
//We are waiting for confirmation
|
||||||
|
try {
|
||||||
|
Thread.sleep(InsightApiConstants.sWaitTime);
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
//TODO this exception never rises
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InsightApiService service = this.mServiceGenerator.getService(InsightApiService.class);
|
||||||
|
Call<Txi> txiCall = service.getTransaction(InsightApiConstants.getPath(this.mAccount.getCryptoCoin()),this.mTxId);
|
||||||
|
txiCall.enqueue(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<Txi> call, Response<Txi> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
Txi txi = response.body();
|
||||||
|
|
||||||
|
GeneralTransaction transaction = new GeneralTransaction();
|
||||||
|
transaction.setAccount(this.mAccount);
|
||||||
|
transaction.setTxid(txi.txid);
|
||||||
|
transaction.setBlock(txi.blockheight);
|
||||||
|
transaction.setDate(new Date(txi.time * 1000));
|
||||||
|
transaction.setFee((long) (txi.fee * Math.pow(10,this.mAccount.getCryptoCoin().getPrecision())));
|
||||||
|
transaction.setConfirm(txi.confirmations);
|
||||||
|
transaction.setType(this.mAccount.getCryptoCoin());
|
||||||
|
transaction.setBlockHeight(txi.blockheight);
|
||||||
|
|
||||||
|
for (Vin vin : txi.vin) {
|
||||||
|
GTxIO input = new GTxIO();
|
||||||
|
input.setAmount((long) (vin.value * Math.pow(10,this.mAccount.getCryptoCoin().getPrecision())));
|
||||||
|
input.setTransaction(transaction);
|
||||||
|
input.setOut(true);
|
||||||
|
input.setType(this.mAccount.getCryptoCoin());
|
||||||
|
String addr = vin.addr;
|
||||||
|
input.setAddressString(addr);
|
||||||
|
input.setIndex(vin.n);
|
||||||
|
input.setScriptHex(vin.scriptSig.hex);
|
||||||
|
input.setOriginalTxid(vin.txid);
|
||||||
|
for (GeneralCoinAddress address : this.mAccount.getAddresses()) {
|
||||||
|
if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) {
|
||||||
|
input.setAddress(address);
|
||||||
|
if (!address.hasTransactionOutput(input, this.mAccount.getNetworkParam())) {
|
||||||
|
address.getTransactionOutput().add(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transaction.getTxInputs().add(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Vout vout : txi.vout) {
|
||||||
|
if(vout.scriptPubKey.addresses == null || vout.scriptPubKey.addresses.length <= 0){
|
||||||
|
// The address is null, this must be a memo
|
||||||
|
String hex = vout.scriptPubKey.hex;
|
||||||
|
int opReturnIndex = hex.indexOf("6a");
|
||||||
|
if(opReturnIndex >= 0) {
|
||||||
|
byte[] memoBytes = new byte[Integer.parseInt(hex.substring(opReturnIndex+2,opReturnIndex+4),16)];
|
||||||
|
for(int i = 0; i < memoBytes.length;i++){
|
||||||
|
memoBytes[i] = Byte.parseByte(hex.substring(opReturnIndex+4+(i*2),opReturnIndex+6+(i*2)),16);
|
||||||
|
}
|
||||||
|
transaction.setMemo(new String(memoBytes));
|
||||||
|
System.out.println("Memo read : " + transaction.getMemo()); //TODO log this line
|
||||||
|
}
|
||||||
|
|
||||||
|
}else {
|
||||||
|
GTxIO output = new GTxIO();
|
||||||
|
output.setAmount((long) (vout.value * Math.pow(10, this.mAccount.getCryptoCoin().getPrecision())));
|
||||||
|
output.setTransaction(transaction);
|
||||||
|
output.setOut(false);
|
||||||
|
output.setType(this.mAccount.getCryptoCoin());
|
||||||
|
String addr = vout.scriptPubKey.addresses[0];
|
||||||
|
output.setAddressString(addr);
|
||||||
|
output.setIndex(vout.n);
|
||||||
|
output.setScriptHex(vout.scriptPubKey.hex);
|
||||||
|
for (GeneralCoinAddress address : this.mAccount.getAddresses()) {
|
||||||
|
if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) {
|
||||||
|
output.setAddress(address);
|
||||||
|
if (!address.hasTransactionInput(output, this.mAccount.getNetworkParam())) {
|
||||||
|
address.getTransactionInput().add(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transaction.getTxOutputs().add(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for features like dash instantSend
|
||||||
|
if(txi.txlock && txi.confirmations< this.mAccount.getCryptoNet().getConfirmationsNeeded()){
|
||||||
|
transaction.setConfirm(this.mAccount.getCryptoNet().getConfirmationsNeeded());
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO database
|
||||||
|
/*SCWallDatabase db = new SCWallDatabase(this.mContext);
|
||||||
|
long idTransaction = db.getGeneralTransactionId(transaction);
|
||||||
|
if (idTransaction == -1) {
|
||||||
|
db.putGeneralTransaction(transaction);
|
||||||
|
} else {
|
||||||
|
transaction.setId(idTransaction);
|
||||||
|
db.updateGeneralTransaction(transaction);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
this.mAccount.updateTransaction(transaction);
|
||||||
|
this.mAccount.balanceChange();
|
||||||
|
|
||||||
|
if (transaction.getConfirm() < this.mAccount.getCryptoNet().getConfirmationsNeeded()) {
|
||||||
|
//If transaction weren't confirmed, add the transaction to watch for change on the confirmations
|
||||||
|
new GetTransactionData(this.mTxId, this.mAccount, this.mContext, true).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO handle the failure response
|
||||||
|
* @param call the Call object
|
||||||
|
* @param t the reason of the failure
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Txi> call, Throwable t) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import cy.agorise.crystalwallet.enums.CryptoCoin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class holds all constant related to the Insight Api
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
abstract class InsightApiConstants {
|
||||||
|
/**
|
||||||
|
* Protocol of the insight api calls
|
||||||
|
*/
|
||||||
|
static final String sProtocol = "https";
|
||||||
|
/**
|
||||||
|
* Protocol of the insigiht api Socket.IO connection
|
||||||
|
*/
|
||||||
|
static final String sProtocolSocketIO = "http";
|
||||||
|
/**
|
||||||
|
* Contains each url information for each coin
|
||||||
|
*/
|
||||||
|
private static final HashMap<CryptoCoin,AddressPort> sServerAddressPort = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* Insight api Socket.IO new transaction by address notification
|
||||||
|
*/
|
||||||
|
static final String sChangeAddressRoom = "bitcoind/addresstxid";
|
||||||
|
/**
|
||||||
|
* Socket.io subscribe command
|
||||||
|
*/
|
||||||
|
static final String sSubscribeEmmit = "subscribe";
|
||||||
|
/**
|
||||||
|
* Tag used in the response of the address transaction notification
|
||||||
|
*/
|
||||||
|
static final String sTxTag = "txid";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait time to check for confirmations
|
||||||
|
*/
|
||||||
|
static long sWaitTime = (30 * 1000); //wait 1 minute
|
||||||
|
|
||||||
|
//Filled the serverAddressPort maps with static data
|
||||||
|
static{
|
||||||
|
//serverAddressPort.put(Coin.BITCOIN,new AddressPort("fr.blockpay.ch",3002,"node/btc/testnet","insight-api"));
|
||||||
|
sServerAddressPort.put(CryptoCoin.BITCOIN,new AddressPort("fr.blockpay.ch",3003,"node/btc/testnet","insight-api"));
|
||||||
|
//serverAddressPort.put(Coin.BITCOIN_TEST,new AddressPort("fr.blockpay.ch",3003,"node/btc/testnet","insight-api"));
|
||||||
|
sServerAddressPort.put(CryptoCoin.LITECOIN,new AddressPort("fr.blockpay.ch",3009,"node/ltc","insight-lite-api"));
|
||||||
|
sServerAddressPort.put(CryptoCoin.DASH,new AddressPort("fr.blockpay.ch",3005,"node/dash","insight-api-dash"));
|
||||||
|
sServerAddressPort.put(CryptoCoin.DOGECOIN,new AddressPort("fr.blockpay.ch",3006,"node/dogecoin","insight-api"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the insight api server address
|
||||||
|
* @param coin The coin of the API to find
|
||||||
|
* @return The String address of the server, can be a name or the IP
|
||||||
|
*/
|
||||||
|
static String getAddress(CryptoCoin coin){
|
||||||
|
return sServerAddressPort.get(coin).mServerAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the port of the server Insight API
|
||||||
|
* @param coin The coin of the API to find
|
||||||
|
* @return The server number port
|
||||||
|
*/
|
||||||
|
static int getPort(CryptoCoin coin){
|
||||||
|
return sServerAddressPort.get(coin).mPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the url path of the server Insight API
|
||||||
|
* @param coin The coin of the API to find
|
||||||
|
* @return The path of the Insight API
|
||||||
|
*/
|
||||||
|
static String getPath(CryptoCoin coin){
|
||||||
|
return sServerAddressPort.get(coin).mPath + "/" + sServerAddressPort.get(coin).mInsightPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains all the url info neccessary to connects to the insight api
|
||||||
|
*/
|
||||||
|
private static class AddressPort{
|
||||||
|
/**
|
||||||
|
* The server address
|
||||||
|
*/
|
||||||
|
final String mServerAddress;
|
||||||
|
/**
|
||||||
|
* The port used in the Socket.io
|
||||||
|
*/
|
||||||
|
final int mPort;
|
||||||
|
/**
|
||||||
|
* The path of the coin server
|
||||||
|
*/
|
||||||
|
final String mPath;
|
||||||
|
/**
|
||||||
|
* The path of the insight api
|
||||||
|
*/
|
||||||
|
final String mInsightPath;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param serverAddress The server address of the Insight API
|
||||||
|
* @param port the port number of the Insight API
|
||||||
|
* @param path the path to the Insight API before the last /
|
||||||
|
* @param insightPath the path after the last / of the Insight API
|
||||||
|
*/
|
||||||
|
AddressPort(String serverAddress, int port, String path, String insightPath) {
|
||||||
|
this.mServerAddress = serverAddress;
|
||||||
|
this.mPort = port;
|
||||||
|
this.mPath = path;
|
||||||
|
this.mInsightPath = insightPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import cy.agorise.crystalwallet.apigenerator.insightapi.models.AddressTxi;
|
||||||
|
import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.http.Field;
|
||||||
|
import retrofit2.http.FormUrlEncoded;
|
||||||
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.POST;
|
||||||
|
import retrofit2.http.Path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds each call to the insigh api server
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface InsightApiService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The query for the info of a single transaction
|
||||||
|
* @param path The path of the insight api without the server address
|
||||||
|
* @param txid the transasction to be query
|
||||||
|
*/
|
||||||
|
@GET("{path}/tx/{txid}")
|
||||||
|
Call<Txi> getTransaction(@Path(value = "path", encoded = true) String path, @Path(value = "txid", encoded = true) String txid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The query for the transasctions of multiples addresses
|
||||||
|
* @param path The path of the insight api without the server address
|
||||||
|
* @param addrs the addresses to be query each separated with a ","
|
||||||
|
*/
|
||||||
|
@GET("{path}/addrs/{addrs}/txs")
|
||||||
|
Call<AddressTxi> getTransactionByAddress(@Path(value = "path", encoded = true) String path, @Path(value = "addrs", encoded = true) String addrs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast Transaction
|
||||||
|
* @param path The path of the insight api without the server address
|
||||||
|
* @param rawtx the rawtx to send in Hex String
|
||||||
|
*/
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST("{path}/tx/send")
|
||||||
|
Call<Txi> broadcastTransaction(@Path(value = "path", encoded = true) String path, @Field("rawtx") String rawtx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the estimate rate fee for a coin in the Insight API
|
||||||
|
* @param path The path of the insight api without the server address
|
||||||
|
*/
|
||||||
|
@GET("{path}/utils/estimatefee?nbBlocks=2")
|
||||||
|
Call<JsonObject> estimateFee(@Path(value = "path", encoded = true) String path);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor;
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generatir fir tge okhttp connection of the Insight API
|
||||||
|
* TODO finish documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
class InsightApiServiceGenerator {
|
||||||
|
/**
|
||||||
|
* Tag used for logging
|
||||||
|
*/
|
||||||
|
public static String TAG = "InsightApiServiceGenerator";
|
||||||
|
/**
|
||||||
|
* The complete uri to connect to the insight api, this change from coin to coin
|
||||||
|
*/
|
||||||
|
private static String sApiBaseUrl;
|
||||||
|
/**
|
||||||
|
* Loggin interceptor
|
||||||
|
*/
|
||||||
|
private static HttpLoggingInterceptor sLogging;
|
||||||
|
/**
|
||||||
|
* Http builder
|
||||||
|
*/
|
||||||
|
private static OkHttpClient.Builder sClientBuilder;
|
||||||
|
/**
|
||||||
|
* Builder for the retrofit class
|
||||||
|
*/
|
||||||
|
private static Retrofit.Builder sBuilder;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static HashMap<Class<?>, Object> sServices;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor, using the url of a insigth api coin
|
||||||
|
* @param apiBaseUrl The complete url to the server of the insight api
|
||||||
|
*/
|
||||||
|
InsightApiServiceGenerator(String apiBaseUrl) {
|
||||||
|
sApiBaseUrl= apiBaseUrl;
|
||||||
|
sLogging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||||
|
sClientBuilder = new OkHttpClient.Builder().addInterceptor(sLogging);
|
||||||
|
sBuilder = new Retrofit.Builder().baseUrl(sApiBaseUrl).addConverterFactory(GsonConverterFactory.create());
|
||||||
|
sServices = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param klass
|
||||||
|
* @param thing
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
private static <T> void setService(Class<T> klass, T thing) {
|
||||||
|
sServices.put(klass, thing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param serviceClass
|
||||||
|
* @param <T>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public <T> T getService(Class<T> serviceClass) {
|
||||||
|
|
||||||
|
T service = serviceClass.cast(sServices.get(serviceClass));
|
||||||
|
if (service == null) {
|
||||||
|
service = createService(serviceClass);
|
||||||
|
setService(serviceClass, service);
|
||||||
|
}
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param serviceClass
|
||||||
|
* @param <S>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static <S> S createService(Class<S> serviceClass) {
|
||||||
|
|
||||||
|
sClientBuilder.interceptors().add(new Interceptor() {
|
||||||
|
@Override
|
||||||
|
public okhttp3.Response intercept(Chain chain) throws IOException {
|
||||||
|
okhttp3.Request original = chain.request();
|
||||||
|
okhttp3.Request.Builder requestBuilder = original.newBuilder().method(original.method(), original.body());
|
||||||
|
|
||||||
|
okhttp3.Request request = requestBuilder.build();
|
||||||
|
return chain.proceed(request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sClientBuilder.readTimeout(5, TimeUnit.MINUTES);
|
||||||
|
sClientBuilder.connectTimeout(5, TimeUnit.MINUTES);
|
||||||
|
OkHttpClient client = sClientBuilder.build();
|
||||||
|
Retrofit retrofit = sBuilder.client(client).build();
|
||||||
|
return retrofit.create(serviceClass);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static InsightApiService Create() {
|
||||||
|
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
|
||||||
|
httpClient.interceptors().add(new Interceptor() {
|
||||||
|
@Override
|
||||||
|
public okhttp3.Response intercept(Chain chain) throws IOException {
|
||||||
|
okhttp3.Request original = chain.request();
|
||||||
|
|
||||||
|
// Customize the request
|
||||||
|
okhttp3.Request request = original.newBuilder().method(original.method(), original.body()).build();
|
||||||
|
|
||||||
|
return chain.proceed(request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
OkHttpClient client = httpClient.build();
|
||||||
|
Retrofit retrofit = new Retrofit.Builder().baseUrl(sApiBaseUrl).client(client).build();
|
||||||
|
return retrofit.create(InsightApiService.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the address txi of a insishgt api response
|
||||||
|
*/
|
||||||
|
public class AddressTxi {
|
||||||
|
/**
|
||||||
|
* The total number of items
|
||||||
|
*/
|
||||||
|
public int totalItems;
|
||||||
|
/**
|
||||||
|
* The start index of the current txi
|
||||||
|
*/
|
||||||
|
public int from;
|
||||||
|
/**
|
||||||
|
* the last index of the current txi
|
||||||
|
*/
|
||||||
|
public int to;
|
||||||
|
/**
|
||||||
|
* The arrays of txi of this response
|
||||||
|
*/
|
||||||
|
public Txi[] items;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The transasction Script public keym is used to validate
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ScriptPubKey {
|
||||||
|
/**
|
||||||
|
* The code to validate in hex
|
||||||
|
*/
|
||||||
|
public String hex;
|
||||||
|
/**
|
||||||
|
* the code to validate this transaction
|
||||||
|
*/
|
||||||
|
public String asm;
|
||||||
|
/**
|
||||||
|
* the acoin address involved
|
||||||
|
*/
|
||||||
|
public String[] addresses;
|
||||||
|
/**
|
||||||
|
* The type of the hash
|
||||||
|
*/
|
||||||
|
public String type;
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reprensents the Script signature of an trnasaction Input
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class ScriptSig {
|
||||||
|
/**
|
||||||
|
* The hex
|
||||||
|
*/
|
||||||
|
public String hex;
|
||||||
|
/**
|
||||||
|
* the asm
|
||||||
|
*/
|
||||||
|
public String asm;
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents one transaction input of the insight API
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Txi {
|
||||||
|
/**
|
||||||
|
* The id of this transaction
|
||||||
|
*/
|
||||||
|
public String txid;
|
||||||
|
/**
|
||||||
|
* the version number of this transaction
|
||||||
|
*/
|
||||||
|
public int version;
|
||||||
|
/**
|
||||||
|
* Time to hold this transaction
|
||||||
|
*/
|
||||||
|
public long locktime;
|
||||||
|
/**
|
||||||
|
* The array of the transaction inputs
|
||||||
|
*/
|
||||||
|
public Vin[] vin;
|
||||||
|
/**
|
||||||
|
* the array of the transactions outputs
|
||||||
|
*/
|
||||||
|
public Vout[] vout;
|
||||||
|
/**
|
||||||
|
* this block hash
|
||||||
|
*/
|
||||||
|
public String blockhash;
|
||||||
|
/**
|
||||||
|
* The blockheight where this transaction belongs, if 0 this transactions hasn't be included in any block yet
|
||||||
|
*/
|
||||||
|
public int blockheight;
|
||||||
|
/**
|
||||||
|
* Number of confirmations
|
||||||
|
*/
|
||||||
|
public int confirmations;
|
||||||
|
/**
|
||||||
|
* The time of the first broadcast fo this transaction
|
||||||
|
*/
|
||||||
|
public long time;
|
||||||
|
/**
|
||||||
|
* The time which this transaction was included
|
||||||
|
*/
|
||||||
|
public long blocktime;
|
||||||
|
/**
|
||||||
|
* Total value to transactions outputs
|
||||||
|
*/
|
||||||
|
public double valueOut;
|
||||||
|
/**
|
||||||
|
* The size in bytes
|
||||||
|
*/
|
||||||
|
public int size;
|
||||||
|
/**
|
||||||
|
* Total value of transactions inputs
|
||||||
|
*/
|
||||||
|
public double valueIn;
|
||||||
|
/**
|
||||||
|
* Fee of this transaction has to be valueIn - valueOut
|
||||||
|
*/
|
||||||
|
public double fee;
|
||||||
|
/**
|
||||||
|
* This is only for dash, is the instantsend state
|
||||||
|
*/
|
||||||
|
public boolean txlock=false;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a transaction input
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Vin {
|
||||||
|
/**
|
||||||
|
* The original transaction id where this transaction is an output
|
||||||
|
*/
|
||||||
|
public String txid;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public int vout;
|
||||||
|
/**
|
||||||
|
* Sequence fo the transaction
|
||||||
|
*/
|
||||||
|
public long sequence;
|
||||||
|
/**
|
||||||
|
* Order of the transasction input on the transasction
|
||||||
|
*/
|
||||||
|
public int n;
|
||||||
|
/**
|
||||||
|
* The script signature
|
||||||
|
*/
|
||||||
|
public ScriptSig scriptSig;
|
||||||
|
/**
|
||||||
|
* The addr of this transaction
|
||||||
|
*/
|
||||||
|
public String addr;
|
||||||
|
/**
|
||||||
|
* Value in satoshi
|
||||||
|
*/
|
||||||
|
public long valueSat;
|
||||||
|
/**
|
||||||
|
* Calue of this transaction
|
||||||
|
*/
|
||||||
|
public double value;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public String doubleSpentTxID;
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package cy.agorise.crystalwallet.apigenerator.insightapi.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a Transasction output
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class Vout {
|
||||||
|
/**
|
||||||
|
* The amount of coin
|
||||||
|
*/
|
||||||
|
public double value;
|
||||||
|
/**
|
||||||
|
* the order of this transaciton output on the transaction
|
||||||
|
*/
|
||||||
|
public int n;
|
||||||
|
/**
|
||||||
|
* The script public key
|
||||||
|
*/
|
||||||
|
public ScriptPubKey scriptPubKey;
|
||||||
|
/**
|
||||||
|
* If this transaciton output was spent what txid it belongs
|
||||||
|
*/
|
||||||
|
public String spentTxId;
|
||||||
|
/**
|
||||||
|
* The index on the transaction that this transaction was spent
|
||||||
|
*/
|
||||||
|
public String spentIndex;
|
||||||
|
/**
|
||||||
|
* The block height of the transaction this output was spent
|
||||||
|
*/
|
||||||
|
public String spentHeight;
|
||||||
|
}
|
|
@ -93,7 +93,7 @@ public class CrystalApplication extends Application {
|
||||||
CryptoNetEvents.getInstance().addListener(crystalWalletNotifier);
|
CryptoNetEvents.getInstance().addListener(crystalWalletNotifier);
|
||||||
|
|
||||||
//Next line is for use the bitshares main net
|
//Next line is for use the bitshares main net
|
||||||
//CryptoNetManager.addCryptoNetURL(CryptoNet.BITSHARES,BITSHARES_URL);
|
CryptoNetManager.addCryptoNetURL(CryptoNet.BITSHARES,BITSHARES_URL);
|
||||||
|
|
||||||
GeneralSetting generalSettingPreferredLanguage = db.generalSettingDao().getSettingByName(GeneralSetting.SETTING_NAME_PREFERRED_LANGUAGE);
|
GeneralSetting generalSettingPreferredLanguage = db.generalSettingDao().getSettingByName(GeneralSetting.SETTING_NAME_PREFERRED_LANGUAGE);
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import java.util.List;
|
||||||
|
|
||||||
import cy.agorise.crystalwallet.activities.PatternRequestActivity;
|
import cy.agorise.crystalwallet.activities.PatternRequestActivity;
|
||||||
import cy.agorise.crystalwallet.activities.PinRequestActivity;
|
import cy.agorise.crystalwallet.activities.PinRequestActivity;
|
||||||
|
import cy.agorise.crystalwallet.activities.PocketRequestActivity;
|
||||||
import cy.agorise.crystalwallet.models.GeneralSetting;
|
import cy.agorise.crystalwallet.models.GeneralSetting;
|
||||||
import cy.agorise.crystalwallet.notifiers.CrystalWalletNotifier;
|
import cy.agorise.crystalwallet.notifiers.CrystalWalletNotifier;
|
||||||
import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel;
|
import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel;
|
||||||
|
@ -27,6 +28,7 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
|
||||||
private int numStarted = 0;
|
private int numStarted = 0;
|
||||||
private String passwordEncrypted;
|
private String passwordEncrypted;
|
||||||
private String patternEncrypted;
|
private String patternEncrypted;
|
||||||
|
private String yubikeyOathTotpPasswordEncrypted;
|
||||||
|
|
||||||
private static CrystalSecurityMonitor instance;
|
private static CrystalSecurityMonitor instance;
|
||||||
private GeneralSettingListViewModel generalSettingListViewModel;
|
private GeneralSettingListViewModel generalSettingListViewModel;
|
||||||
|
@ -38,8 +40,10 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
|
||||||
|
|
||||||
this.passwordEncrypted = "";
|
this.passwordEncrypted = "";
|
||||||
this.patternEncrypted = "";
|
this.patternEncrypted = "";
|
||||||
|
this.yubikeyOathTotpPasswordEncrypted = "";
|
||||||
GeneralSetting passwordGeneralSetting = generalSettingListViewModel.getGeneralSettingByName(GeneralSetting.SETTING_PASSWORD);;
|
GeneralSetting passwordGeneralSetting = generalSettingListViewModel.getGeneralSettingByName(GeneralSetting.SETTING_PASSWORD);;
|
||||||
GeneralSetting patternGeneralSetting = generalSettingListViewModel.getGeneralSettingByName(GeneralSetting.SETTING_PATTERN);;
|
GeneralSetting patternGeneralSetting = generalSettingListViewModel.getGeneralSettingByName(GeneralSetting.SETTING_PATTERN);;
|
||||||
|
GeneralSetting yubikeyOathTotpPasswordSetting = generalSettingListViewModel.getGeneralSettingByName(GeneralSetting.SETTING_YUBIKEY_OATH_TOTP_PASSWORD);;
|
||||||
|
|
||||||
if (passwordGeneralSetting != null){
|
if (passwordGeneralSetting != null){
|
||||||
this.passwordEncrypted = passwordGeneralSetting.getValue();
|
this.passwordEncrypted = passwordGeneralSetting.getValue();
|
||||||
|
@ -47,6 +51,9 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
|
||||||
if (patternGeneralSetting != null){
|
if (patternGeneralSetting != null){
|
||||||
this.patternEncrypted = patternGeneralSetting.getValue();
|
this.patternEncrypted = patternGeneralSetting.getValue();
|
||||||
}
|
}
|
||||||
|
if (yubikeyOathTotpPasswordSetting != null){
|
||||||
|
this.yubikeyOathTotpPasswordEncrypted = yubikeyOathTotpPasswordSetting.getValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CrystalSecurityMonitor getInstance(final FragmentActivity fragmentActivity){
|
public static CrystalSecurityMonitor getInstance(final FragmentActivity fragmentActivity){
|
||||||
|
@ -57,6 +64,10 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getServiceName(){
|
||||||
|
return "cy.agorise.crystalwallet";
|
||||||
|
}
|
||||||
|
|
||||||
public void clearSecurity(){
|
public void clearSecurity(){
|
||||||
this.patternEncrypted = "";
|
this.patternEncrypted = "";
|
||||||
this.passwordEncrypted = "";
|
this.passwordEncrypted = "";
|
||||||
|
@ -65,6 +76,12 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
|
||||||
generalSettingListViewModel.deleteGeneralSettingByName(GeneralSetting.SETTING_PATTERN);
|
generalSettingListViewModel.deleteGeneralSettingByName(GeneralSetting.SETTING_PATTERN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearSecurity2ndFactor(){
|
||||||
|
this.yubikeyOathTotpPasswordEncrypted = "";
|
||||||
|
|
||||||
|
generalSettingListViewModel.deleteGeneralSettingByName(GeneralSetting.SETTING_YUBIKEY_OATH_TOTP_PASSWORD);
|
||||||
|
}
|
||||||
|
|
||||||
public void setPasswordSecurity(String password){
|
public void setPasswordSecurity(String password){
|
||||||
clearSecurity();
|
clearSecurity();
|
||||||
this.passwordEncrypted = password;
|
this.passwordEncrypted = password;
|
||||||
|
@ -95,6 +112,19 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean is2ndFactorSet(){
|
||||||
|
return !this.yubikeyOathTotpPasswordEncrypted.equals("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setYubikeyOathTotpSecurity(String name, String password){
|
||||||
|
this.yubikeyOathTotpPasswordEncrypted = password;
|
||||||
|
GeneralSetting yubikeyOathTotpSetting = new GeneralSetting();
|
||||||
|
yubikeyOathTotpSetting.setName(GeneralSetting.SETTING_YUBIKEY_OATH_TOTP_PASSWORD);
|
||||||
|
yubikeyOathTotpSetting.setValue(password);
|
||||||
|
|
||||||
|
generalSettingListViewModel.saveGeneralSetting(yubikeyOathTotpSetting);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityStarted(Activity activity) {
|
public void onActivityStarted(Activity activity) {
|
||||||
if (numStarted == 0) {
|
if (numStarted == 0) {
|
||||||
|
@ -130,6 +160,19 @@ public class CrystalSecurityMonitor implements Application.ActivityLifecycleCall
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void call2ndFactor(Activity activity){
|
||||||
|
Intent intent = null;
|
||||||
|
if ((this.yubikeyOathTotpPasswordEncrypted != null) && (!this.yubikeyOathTotpPasswordEncrypted.equals(""))) {
|
||||||
|
intent = new Intent(activity, PocketRequestActivity.class);
|
||||||
|
//intent.putExtra("ACTIVITY_TYPE", "PASSWORD_REQUEST");
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String get2ndFactorValue(){
|
||||||
|
return this.yubikeyOathTotpPasswordEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Activity activity, Bundle bundle) {
|
public void onActivityCreated(Activity activity, Bundle bundle) {
|
||||||
//
|
//
|
||||||
|
|
|
@ -30,7 +30,9 @@ public abstract class BitsharesConstant {
|
||||||
"http://185.208.208.147:11012", // Openledger node
|
"http://185.208.208.147:11012", // Openledger node
|
||||||
};
|
};
|
||||||
|
|
||||||
public final static String FAUCET_URL = "http://185.208.208.147:5010";
|
//testnet faucet
|
||||||
|
//public final static String FAUCET_URL = "http://185.208.208.147:5010";
|
||||||
|
public final static String FAUCET_URL = "https://de.palmpay.io";
|
||||||
public final static String EQUIVALENT_URL = "wss://bitshares.openledger.info/ws";
|
public final static String EQUIVALENT_URL = "wss://bitshares.openledger.info/ws";
|
||||||
|
|
||||||
public final static BitsharesAsset[] SMARTCOINS = new BitsharesAsset[]{
|
public final static BitsharesAsset[] SMARTCOINS = new BitsharesAsset[]{
|
||||||
|
|
|
@ -1,20 +1,42 @@
|
||||||
package cy.agorise.crystalwallet.fragments;
|
package cy.agorise.crystalwallet.fragments;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.nfc.NfcAdapter;
|
||||||
|
import android.nfc.Tag;
|
||||||
|
import android.nfc.tech.IsoDep;
|
||||||
|
import android.nfc.tech.MifareClassic;
|
||||||
|
import android.nfc.tech.NdefFormatable;
|
||||||
|
import android.nfc.tech.NfcA;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.design.widget.TabLayout;
|
import android.support.design.widget.TabLayout;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.FragmentPagerAdapter;
|
import android.support.v4.app.FragmentPagerAdapter;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.Switch;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base32;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnCheckedChanged;
|
||||||
import cy.agorise.crystalwallet.R;
|
import cy.agorise.crystalwallet.R;
|
||||||
import cy.agorise.crystalwallet.application.CrystalSecurityMonitor;
|
import cy.agorise.crystalwallet.application.CrystalSecurityMonitor;
|
||||||
import cy.agorise.crystalwallet.models.GeneralSetting;
|
import cy.agorise.crystalwallet.models.GeneralSetting;
|
||||||
import cy.agorise.crystalwallet.util.ChildViewPager;
|
import cy.agorise.crystalwallet.util.ChildViewPager;
|
||||||
|
import cy.agorise.crystalwallet.util.yubikey.Algorithm;
|
||||||
|
import cy.agorise.crystalwallet.util.yubikey.OathType;
|
||||||
|
import cy.agorise.crystalwallet.util.yubikey.YkOathApi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by xd on 1/17/18.
|
* Created by xd on 1/17/18.
|
||||||
|
@ -34,11 +56,23 @@ public class SecuritySettingsFragment extends Fragment {
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private NfcAdapter mNfcAdapter;
|
||||||
|
private PendingIntent pendingIntent;
|
||||||
|
private IntentFilter ndef;
|
||||||
|
IntentFilter[] intentFiltersArray;
|
||||||
|
String[][] techList;
|
||||||
|
|
||||||
|
|
||||||
@BindView(R.id.pager)
|
@BindView(R.id.pager)
|
||||||
public ChildViewPager mPager;
|
public ChildViewPager mPager;
|
||||||
|
|
||||||
public SecurityPagerAdapter securityPagerAdapter;
|
public SecurityPagerAdapter securityPagerAdapter;
|
||||||
|
|
||||||
|
@BindView(R.id.sPocketSecurity)
|
||||||
|
Switch sPocketSecurity;
|
||||||
|
@BindView(R.id.etPocketPassword)
|
||||||
|
EditText etPocketPassword;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
|
@ -66,6 +100,9 @@ public class SecuritySettingsFragment extends Fragment {
|
||||||
mPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
|
mPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
|
||||||
tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mPager));
|
tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mPager));
|
||||||
|
|
||||||
|
mNfcAdapter = NfcAdapter.getDefaultAdapter(this.getActivity());
|
||||||
|
this.configureForegroundDispatch();
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,4 +138,87 @@ public class SecuritySettingsFragment extends Fragment {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnCheckedChanged(R.id.sPocketSecurity)
|
||||||
|
public void onPocketSecurityActivated(CompoundButton button, boolean checked){
|
||||||
|
if (checked) {
|
||||||
|
enableNfc();
|
||||||
|
} else {
|
||||||
|
disableNfc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void configureForegroundDispatch(){
|
||||||
|
if (mNfcAdapter != null) {
|
||||||
|
pendingIntent = PendingIntent.getActivity(
|
||||||
|
this.getActivity(), 0, new Intent(this.getActivity(), getActivity().getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
|
||||||
|
|
||||||
|
ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
|
||||||
|
try {
|
||||||
|
ndef.addDataType("*/*"); /* Handles all MIME based dispatches.
|
||||||
|
You should specify only the ones that you need. */
|
||||||
|
} catch (IntentFilter.MalformedMimeTypeException e) {
|
||||||
|
throw new RuntimeException("fail", e);
|
||||||
|
}
|
||||||
|
intentFiltersArray = new IntentFilter[]{ndef,};
|
||||||
|
techList = new String[][]{ new String[] {IsoDep.class.getName(), NfcA.class.getName(), MifareClassic.class.getName(), NdefFormatable.class.getName()} };
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this.getContext(), "This device doesn't support NFC.", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableNfc(){
|
||||||
|
mNfcAdapter.enableForegroundDispatch(this.getActivity(), pendingIntent, intentFiltersArray, techList);
|
||||||
|
Toast.makeText(this.getContext(), "Tap with your yubikey", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableNfc(){
|
||||||
|
mNfcAdapter.disableForegroundDispatch(this.getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
disableNfc();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (mNfcAdapter != null) {
|
||||||
|
enableNfc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNewIntent(Intent intent) {
|
||||||
|
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
||||||
|
IsoDep tagIsoDep = IsoDep.get(tagFromIntent);
|
||||||
|
Log.i("Tag from nfc","New Intent");
|
||||||
|
try {
|
||||||
|
|
||||||
|
String serviceName = "cy.agorise.crystalwallet";
|
||||||
|
String encodedSecret = etPocketPassword.getText().toString();
|
||||||
|
Base32 decoder = new Base32();
|
||||||
|
|
||||||
|
if ((encodedSecret != null) && (!encodedSecret.equals("")) && decoder.isInAlphabet(encodedSecret)) {
|
||||||
|
byte[] secret = decoder.decode(encodedSecret);
|
||||||
|
tagIsoDep.connect();
|
||||||
|
tagIsoDep.setTimeout(15000);
|
||||||
|
YkOathApi ykOathApi = new YkOathApi(tagIsoDep);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ykOathApi.putCode(serviceName, secret, OathType.TOTP, Algorithm.SHA256, (byte) 6, 0, false);
|
||||||
|
CrystalSecurityMonitor.getInstance(null).setYubikeyOathTotpSecurity(CrystalSecurityMonitor.getServiceName(),encodedSecret);
|
||||||
|
} catch(IOException e) {
|
||||||
|
Toast.makeText(this.getContext(), "There's no space for new credentials!", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
tagIsoDep.close();
|
||||||
|
Toast.makeText(this.getContext(), "Credential saved!", Toast.LENGTH_LONG).show();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this.getContext(), "Invalid password for credential", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
166
app/src/main/java/cy/agorise/crystalwallet/models/GTxIO.java
Normal file
166
app/src/main/java/cy/agorise/crystalwallet/models/GTxIO.java
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package cy.agorise.crystalwallet.models;
|
||||||
|
|
||||||
|
import cy.agorise.crystalwallet.enums.CryptoCoin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General Coin Transaction Input/Output
|
||||||
|
*
|
||||||
|
* This class represent each Input or Output Transaction of a General Coin Transaction
|
||||||
|
*
|
||||||
|
* Created by henry on 06/02/2017.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class GTxIO {
|
||||||
|
/**
|
||||||
|
* The id on the database
|
||||||
|
*/
|
||||||
|
private long mId = -1;
|
||||||
|
/**
|
||||||
|
* The Coin type of this transaction
|
||||||
|
*/
|
||||||
|
private CryptoCoin mType;
|
||||||
|
/**
|
||||||
|
* The index on the transaction Input/Output
|
||||||
|
*/
|
||||||
|
private int mIndex;
|
||||||
|
/**
|
||||||
|
* The address that this transaction Input/Output belongs
|
||||||
|
*/
|
||||||
|
private GeneralCoinAddress mAddress;
|
||||||
|
/**
|
||||||
|
* The transaction that this Input/Output belongs
|
||||||
|
*/
|
||||||
|
private GeneralTransaction mTransaction;
|
||||||
|
/**
|
||||||
|
* The amount
|
||||||
|
*/
|
||||||
|
private long mAmount;
|
||||||
|
/**
|
||||||
|
* If this transaction is output or input
|
||||||
|
*/
|
||||||
|
private boolean mIsOut;
|
||||||
|
/**
|
||||||
|
* The address of this transaction as String
|
||||||
|
*/
|
||||||
|
private String mAddressString;
|
||||||
|
/**
|
||||||
|
* The Script as Hex
|
||||||
|
*/
|
||||||
|
private String mScriptHex;
|
||||||
|
/**
|
||||||
|
* If this is a transaction output, the original transaction where this is input
|
||||||
|
*/
|
||||||
|
private String mOriginalTxId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty Constructor
|
||||||
|
*/
|
||||||
|
public GTxIO() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General Constructor, used by the DB.
|
||||||
|
*
|
||||||
|
* @param id The id in the dataabase
|
||||||
|
* @param type The coin mType
|
||||||
|
* @param address The addres fo an account on the wallet, or null if the address is external
|
||||||
|
* @param transaction The transaction where this belongs
|
||||||
|
* @param amount The amount with the lowest precision
|
||||||
|
* @param isOut if this is an output
|
||||||
|
* @param addressString The string of the General Coin address, this can't be null
|
||||||
|
* @param index The index on the transaction
|
||||||
|
* @param scriptHex The script in hex String
|
||||||
|
*/
|
||||||
|
public GTxIO(long id, CryptoCoin type, GeneralCoinAddress address, GeneralTransaction transaction, long amount, boolean isOut, String addressString, int index, String scriptHex) {
|
||||||
|
this.mId = id;
|
||||||
|
this.mType = type;
|
||||||
|
this.mAddress = address;
|
||||||
|
this.mTransaction = transaction;
|
||||||
|
this.mAmount = amount;
|
||||||
|
this.mIsOut = isOut;
|
||||||
|
this.mAddressString = addressString;
|
||||||
|
this.mIndex = index;
|
||||||
|
this.mScriptHex = scriptHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return mId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(long id) {
|
||||||
|
this.mId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CryptoCoin getType() {
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(CryptoCoin type) {
|
||||||
|
this.mType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndex() {
|
||||||
|
return mIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndex(int index) {
|
||||||
|
this.mIndex = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeneralCoinAddress getAddress() {
|
||||||
|
return mAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(GeneralCoinAddress address) {
|
||||||
|
this.mAddress = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeneralTransaction getTransaction() {
|
||||||
|
return mTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTransaction(GeneralTransaction transaction) {
|
||||||
|
this.mTransaction = transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getAmount() {
|
||||||
|
return mAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAmount(long amount) {
|
||||||
|
this.mAmount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOut() {
|
||||||
|
return mIsOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOut(boolean out) {
|
||||||
|
mIsOut = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddressString() {
|
||||||
|
return mAddressString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddressString(String addressString) {
|
||||||
|
this.mAddressString = addressString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScriptHex() {
|
||||||
|
return mScriptHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScriptHex(String scriptHex) {
|
||||||
|
this.mScriptHex = scriptHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOriginalTxid() {
|
||||||
|
return mOriginalTxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOriginalTxid(String originalTxid) {
|
||||||
|
this.mOriginalTxId = originalTxid;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,392 @@
|
||||||
|
package cy.agorise.crystalwallet.models;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
|
import org.bitcoinj.crypto.ChildNumber;
|
||||||
|
import org.bitcoinj.crypto.DeterministicKey;
|
||||||
|
import org.bitcoinj.crypto.HDKeyDerivation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import cy.agorise.crystalwallet.enums.CryptoCoin;
|
||||||
|
import cy.agorise.crystalwallet.enums.CryptoNet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by henry on 2/8/2018.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public abstract class GeneralCoinAccount extends CryptoNetAccount {
|
||||||
|
/**
|
||||||
|
* The account number of the BIP-44
|
||||||
|
*/
|
||||||
|
protected int mAccountNumber;
|
||||||
|
/**
|
||||||
|
* The index of the last used external address
|
||||||
|
*/
|
||||||
|
protected int mLastExternalIndex;
|
||||||
|
/**
|
||||||
|
* The indes of the last used change address
|
||||||
|
*/
|
||||||
|
protected int mLastChangeIndex;
|
||||||
|
/**
|
||||||
|
* The account key, this is calculated as a cache
|
||||||
|
*/
|
||||||
|
protected DeterministicKey mAccountKey;
|
||||||
|
/**
|
||||||
|
* With this key we can calculate the external addresses
|
||||||
|
*/
|
||||||
|
protected DeterministicKey mExternalKey;
|
||||||
|
/**
|
||||||
|
* With this key we can calculate the change address
|
||||||
|
*/
|
||||||
|
protected DeterministicKey mChangeKey;
|
||||||
|
/**
|
||||||
|
* The keys for externals addresses
|
||||||
|
*/
|
||||||
|
protected HashMap<Integer, GeneralCoinAddress> mExternalKeys = new HashMap<Integer,GeneralCoinAddress>();
|
||||||
|
/**
|
||||||
|
* The keys for the change addresses
|
||||||
|
*/
|
||||||
|
protected HashMap<Integer, GeneralCoinAddress> mChangeKeys = new HashMap<Integer,GeneralCoinAddress>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of transaction that involves this account
|
||||||
|
*/
|
||||||
|
protected List<GeneralTransaction> mTransactions = new ArrayList<GeneralTransaction>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Limit gap define in the BIP-44
|
||||||
|
*/
|
||||||
|
private final static int sAddressGap = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is the coin number defined by the SLIP-44
|
||||||
|
*/
|
||||||
|
private final int mCoinNumber;
|
||||||
|
|
||||||
|
public GeneralCoinAccount(long mId, AccountSeed seed, int mAccountIndex, CryptoNet mCryptoNet, int mAccountNumber, int mLastExternalIndex, int mLastChangeIndex, int mCoinNumber) {
|
||||||
|
super(mId, seed.getId(), mAccountIndex, mCryptoNet);
|
||||||
|
this.mAccountNumber = mAccountNumber;
|
||||||
|
this.mLastExternalIndex = mLastExternalIndex;
|
||||||
|
this.mLastChangeIndex = mLastChangeIndex;
|
||||||
|
this.mCoinNumber = mCoinNumber;
|
||||||
|
calculateAddresses((DeterministicKey) seed.getPrivateKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for the transactions of this account, this is used from the database
|
||||||
|
*/
|
||||||
|
public void setTransactions(List<GeneralTransaction> transactions) {
|
||||||
|
this.mTransactions = transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates each basic key, not the addresses keys using the BIP-44
|
||||||
|
*/
|
||||||
|
private void calculateAddresses(DeterministicKey masterKey) {
|
||||||
|
DeterministicKey purposeKey = HDKeyDerivation.deriveChildKey(masterKey,
|
||||||
|
new ChildNumber(44, true));
|
||||||
|
DeterministicKey coinKey = HDKeyDerivation.deriveChildKey(purposeKey,
|
||||||
|
new ChildNumber(this.mCoinNumber, true));
|
||||||
|
this.mAccountKey = HDKeyDerivation.deriveChildKey(coinKey,
|
||||||
|
new ChildNumber(this.mAccountNumber, true));
|
||||||
|
this.mExternalKey = HDKeyDerivation.deriveChildKey(this.mAccountKey,
|
||||||
|
new ChildNumber(0, false));
|
||||||
|
this.mChangeKey = HDKeyDerivation.deriveChildKey(this.mAccountKey,
|
||||||
|
new ChildNumber(1, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the external address keys until the index + gap
|
||||||
|
*/
|
||||||
|
public void calculateGapExternal() {
|
||||||
|
for (int i = 0; i < this.mLastExternalIndex + this.sAddressGap; i++) {
|
||||||
|
if (!this.mExternalKeys.containsKey(i)) {
|
||||||
|
this.mExternalKeys.put(i, new GeneralCoinAddress(this, false, i,
|
||||||
|
HDKeyDerivation.deriveChildKey(this.mExternalKey,
|
||||||
|
new ChildNumber(i, false))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the change address keys until the index + gap
|
||||||
|
*/
|
||||||
|
public void calculateGapChange() {
|
||||||
|
for (int i = 0; i < this.mLastChangeIndex + this.sAddressGap; i++) {
|
||||||
|
if (!this.mChangeKeys.containsKey(i)) {
|
||||||
|
this.mChangeKeys.put(i, new GeneralCoinAddress(this, true, i,
|
||||||
|
HDKeyDerivation.deriveChildKey(this.mChangeKey,
|
||||||
|
new ChildNumber(i, false))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO check init address
|
||||||
|
/*public List<GeneralCoinAddress> getAddresses(SCWallDatabase db) {
|
||||||
|
//TODO check for used address
|
||||||
|
this.getNextReceiveAddress();
|
||||||
|
this.getNextChangeAddress();
|
||||||
|
this.calculateGapExternal();
|
||||||
|
this.calculateGapChange();
|
||||||
|
|
||||||
|
List<GeneralCoinAddress> addresses = new ArrayList();
|
||||||
|
addresses.addAll(this.mChangeKeys.values());
|
||||||
|
addresses.addAll(this.mExternalKeys.values());
|
||||||
|
this.saveAddresses(db);
|
||||||
|
return addresses;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of all the address, external and change addresses
|
||||||
|
* @return a list with all the addresses of this account
|
||||||
|
*/
|
||||||
|
public List<GeneralCoinAddress> getAddresses() {
|
||||||
|
List<GeneralCoinAddress> addresses = new ArrayList();
|
||||||
|
addresses.addAll(this.mChangeKeys.values());
|
||||||
|
addresses.addAll(this.mExternalKeys.values());
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Charges the list of addresse of this account, this is used from the database
|
||||||
|
*/
|
||||||
|
public void loadAddresses(List<GeneralCoinAddress> addresses) {
|
||||||
|
for (GeneralCoinAddress address : addresses) {
|
||||||
|
if (address.isIsChange()) {
|
||||||
|
this.mChangeKeys.put(address.getIndex(), address);
|
||||||
|
} else {
|
||||||
|
this.mExternalKeys.put(address.getIndex(), address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO save address
|
||||||
|
/*public void saveAddresses(SCWallDatabase db) {
|
||||||
|
for (GeneralCoinAddress externalAddress : this.mExternalKeys.values()) {
|
||||||
|
if (externalAddress.getId() == -1) {
|
||||||
|
long id = db.putGeneralCoinAddress(externalAddress);
|
||||||
|
if(id != -1)
|
||||||
|
externalAddress.setId(id);
|
||||||
|
} else {
|
||||||
|
db.updateGeneralCoinAddress(externalAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (GeneralCoinAddress changeAddress : this.mChangeKeys.values()) {
|
||||||
|
if (changeAddress.getId() == -1) {
|
||||||
|
Log.i("SCW","change address id " + changeAddress.getId());
|
||||||
|
long id = db.putGeneralCoinAddress(changeAddress);
|
||||||
|
if(id != -1)
|
||||||
|
changeAddress.setId(id);
|
||||||
|
} else {
|
||||||
|
db.updateGeneralCoinAddress(changeAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
db.updateGeneralCoinAccount(this);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of the account number
|
||||||
|
*/
|
||||||
|
public int getAccountNumber() {
|
||||||
|
return this.mAccountNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of the last external address used index
|
||||||
|
*/
|
||||||
|
public int getLastExternalIndex() {
|
||||||
|
return this.mLastExternalIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of the last change address used index
|
||||||
|
*/
|
||||||
|
public int getLastChangeIndex() {
|
||||||
|
return this.mLastChangeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of the next receive address
|
||||||
|
* @return The next unused receive address to be used
|
||||||
|
*/
|
||||||
|
public abstract String getNextReceiveAddress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of the next change address
|
||||||
|
* @return The next unused change address to be used
|
||||||
|
*/
|
||||||
|
public abstract String getNextChangeAddress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transfer coin amount to another address
|
||||||
|
*
|
||||||
|
* @param toAddress The destination address
|
||||||
|
* @param coin the coin
|
||||||
|
* @param amount the amount to send in satoshi
|
||||||
|
* @param memo the memo, this can be empty
|
||||||
|
* @param context the android context
|
||||||
|
*/
|
||||||
|
public abstract void send(String toAddress, CryptoCoin coin, long amount, String memo,
|
||||||
|
Context context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform this account into json object to be saved in the bin file, or any other file
|
||||||
|
*/
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject answer = new JsonObject();
|
||||||
|
answer.addProperty("type", this.getCryptoNet().name());
|
||||||
|
answer.addProperty("name", this.getName());
|
||||||
|
answer.addProperty("accountNumber", this.mAccountNumber);
|
||||||
|
answer.addProperty("changeIndex", this.mLastChangeIndex);
|
||||||
|
answer.addProperty("externalIndex", this.mLastExternalIndex);
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of the list of transactions
|
||||||
|
*/
|
||||||
|
public List<GeneralTransaction> getTransactions() {
|
||||||
|
List<GeneralTransaction> transactions = new ArrayList();
|
||||||
|
for (GeneralCoinAddress address : this.mExternalKeys.values()) {
|
||||||
|
for (GTxIO giotx : address.getTransactionInput()) {
|
||||||
|
if (!transactions.contains(giotx.getTransaction())) {
|
||||||
|
transactions.add(giotx.getTransaction());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (GTxIO giotx : address.getTransactionOutput()) {
|
||||||
|
if (!transactions.contains(giotx.getTransaction())) {
|
||||||
|
transactions.add(giotx.getTransaction());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (GeneralCoinAddress address : this.mChangeKeys.values()) {
|
||||||
|
for (GTxIO giotx : address.getTransactionInput()) {
|
||||||
|
if (!transactions.contains(giotx.getTransaction())) {
|
||||||
|
transactions.add(giotx.getTransaction());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (GTxIO giotx : address.getTransactionOutput()) {
|
||||||
|
if (!transactions.contains(giotx.getTransaction())) {
|
||||||
|
transactions.add(giotx.getTransaction());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(transactions, new TransactionsCustomComparator());
|
||||||
|
return transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CryptoCoin getCryptoCoin(){
|
||||||
|
return CryptoCoin.valueOf(this.getCryptoNet().name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the address as string of an adrees index
|
||||||
|
* @param index The index of the address
|
||||||
|
* @param change if it is change addres or is a external address
|
||||||
|
* @return The Address as string
|
||||||
|
*/
|
||||||
|
public abstract String getAddressString(int index, boolean change);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the GeneralCoinAddress object of an address
|
||||||
|
* @param index the index of the address
|
||||||
|
* @param change if it is change addres or is a external address
|
||||||
|
* @return The GeneralCoinAddress of the address
|
||||||
|
*/
|
||||||
|
public abstract GeneralCoinAddress getAddress(int index, boolean change);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the network parameters, this is used for the bitcoiinj library
|
||||||
|
*/
|
||||||
|
public abstract NetworkParameters getNetworkParam();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers the event onBalanceChange
|
||||||
|
*/
|
||||||
|
public void balanceChange() {
|
||||||
|
this._fireOnChangeBalance(this.getBalance().get(0)); //TODO make it more genertic
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract List<CryptoNetBalance> getBalance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare the transaction, to order it for the list of transaction
|
||||||
|
*/
|
||||||
|
public class TransactionsCustomComparator implements Comparator<GeneralTransaction> {
|
||||||
|
@Override
|
||||||
|
public int compare(GeneralTransaction o1, GeneralTransaction o2) {
|
||||||
|
return o1.getDate().compareTo(o2.getDate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add listener for the onChangebalance Event
|
||||||
|
*/
|
||||||
|
/*public void addChangeBalanceListener(ChangeBalanceListener listener) {
|
||||||
|
this.mChangeBalanceListeners.add(listener);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire the onChangeBalance event
|
||||||
|
*/
|
||||||
|
protected void _fireOnChangeBalance(CryptoNetBalance balance) {
|
||||||
|
/*for (ChangeBalanceListener listener : this.mChangeBalanceListeners) {
|
||||||
|
listener.balanceChange(balance);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
GeneralCoinAccount that = (GeneralCoinAccount) o;
|
||||||
|
|
||||||
|
if (this.getCryptoNet() != that.getCryptoNet()) return false;
|
||||||
|
if (this.getAccountNumber() != that.getAccountNumber()) return false;
|
||||||
|
return this.mAccountKey != null ? this.mAccountKey.equals(that.mAccountKey)
|
||||||
|
: that.mAccountKey == null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = this.getAccountNumber();
|
||||||
|
result = 31 * result + (this.mAccountKey != null ? this.mAccountKey.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a transaction
|
||||||
|
*
|
||||||
|
* @param transaction The transaction to update
|
||||||
|
*/
|
||||||
|
public void updateTransaction(GeneralTransaction transaction){
|
||||||
|
// Checks if it has an external address
|
||||||
|
for (GeneralCoinAddress address : this.mExternalKeys.values()) {
|
||||||
|
if(address.updateTransaction(transaction)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (GeneralCoinAddress address : this.mChangeKeys.values()) {
|
||||||
|
if(address.updateTransaction(transaction)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,371 @@
|
||||||
|
package cy.agorise.crystalwallet.models;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.Address;
|
||||||
|
import org.bitcoinj.core.ECKey;
|
||||||
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
|
import org.bitcoinj.crypto.DeterministicKey;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import cy.agorise.graphenej.Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an Address of a General Coin Account
|
||||||
|
*/
|
||||||
|
public class GeneralCoinAddress {
|
||||||
|
/**
|
||||||
|
* The id on the database
|
||||||
|
*/
|
||||||
|
private long mId = -1;
|
||||||
|
/**
|
||||||
|
* The account that this address belongs
|
||||||
|
*/
|
||||||
|
private final GeneralCoinAccount mAccount;
|
||||||
|
/**
|
||||||
|
* If this is change or external
|
||||||
|
*/
|
||||||
|
private final boolean mIsChange;
|
||||||
|
/**
|
||||||
|
* The index fo this address in the account
|
||||||
|
*/
|
||||||
|
private final int mIndex;
|
||||||
|
/**
|
||||||
|
* The ky used to calculate the address
|
||||||
|
*/
|
||||||
|
private ECKey mKey;
|
||||||
|
/**
|
||||||
|
* The list of the transactions that used this address as input
|
||||||
|
*/
|
||||||
|
private List<GTxIO> mTransactionInput = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* The list of the transactions that used this address as output
|
||||||
|
*/
|
||||||
|
private List<GTxIO> mTransactionOutput = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contrsutcotr used from the database
|
||||||
|
* @param id The id on the database
|
||||||
|
* @param account The account of this address
|
||||||
|
* @param isChange if it is change or external address
|
||||||
|
* @param index the index on the account of this address
|
||||||
|
* @param publicHexKey The public Address String
|
||||||
|
*/
|
||||||
|
public GeneralCoinAddress(long id, GeneralCoinAccount account, boolean isChange, int index, String publicHexKey) {
|
||||||
|
this.mId = id;
|
||||||
|
this.mAccount = account;
|
||||||
|
this.mIsChange = isChange;
|
||||||
|
this.mIndex = index;
|
||||||
|
this.mKey = ECKey.fromPublicOnly(Util.hexToBytes(publicHexKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic constructor
|
||||||
|
* @param account The account of this address
|
||||||
|
* @param isChange if it is change or external address
|
||||||
|
* @param index The index on the account of this address
|
||||||
|
* @param key The key to generate the private and the public key of this address
|
||||||
|
*/
|
||||||
|
public GeneralCoinAddress(GeneralCoinAccount account, boolean isChange, int index, DeterministicKey key) {
|
||||||
|
this.mId = -1;
|
||||||
|
this.mAccount = account;
|
||||||
|
this.mIsChange = isChange;
|
||||||
|
this.mIndex = index;
|
||||||
|
this.mKey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter of the database id
|
||||||
|
*/
|
||||||
|
public long getId() {
|
||||||
|
return mId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter of the database id
|
||||||
|
*/
|
||||||
|
public void setId(long id) {
|
||||||
|
this.mId = id;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Getter for he account
|
||||||
|
*/
|
||||||
|
public GeneralCoinAccount getAccount() {
|
||||||
|
return mAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this addres is change, if not is external
|
||||||
|
*/
|
||||||
|
public boolean isIsChange() {
|
||||||
|
return mIsChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the index on the account of this address
|
||||||
|
*/
|
||||||
|
public int getIndex() {
|
||||||
|
return mIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the key of this address
|
||||||
|
*/
|
||||||
|
public ECKey getKey() {
|
||||||
|
return mKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the key for generate private key, this is used when this address is loaded from the database
|
||||||
|
* and want to be used to send transactions
|
||||||
|
* @param key The key that generates the private and the public key
|
||||||
|
*/
|
||||||
|
public void setKey(DeterministicKey key) {
|
||||||
|
this.mKey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the address as a String
|
||||||
|
* @param param The network param of this address
|
||||||
|
*/
|
||||||
|
public String getAddressString(NetworkParameters param) {
|
||||||
|
return mKey.toAddress(param).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the bitcoinj Address representing this address
|
||||||
|
* @param param The network parameter of this address
|
||||||
|
*/
|
||||||
|
public Address getAddress(NetworkParameters param) {
|
||||||
|
return mKey.toAddress(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of transaction that this address is input
|
||||||
|
*/
|
||||||
|
public List<GTxIO> getTransactionInput() {
|
||||||
|
return mTransactionInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the transactions that this address is input
|
||||||
|
*/
|
||||||
|
public void setTransactionInput(List<GTxIO> transactionInput) {
|
||||||
|
this.mTransactionInput = transactionInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find if this address is input of a transaction
|
||||||
|
* @param inputToFind The GTxIO to find
|
||||||
|
* @param param The network parameter of this address
|
||||||
|
* @return if this address belongs to the transaction
|
||||||
|
*/
|
||||||
|
public boolean hasTransactionInput(GTxIO inputToFind, NetworkParameters param) {
|
||||||
|
for (GTxIO input : mTransactionInput) {
|
||||||
|
if ((input.getTransaction().getTxid().equals(inputToFind.getTransaction().getTxid()))
|
||||||
|
&& (input.getAddress().getAddressString(param).equals(inputToFind.getAddress()
|
||||||
|
.getAddressString(param)))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of transaction that this address is output
|
||||||
|
*/
|
||||||
|
public List<GTxIO> getTransactionOutput() {
|
||||||
|
return mTransactionOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find if this address is output of a transaction
|
||||||
|
* @param outputToFind The GTxIO to find
|
||||||
|
* @param param the network parameter of this address
|
||||||
|
* @return if this address belongs to the transaction
|
||||||
|
*/
|
||||||
|
public boolean hasTransactionOutput(GTxIO outputToFind, NetworkParameters param) {
|
||||||
|
for (GTxIO output : mTransactionOutput) {
|
||||||
|
if ((output.getTransaction().getTxid().equals(outputToFind.getTransaction().getTxid()))
|
||||||
|
&& (output.getAddress().getAddressString(param).equals(outputToFind.getAddress()
|
||||||
|
.getAddressString(param)))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the list of transaction that this address is output
|
||||||
|
*/
|
||||||
|
public void setTransactionOutput(List<GTxIO> outputTransaction) {
|
||||||
|
this.mTransactionOutput = outputTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of uncofirmed balance
|
||||||
|
*/
|
||||||
|
public long getUnconfirmedBalance() {
|
||||||
|
long answer = 0;
|
||||||
|
for (GTxIO input : mTransactionInput) {
|
||||||
|
if (input.getTransaction().getConfirm() < mAccount.getCryptoNet().getConfirmationsNeeded()) {
|
||||||
|
answer += input.getAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (GTxIO output : mTransactionOutput) {
|
||||||
|
if (output.getTransaction().getConfirm() < mAccount.getCryptoNet().getConfirmationsNeeded()) {
|
||||||
|
answer -= output.getAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of confirmed balance
|
||||||
|
*/
|
||||||
|
public long getConfirmedBalance() {
|
||||||
|
long answer = 0;
|
||||||
|
for (GTxIO input : mTransactionInput) {
|
||||||
|
if (input.getTransaction().getConfirm() >= mAccount.getCryptoNet().getConfirmationsNeeded()) {
|
||||||
|
answer += input.getAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (GTxIO output : mTransactionOutput) {
|
||||||
|
if (output.getTransaction().getConfirm() >= mAccount.getCryptoNet().getConfirmationsNeeded()) {
|
||||||
|
answer -= output.getAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the date of the last transaction or null if there is no transaction
|
||||||
|
*/
|
||||||
|
public Date getLastDate() {
|
||||||
|
Date lastDate = null;
|
||||||
|
for (GTxIO input : mTransactionInput) {
|
||||||
|
if (lastDate == null || lastDate.before(input.getTransaction().getDate())) {
|
||||||
|
lastDate = input.getTransaction().getDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (GTxIO output : mTransactionOutput) {
|
||||||
|
if (lastDate == null || lastDate.before(output.getTransaction().getDate())) {
|
||||||
|
lastDate = output.getTransaction().getDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of the less cofnirmed transaction, this is used to set how confirmations are
|
||||||
|
* left
|
||||||
|
*/
|
||||||
|
public int getLessConfirmed(){
|
||||||
|
int lessConfirm = -1;
|
||||||
|
for (GTxIO input : mTransactionInput) {
|
||||||
|
if (lessConfirm == -1 || input.getTransaction().getConfirm() < lessConfirm) {
|
||||||
|
lessConfirm = input.getTransaction().getConfirm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (GTxIO output : mTransactionOutput) {
|
||||||
|
if (lessConfirm == -1 || output.getTransaction().getConfirm() < lessConfirm) {
|
||||||
|
lessConfirm = output.getTransaction().getConfirm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lessConfirm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the unspend transactions input
|
||||||
|
* @return The list with the unspend transasctions
|
||||||
|
*/
|
||||||
|
public List<GTxIO> getUTXos(){
|
||||||
|
List<GTxIO> utxo = new ArrayList<>();
|
||||||
|
for(GTxIO gitx : mTransactionInput){
|
||||||
|
boolean find = false;
|
||||||
|
for(GTxIO gotx : mTransactionOutput){
|
||||||
|
if(gitx.getTransaction().getTxid().equals(gotx.getOriginalTxid())){
|
||||||
|
find = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!find){
|
||||||
|
utxo.add(gitx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return utxo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire the onBalanceChange event
|
||||||
|
*/
|
||||||
|
public void BalanceChange() {
|
||||||
|
this.getAccount().balanceChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
GeneralCoinAddress that = (GeneralCoinAddress) o;
|
||||||
|
|
||||||
|
return mIsChange == that.mIsChange && mIndex == that.mIndex && mId == -1
|
||||||
|
&& (mAccount != null ? mAccount.equals(that.mAccount) : that.mAccount == null
|
||||||
|
&& (mKey != null ? mKey.equals(that.mKey) : that.mKey == null
|
||||||
|
&& (mTransactionInput != null ? mTransactionInput.equals(that.mTransactionInput)
|
||||||
|
: that.mTransactionInput == null && (mTransactionOutput != null
|
||||||
|
? mTransactionOutput.equals(that.mTransactionOutput)
|
||||||
|
: that.mTransactionOutput == null))));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = (int) mId;
|
||||||
|
result = 31 * result + (mAccount != null ? mAccount.hashCode() : 0);
|
||||||
|
result = 31 * result + (mIsChange ? 1 : 0);
|
||||||
|
result = 31 * result + mIndex;
|
||||||
|
result = 31 * result + (mKey != null ? mKey.hashCode() : 0);
|
||||||
|
result = 31 * result + (mTransactionInput != null ? mTransactionInput.hashCode() : 0);
|
||||||
|
result = 31 * result + (mTransactionOutput != null ? mTransactionOutput.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the transactions of this Address
|
||||||
|
* @param transaction The transaction to update
|
||||||
|
* @return true if this address has the transaction false otherwise
|
||||||
|
*/
|
||||||
|
public boolean updateTransaction(GeneralTransaction transaction){
|
||||||
|
for(GTxIO gitx : mTransactionInput){
|
||||||
|
if(gitx.getTransaction().equals(transaction)){
|
||||||
|
gitx.getTransaction().setConfirm(transaction.getConfirm());
|
||||||
|
gitx.getTransaction().setBlock(transaction.getBlock());
|
||||||
|
gitx.getTransaction().setBlockHeight(transaction.getBlockHeight());
|
||||||
|
gitx.getTransaction().setDate(transaction.getDate());
|
||||||
|
gitx.getTransaction().setMemo(transaction.getMemo());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(GTxIO gotx : mTransactionOutput){
|
||||||
|
if(gotx.getTransaction().equals(transaction)){
|
||||||
|
gotx.getTransaction().setConfirm(transaction.getConfirm());
|
||||||
|
gotx.getTransaction().setBlock(transaction.getBlock());
|
||||||
|
gotx.getTransaction().setBlockHeight(transaction.getBlockHeight());
|
||||||
|
gotx.getTransaction().setDate(transaction.getDate());
|
||||||
|
gotx.getTransaction().setMemo(transaction.getMemo());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ public class GeneralSetting {
|
||||||
public final static String SETTING_PATTERN = "PATTERN";
|
public final static String SETTING_PATTERN = "PATTERN";
|
||||||
public final static String SETTING_NAME_RECEIVED_FUNDS_SOUND_PATH = "RECEIVED_FUNDS_SOUND_PATH";
|
public final static String SETTING_NAME_RECEIVED_FUNDS_SOUND_PATH = "RECEIVED_FUNDS_SOUND_PATH";
|
||||||
public final static String SETTING_LAST_LICENSE_READ = "LAST_LICENSE_READ";
|
public final static String SETTING_LAST_LICENSE_READ = "LAST_LICENSE_READ";
|
||||||
|
public final static String SETTING_YUBIKEY_OATH_TOTP_NAME = "YUBIKEY_OATH_TOTP_NAME";
|
||||||
|
public final static String SETTING_YUBIKEY_OATH_TOTP_PASSWORD = "YUBIKEY_OATH_TOTP_PASSWORD";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id on the database
|
* The id on the database
|
||||||
|
|
|
@ -0,0 +1,236 @@
|
||||||
|
package cy.agorise.crystalwallet.models;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import cy.agorise.crystalwallet.enums.CryptoCoin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A General Coin Transaction, of Cryptocurrency like bitcoin
|
||||||
|
*
|
||||||
|
* Created by henry on 06/02/2017.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class GeneralTransaction {
|
||||||
|
/**
|
||||||
|
* The id on the database
|
||||||
|
*/
|
||||||
|
private long mId = -1;
|
||||||
|
/**
|
||||||
|
* The Tx id of this transaciton
|
||||||
|
*/
|
||||||
|
private String mTxId;
|
||||||
|
/**
|
||||||
|
* the type of crypto coin fo this transaction
|
||||||
|
*/
|
||||||
|
private CryptoCoin mType;
|
||||||
|
/**
|
||||||
|
* If this is confirmed, the block where it belongs, 0 means this hasn't be included in any block
|
||||||
|
*/
|
||||||
|
private long mBlock;
|
||||||
|
/**
|
||||||
|
* The amount of fee of this transaction
|
||||||
|
*/
|
||||||
|
private long mFee;
|
||||||
|
/**
|
||||||
|
* the number of confirmations of this transacion, 0 means it hasn't been included in any block
|
||||||
|
*/
|
||||||
|
private int mConfirm;
|
||||||
|
/**
|
||||||
|
* The date of this transaction first broadcast
|
||||||
|
*/
|
||||||
|
private Date mDate;
|
||||||
|
/**
|
||||||
|
* The height of this transaction on the block
|
||||||
|
*/
|
||||||
|
private int mBlockHeight;
|
||||||
|
/**
|
||||||
|
* The memo of this transaciton
|
||||||
|
*/
|
||||||
|
private String mMemo = null;
|
||||||
|
/**
|
||||||
|
* The account that this transaction belong as input or output.
|
||||||
|
*/
|
||||||
|
private GeneralCoinAccount mAccount;
|
||||||
|
/**
|
||||||
|
* The inputs of this transactions
|
||||||
|
*/
|
||||||
|
private List<GTxIO> mTxInputs = new ArrayList();
|
||||||
|
/**
|
||||||
|
* the outputs of this transasctions
|
||||||
|
*/
|
||||||
|
private List<GTxIO> mTxOutputs = new ArrayList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* empty constructor
|
||||||
|
*/
|
||||||
|
public GeneralTransaction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor form the database
|
||||||
|
* @param id the id on the database
|
||||||
|
* @param txid the txid of this transaction
|
||||||
|
* @param type The cryptocoin type
|
||||||
|
* @param block The block where this transaction is, 0 means this hasn't be confirmed
|
||||||
|
* @param fee the fee of this transaction
|
||||||
|
* @param confirm the number of confirmations of this transasciton
|
||||||
|
* @param date the date of this transaction
|
||||||
|
* @param blockHeight the height on the block where this transasciton is
|
||||||
|
* @param memo the memo of this transaction
|
||||||
|
* @param account The account to this transaction belongs, as input or output
|
||||||
|
*/
|
||||||
|
public GeneralTransaction(long id, String txid, CryptoCoin type, long block, long fee, int confirm, Date date, int blockHeight, String memo, GeneralCoinAccount account) {
|
||||||
|
this.mId = id;
|
||||||
|
this.mTxId = txid;
|
||||||
|
this.mType = type;
|
||||||
|
this.mBlock = block;
|
||||||
|
this.mFee = fee;
|
||||||
|
this.mConfirm = confirm;
|
||||||
|
this.mDate = date;
|
||||||
|
this.mBlockHeight = blockHeight;
|
||||||
|
this.mMemo = memo;
|
||||||
|
this.mAccount = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return mId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(long id) {
|
||||||
|
this.mId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTxid() { return mTxId; }
|
||||||
|
|
||||||
|
public void setTxid(String txid) { this.mTxId = txid; }
|
||||||
|
|
||||||
|
public CryptoCoin getType() {
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setType(CryptoCoin type) {
|
||||||
|
this.mType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getBlock() {
|
||||||
|
return mBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlock(long block) {
|
||||||
|
this.mBlock = block;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getFee() {
|
||||||
|
return mFee;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFee(long fee) {
|
||||||
|
this.mFee = fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getConfirm() {
|
||||||
|
return mConfirm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfirm(int confirm) {
|
||||||
|
this.mConfirm = confirm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDate() {
|
||||||
|
return mDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDate(Date date) {
|
||||||
|
this.mDate = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockHeight() {
|
||||||
|
return mBlockHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlockHeight(int blockHeight) {
|
||||||
|
this.mBlockHeight = blockHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMemo() {
|
||||||
|
return mMemo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMemo(String memo) {
|
||||||
|
this.mMemo = memo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GTxIO> getTxInputs() {
|
||||||
|
return mTxInputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTxInputs(List<GTxIO> txInputs) {
|
||||||
|
this.mTxInputs = txInputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<GTxIO> getTxOutputs() {
|
||||||
|
return mTxOutputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTxOutputs(List<GTxIO> txOutputs) {
|
||||||
|
this.mTxOutputs = txOutputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeneralCoinAccount getAccount() {
|
||||||
|
return mAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccount(GeneralCoinAccount account) {
|
||||||
|
this.mAccount = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
GeneralTransaction that = (GeneralTransaction) o;
|
||||||
|
|
||||||
|
if (mTxId != null ? !mTxId.equals(that.mTxId) : that.mTxId != null) return false;
|
||||||
|
return mType == that.mType;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = mTxId != null ? mTxId.hashCode() : 0;
|
||||||
|
result = 31 * result + mType.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns how this transaction changes the balance of the account
|
||||||
|
* @return The amount of balance this transasciton adds to the total balance of the account
|
||||||
|
*/
|
||||||
|
public double getAccountBalanceChange(){
|
||||||
|
double balance = 0;
|
||||||
|
boolean theresAccountInput = false;
|
||||||
|
|
||||||
|
for (GTxIO txInputs : this.getTxInputs()){
|
||||||
|
if (txInputs.isOut() && (txInputs.getAddress() != null)){
|
||||||
|
balance += -txInputs.getAmount();
|
||||||
|
theresAccountInput = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (GTxIO txOutput : this.getTxOutputs()){
|
||||||
|
if (!txOutput.isOut() && (txOutput.getAddress() != null)){
|
||||||
|
balance += txOutput.getAmount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theresAccountInput){
|
||||||
|
balance += -this.getFee();
|
||||||
|
}
|
||||||
|
|
||||||
|
return balance;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,11 @@
|
||||||
package cy.agorise.crystalwallet.util;
|
package cy.agorise.crystalwallet.util;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import cy.agorise.crystalwallet.util.yubikey.TOTP;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Henry Varona on 29/1/2018.
|
* Created by Henry Varona on 29/1/2018.
|
||||||
*/
|
*/
|
||||||
|
@ -19,4 +25,35 @@ public class PasswordManager {
|
||||||
public static String encriptPassword(String password){
|
public static String encriptPassword(String password){
|
||||||
return password;
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String totpd(String sharedSecret, long unixtime, byte[] salt){
|
||||||
|
char[] ch = sharedSecret.toCharArray();
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (char c : ch) {
|
||||||
|
String hexCode=String.format("%H", c);
|
||||||
|
builder.append(hexCode);
|
||||||
|
}
|
||||||
|
String secretHex = String.format("%040x", new BigInteger(1, sharedSecret.getBytes()));
|
||||||
|
|
||||||
|
long time = unixtime/30L;
|
||||||
|
|
||||||
|
String steps = Long.toHexString(time).toUpperCase();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*for (int i=0;i<64;i++){
|
||||||
|
steps = Long.toHexString(time+i).toUpperCase();
|
||||||
|
Log.i("TOTPTEST", TOTP.generateTOTP(
|
||||||
|
secretHex,
|
||||||
|
steps,
|
||||||
|
"6", "HmacSHA1", salt));
|
||||||
|
secretHex = "00"+secretHex;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return TOTP.generateTOTP(
|
||||||
|
secretHex,
|
||||||
|
steps,
|
||||||
|
"6", "HmacSHA1", salt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package cy.agorise.crystalwallet.util.yubikey;
|
||||||
|
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import kotlin.jvm.internal.Intrinsics;
|
||||||
|
|
||||||
|
public enum Algorithm {
|
||||||
|
SHA1((byte)1, "SHA1", 64),
|
||||||
|
SHA256((byte)2, "SHA256", 64),
|
||||||
|
SHA512((byte)3, "SHA512", 128);
|
||||||
|
|
||||||
|
protected Byte byteVal;
|
||||||
|
protected String name;
|
||||||
|
protected int blockSize;
|
||||||
|
protected MessageDigest messageDigest;
|
||||||
|
|
||||||
|
Algorithm(Byte byteVal, String name, int blockSize){
|
||||||
|
this.byteVal = byteVal;
|
||||||
|
this.name = name;
|
||||||
|
this.blockSize = blockSize;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.messageDigest = MessageDigest.getInstance(name);
|
||||||
|
} catch (NoSuchAlgorithmException e){
|
||||||
|
this.messageDigest = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Byte getByteVal(){
|
||||||
|
return this.byteVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName(){
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockSize() {
|
||||||
|
return this.blockSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] prepareKey(byte[] key){
|
||||||
|
int keyLength = key.length;
|
||||||
|
byte[] keyPrepared;
|
||||||
|
if ((0 <= keyLength) && (keyLength <= 13)) {
|
||||||
|
keyPrepared = Arrays.copyOf(key, 14);
|
||||||
|
return keyPrepared;
|
||||||
|
}
|
||||||
|
if ((14 <= keyLength) && (keyLength <= this.blockSize)){
|
||||||
|
keyPrepared = key;
|
||||||
|
return keyPrepared;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPrepared = this.messageDigest.digest(key);
|
||||||
|
return keyPrepared;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package cy.agorise.crystalwallet.util.yubikey;
|
||||||
|
|
||||||
|
public enum OathType {
|
||||||
|
HOTP((byte)0x10),
|
||||||
|
TOTP((byte)0x20);
|
||||||
|
|
||||||
|
protected Byte byteVal;
|
||||||
|
|
||||||
|
OathType(Byte byteVal){
|
||||||
|
this.byteVal = byteVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Byte getByteVal(){
|
||||||
|
return this.byteVal;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
package cy.agorise.crystalwallet.util.yubikey;
|
||||||
|
|
||||||
|
import java.lang.reflect.UndeclaredThrowableException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Source of this class: https://tools.ietf.org/html/rfc6238
|
||||||
|
* */
|
||||||
|
|
||||||
|
public class TOTP {
|
||||||
|
|
||||||
|
private TOTP() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method uses the JCE to provide the crypto algorithm.
|
||||||
|
* HMAC computes a Hashed Message Authentication Code with the
|
||||||
|
* crypto hash algorithm as a parameter.
|
||||||
|
*
|
||||||
|
* @param crypto: the crypto algorithm (HmacSHA1, HmacSHA256,
|
||||||
|
* HmacSHA512)
|
||||||
|
* @param keyBytes: the bytes to use for the HMAC key
|
||||||
|
* @param text: the message or text to be authenticated
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static byte[] hmac_sha(String crypto, byte[] keyBytes,
|
||||||
|
byte[] text, byte[] salt){
|
||||||
|
try {
|
||||||
|
Mac hmac;
|
||||||
|
hmac = Mac.getInstance(crypto);
|
||||||
|
SecretKeySpec macKey =
|
||||||
|
new SecretKeySpec(keyBytes, "RAW");
|
||||||
|
hmac.init(macKey);
|
||||||
|
return hmac.doFinal(text);
|
||||||
|
/*PBEKeySpec spec = new PBEKeySpec((new String(text)).toCharArray(), salt, 1000, 128);
|
||||||
|
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
|
||||||
|
byte[] hash = skf.generateSecret(spec).getEncoded();
|
||||||
|
return hash;*/
|
||||||
|
} catch (GeneralSecurityException gse) {
|
||||||
|
throw new UndeclaredThrowableException(gse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method converts a HEX string to Byte[]
|
||||||
|
*
|
||||||
|
* @param hex: the HEX string
|
||||||
|
*
|
||||||
|
* @return: a byte array
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static byte[] hexStr2Bytes(String hex){
|
||||||
|
// Adding one byte to get the right conversion
|
||||||
|
// Values starting with "0" can be converted
|
||||||
|
byte[] bArray = new BigInteger("10" + hex,16).toByteArray();
|
||||||
|
|
||||||
|
// Copy all the REAL bytes, not the "first"
|
||||||
|
byte[] ret = new byte[bArray.length - 1];
|
||||||
|
for (int i = 0; i < ret.length; i++)
|
||||||
|
ret[i] = bArray[i+1];
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int[] DIGITS_POWER
|
||||||
|
// 0 1 2 3 4 5 6 7 8
|
||||||
|
= {1,10,100,1000,10000,100000,1000000,10000000,100000000 };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method generates a TOTP value for the given
|
||||||
|
* set of parameters.
|
||||||
|
*
|
||||||
|
* @param key: the shared secret, HEX encoded
|
||||||
|
* @param time: a value that reflects a time
|
||||||
|
* @param returnDigits: number of digits to return
|
||||||
|
*
|
||||||
|
* @return: a numeric String in base 10 that includes
|
||||||
|
* {@link truncationDigits} digits
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static String generateTOTP(String key,
|
||||||
|
String time,
|
||||||
|
String returnDigits,
|
||||||
|
byte[] salt){
|
||||||
|
return generateTOTP(key, time, returnDigits, "PBKDF2WithHmacSHA1", salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method generates a TOTP value for the given
|
||||||
|
* set of parameters.
|
||||||
|
*
|
||||||
|
* @param key: the shared secret, HEX encoded
|
||||||
|
* @param time: a value that reflects a time
|
||||||
|
* @param returnDigits: number of digits to return
|
||||||
|
*
|
||||||
|
* @return: a numeric String in base 10 that includes
|
||||||
|
* {@link truncationDigits} digits
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static String generateTOTP256(String key,
|
||||||
|
String time,
|
||||||
|
String returnDigits,
|
||||||
|
byte[] salt){
|
||||||
|
return generateTOTP(key, time, returnDigits, "HmacSHA256", salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method generates a TOTP value for the given
|
||||||
|
* set of parameters.
|
||||||
|
*
|
||||||
|
* @param key: the shared secret, HEX encoded
|
||||||
|
* @param time: a value that reflects a time
|
||||||
|
* @param returnDigits: number of digits to return
|
||||||
|
*
|
||||||
|
* @return: a numeric String in base 10 that includes
|
||||||
|
* {@link truncationDigits} digits
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static String generateTOTP512(String key,
|
||||||
|
String time,
|
||||||
|
String returnDigits,
|
||||||
|
byte[] salt){
|
||||||
|
return generateTOTP(key, time, returnDigits, "HmacSHA512", salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method generates a TOTP value for the given
|
||||||
|
* set of parameters.
|
||||||
|
*
|
||||||
|
* @param key: the shared secret, HEX encoded
|
||||||
|
* @param time: a value that reflects a time
|
||||||
|
* @param returnDigits: number of digits to return
|
||||||
|
* @param crypto: the crypto function to use
|
||||||
|
*
|
||||||
|
* @return: a numeric String in base 10 that includes
|
||||||
|
* {@link truncationDigits} digits
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static String generateTOTP(String key,
|
||||||
|
String time,
|
||||||
|
String returnDigits,
|
||||||
|
String crypto,
|
||||||
|
byte[] salt){
|
||||||
|
int codeDigits = Integer.decode(returnDigits).intValue();
|
||||||
|
String result = null;
|
||||||
|
|
||||||
|
// Using the counter
|
||||||
|
// First 8 bytes are for the movingFactor
|
||||||
|
// Compliant with base RFC 4226 (HOTP)
|
||||||
|
while (time.length() < 16 )
|
||||||
|
time = "0" + time;
|
||||||
|
|
||||||
|
// Get the HEX in a Byte[]
|
||||||
|
byte[] msg = hexStr2Bytes(time);
|
||||||
|
byte[] k = hexStr2Bytes(key);
|
||||||
|
byte[] hash = hmac_sha(crypto, k, msg, salt);
|
||||||
|
|
||||||
|
// put selected bytes into result int
|
||||||
|
int offset = hash[hash.length - 1] & 0xf;
|
||||||
|
|
||||||
|
int binary =
|
||||||
|
((hash[offset] & 0x7f) << 24) |
|
||||||
|
((hash[offset + 1] & 0xff) << 16) |
|
||||||
|
((hash[offset + 2] & 0xff) << 8) |
|
||||||
|
(hash[offset + 3] & 0xff);
|
||||||
|
|
||||||
|
int otp = binary % DIGITS_POWER[codeDigits];
|
||||||
|
|
||||||
|
result = Integer.toString(otp);
|
||||||
|
while (result.length() < codeDigits) {
|
||||||
|
result = "0" + result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,268 @@
|
||||||
|
package cy.agorise.crystalwallet.util.yubikey
|
||||||
|
|
||||||
|
import android.nfc.tech.IsoDep
|
||||||
|
import android.util.Base64
|
||||||
|
//import com.yubico.yubioath.exc.AppletMissingException
|
||||||
|
//import com.yubico.yubioath.exc.AppletSelectException
|
||||||
|
//import com.yubico.yubioath.exc.StorageFullException
|
||||||
|
//import com.yubico.yubioath.transport.ApduError
|
||||||
|
//import com.yubico.yubioath.transport.Backend
|
||||||
|
import java.io.Closeable
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
|
||||||
|
class YkOathApi @Throws(IOException::class/*, AppletSelectException::class*/)
|
||||||
|
constructor(private var tag: IsoDep) : Closeable {
|
||||||
|
val deviceSalt: ByteArray
|
||||||
|
val deviceInfo: DeviceInfo
|
||||||
|
private var challenge = byteArrayOf()
|
||||||
|
|
||||||
|
init {
|
||||||
|
//try {
|
||||||
|
val resp = send(0xa4.toByte(), p1 = 0x04) { put(AID) }
|
||||||
|
val version = Version.parse(resp.parseTlv(VERSION_TAG))
|
||||||
|
deviceSalt = resp.parseTlv(NAME_TAG)
|
||||||
|
val id = getDeviceId(deviceSalt)
|
||||||
|
|
||||||
|
if (resp.hasRemaining()) {
|
||||||
|
challenge = resp.parseTlv(CHALLENGE_TAG)
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceInfo = DeviceInfo(id, false, version, challenge.isNotEmpty())
|
||||||
|
/*} catch (e: ApduError) {
|
||||||
|
throw AppletMissingException()
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fun isLocked(): Boolean = challenge.isNotEmpty()
|
||||||
|
|
||||||
|
fun reselect() {
|
||||||
|
send(0xa4.toByte(), p1 = 0x04) { put(AID) }.apply {
|
||||||
|
parseTlv(VERSION_TAG)
|
||||||
|
parseTlv(NAME_TAG)
|
||||||
|
challenge = if (hasRemaining()) {
|
||||||
|
parseTlv(CHALLENGE_TAG)
|
||||||
|
} else byteArrayOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unlock(signer: ChallengeSigner): Boolean {
|
||||||
|
val response = signer.sign(challenge)
|
||||||
|
val myChallenge = ByteArray(8)
|
||||||
|
val random = SecureRandom()
|
||||||
|
random.nextBytes(myChallenge)
|
||||||
|
val myResponse = signer.sign(myChallenge)
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val resp = send(VALIDATE_INS) {
|
||||||
|
tlv(RESPONSE_TAG, response)
|
||||||
|
tlv(CHALLENGE_TAG, myChallenge)
|
||||||
|
}
|
||||||
|
Arrays.equals(myResponse, resp.parseTlv(RESPONSE_TAG))
|
||||||
|
} catch (e: ApduError) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setLockCode(secret: ByteArray) {
|
||||||
|
val challenge = ByteArray(8)
|
||||||
|
val random = SecureRandom()
|
||||||
|
random.nextBytes(challenge)
|
||||||
|
val response = Mac.getInstance("HmacSHA1").apply {
|
||||||
|
init(SecretKeySpec(secret, algorithm))
|
||||||
|
}.doFinal(challenge)
|
||||||
|
|
||||||
|
send(SET_CODE_INS) {
|
||||||
|
tlv(KEY_TAG, byteArrayOf(OathType.TOTP.byteVal or Algorithm.SHA1.byteVal) + secret)
|
||||||
|
tlv(CHALLENGE_TAG, challenge)
|
||||||
|
tlv(RESPONSE_TAG, response)
|
||||||
|
}
|
||||||
|
deviceInfo.hasPassword = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unsetLockCode() {
|
||||||
|
send(SET_CODE_INS) { tlv(KEY_TAG) }
|
||||||
|
deviceInfo.hasPassword = false
|
||||||
|
}*/
|
||||||
|
|
||||||
|
fun listCredentials(): List<String> {
|
||||||
|
val resp = send(LIST_INS)
|
||||||
|
|
||||||
|
return mutableListOf<String>().apply {
|
||||||
|
while (resp.hasRemaining()) {
|
||||||
|
val nameBytes = resp.parseTlv(NAME_LIST_TAG)
|
||||||
|
add(String(nameBytes, 1, nameBytes.size - 1)) //First byte is algorithm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun putCode(name: String, key: ByteArray, type: OathType, algorithm: Algorithm, digits: Byte, imf: Int, touch: Boolean) {
|
||||||
|
//send(tag, 0xa4.toByte(), p1 = 0x04) { put(AID) }
|
||||||
|
//if (touch && deviceInfo.version.major < 4) {
|
||||||
|
// throw IllegalArgumentException("Require touch requires YubiKey 4")
|
||||||
|
//}
|
||||||
|
//try {
|
||||||
|
send(PUT_INS) {
|
||||||
|
tlv(NAME_TAG, name.toByteArray())
|
||||||
|
tlv(KEY_TAG, byteArrayOf(type.byteVal or algorithm.byteVal, digits) + algorithm.prepareKey(key))
|
||||||
|
if (touch) put(PROPERTY_TAG).put(REQUIRE_TOUCH_PROP)
|
||||||
|
if (type == OathType.HOTP && imf > 0) put(IMF_TAG).put(4).putInt(imf)
|
||||||
|
}
|
||||||
|
//} catch (e: ApduError) {
|
||||||
|
// throw if (e.status == APDU_FILE_FULL) StorageFullException("No more room for OATH credentials!") else e
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*fun deleteCode(name: String) {
|
||||||
|
send(DELETE_INS) { tlv(NAME_TAG, name.toByteArray()) }
|
||||||
|
}*/
|
||||||
|
|
||||||
|
fun calculate(name: String, challenge: ByteArray, truncate: Boolean = true): ByteArray {
|
||||||
|
val resp = send(CALCULATE_INS, p2 = if (truncate) 1 else 0) {
|
||||||
|
tlv(NAME_TAG, name.toByteArray())
|
||||||
|
tlv(CHALLENGE_TAG, challenge)
|
||||||
|
}
|
||||||
|
return resp.parseTlv(resp.slice().get())
|
||||||
|
}
|
||||||
|
|
||||||
|
/*fun calculateAll(challenge: ByteArray): List<ResponseData> {
|
||||||
|
val resp = send(CALCULATE_ALL_INS, p2 = 1) {
|
||||||
|
tlv(CHALLENGE_TAG, challenge)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mutableListOf<ResponseData>().apply {
|
||||||
|
while (resp.hasRemaining()) {
|
||||||
|
val name = String(resp.parseTlv(NAME_TAG))
|
||||||
|
val respType = resp.slice().get() // Peek
|
||||||
|
val hashBytes = resp.parseTlv(respType)
|
||||||
|
val oathType = if (respType == NO_RESPONSE_TAG) OathType.HOTP else OathType.TOTP
|
||||||
|
val touch = respType == TOUCH_TAG
|
||||||
|
|
||||||
|
add(ResponseData(name, oathType, touch, hashBytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun send(ins: Byte, p1: Byte = 0, p2: Byte = 0, data: ByteBuffer.() -> Unit = {}): ByteBuffer {
|
||||||
|
val apdu = ByteBuffer.allocate(256).put(0).put(ins).put(p1).put(p2).put(0).apply(data).let {
|
||||||
|
it.put(4, (it.position() - 5).toByte()).array().copyOfRange(0, it.position())
|
||||||
|
}
|
||||||
|
|
||||||
|
return ByteBuffer.allocate(4096).apply {
|
||||||
|
var resp = splitApduResponse(tag.transceive(apdu))
|
||||||
|
while (resp.status != APDU_OK) {
|
||||||
|
if ((resp.status shr 8).toByte() == APDU_DATA_REMAINING_SW1) {
|
||||||
|
put(resp.data)
|
||||||
|
resp = splitApduResponse(tag.transceive(byteArrayOf(0, SEND_REMAINING_INS, 0, 0)))
|
||||||
|
} else {
|
||||||
|
throw IOException(""+resp.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
put(resp.data).limit(position()).rewind()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
/*backend.close()
|
||||||
|
backend = object : Backend {
|
||||||
|
override val persistent: Boolean = false
|
||||||
|
override fun sendApdu(apdu: ByteArray): ByteArray = throw IOException("SENDING APDU ON CLOSED BACKEND!")
|
||||||
|
override fun close() = throw IOException("Backend already closed!")
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Version(val major: Int, val minor: Int, val micro: Int) {
|
||||||
|
companion object {
|
||||||
|
fun parse(data: ByteArray): Version = Version(data[0].toInt(), data[1].toInt(), data[2].toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "%d.%d.%d".format(major, minor, micro)
|
||||||
|
|
||||||
|
fun compare(major: Int, minor: Int, micro: Int): Int {
|
||||||
|
return if (major > this.major || (major == this.major && (minor > this.minor || minor == this.minor && micro > this.micro))) {
|
||||||
|
-1
|
||||||
|
} else if (major == this.major && minor == this.minor && micro == this.micro) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compare(version: Version): Int = compare(version.major, version.minor, version.micro)
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeviceInfo(val id: String, val persistent: Boolean, val version: Version, initialHasPassword: Boolean) {
|
||||||
|
var hasPassword = initialHasPassword
|
||||||
|
internal set
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResponseData(val key: String, val oathType: OathType, val touch: Boolean, val data: ByteArray)
|
||||||
|
|
||||||
|
private infix fun Byte.or(b: Byte): Byte = (toInt() or b.toInt()).toByte()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const private val APDU_OK = 0x9000
|
||||||
|
const private val APDU_FILE_FULL = 0x6a84
|
||||||
|
const private val APDU_DATA_REMAINING_SW1 = 0x61.toByte()
|
||||||
|
|
||||||
|
const private val NAME_TAG: Byte = 0x71
|
||||||
|
const private val NAME_LIST_TAG: Byte = 0x72
|
||||||
|
const private val KEY_TAG: Byte = 0x73
|
||||||
|
const private val CHALLENGE_TAG: Byte = 0x74
|
||||||
|
const private val RESPONSE_TAG: Byte = 0x75
|
||||||
|
const private val T_RESPONSE_TAG: Byte = 0x76
|
||||||
|
const private val NO_RESPONSE_TAG: Byte = 0x77
|
||||||
|
const private val PROPERTY_TAG: Byte = 0x78
|
||||||
|
const private val VERSION_TAG: Byte = 0x79
|
||||||
|
const private val IMF_TAG: Byte = 0x7a
|
||||||
|
const private val TOUCH_TAG: Byte = 0x7c
|
||||||
|
|
||||||
|
const private val ALWAYS_INCREASING_PROP: Byte = 0x01
|
||||||
|
const private val REQUIRE_TOUCH_PROP: Byte = 0x02
|
||||||
|
|
||||||
|
const private val PUT_INS: Byte = 0x01
|
||||||
|
const private val DELETE_INS: Byte = 0x02
|
||||||
|
const private val SET_CODE_INS: Byte = 0x03
|
||||||
|
const private val RESET_INS: Byte = 0x04
|
||||||
|
|
||||||
|
const private val LIST_INS = 0xa1.toByte()
|
||||||
|
const private val CALCULATE_INS = 0xa2.toByte()
|
||||||
|
const private val VALIDATE_INS = 0xa3.toByte()
|
||||||
|
const private val CALCULATE_ALL_INS = 0xa4.toByte()
|
||||||
|
const private val SEND_REMAINING_INS = 0xa5.toByte()
|
||||||
|
|
||||||
|
private val AID = byteArrayOf(0xa0.toByte(), 0x00, 0x00, 0x05, 0x27, 0x21, 0x01, 0x01)
|
||||||
|
|
||||||
|
private fun getDeviceId(id: ByteArray): String {
|
||||||
|
val digest = MessageDigest.getInstance("SHA256").apply {
|
||||||
|
update(id)
|
||||||
|
}.digest()
|
||||||
|
|
||||||
|
return Base64.encodeToString(digest.sliceArray(0 until 16), Base64.NO_PADDING or Base64.NO_WRAP)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun ByteBuffer.parseTlv(tag: Byte): ByteArray {
|
||||||
|
val readTag = get()
|
||||||
|
if (readTag != tag) {
|
||||||
|
throw IOException("Required tag: %02x, got %02x".format(tag, readTag))
|
||||||
|
}
|
||||||
|
return ByteArray(0xff and get().toInt()).apply { get(this) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteBuffer.tlv(tag: Byte, data: ByteArray = byteArrayOf()): ByteBuffer {
|
||||||
|
return put(tag).put(data.size.toByte()).put(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Response(val data: ByteArray, val status: Int)
|
||||||
|
|
||||||
|
private fun splitApduResponse(resp: ByteArray): Response {
|
||||||
|
return Response(
|
||||||
|
resp.copyOfRange(0, resp.size - 2),
|
||||||
|
((0xff and resp[resp.size - 2].toInt()) shl 8) or (0xff and resp[resp.size - 1].toInt()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
app/src/main/res/layout/activity_pocket_request.xml
Normal file
18
app/src/main/res/layout/activity_pocket_request.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:background="@color/colorPrimary">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Please tap pocket!"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
|
@ -68,7 +68,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="140dp"
|
android:layout_height="140dp"
|
||||||
android:background="@color/lightGray"
|
android:background="@color/lightGray"
|
||||||
android:visibility="gone"
|
android:visibility="visible"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
@ -102,15 +102,14 @@
|
||||||
android:text="@string/user_name_password_placeholder"
|
android:text="@string/user_name_password_placeholder"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvPocketSecurity"
|
app:layout_constraintTop_toBottomOf="@id/tvPocketSecurity"
|
||||||
app:layout_constraintStart_toStartOf="@id/tvPocketSecurity"/>
|
app:layout_constraintStart_toStartOf="@id/tvPocketSecurity"/>
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/etPocketPassword"
|
android:id="@+id/etPocketPassword"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="numberPassword"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/tvPocketSecurityUser"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/tvPocketSecurity"
|
|
||||||
app:layout_constraintEnd_toEndOf="@id/sPocketSecurity"
|
app:layout_constraintEnd_toEndOf="@id/sPocketSecurity"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/tvPocketSecurity"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tvPocketSecurityUser"
|
||||||
tools:ignore="LabelFor" />
|
tools:ignore="LabelFor" />
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.constraint.ConstraintLayout>
|
13
app/src/main/res/xml/tech.xml
Normal file
13
app/src/main/res/xml/tech.xml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<tech-list>
|
||||||
|
<tech>android.nfc.tech.MifareUltralight</tech>
|
||||||
|
<tech>android.nfc.tech.Ndef</tech>
|
||||||
|
<tech>android.nfc.tech.NfcA</tech>
|
||||||
|
</tech-list>
|
||||||
|
<tech-list>
|
||||||
|
<tech>android.nfc.tech.MifareClassic</tech>
|
||||||
|
<tech>android.nfc.tech.Ndef</tech>
|
||||||
|
<tech>android.nfc.tech.NfcA</tech>
|
||||||
|
</tech-list>
|
||||||
|
</resources>
|
|
@ -6,6 +6,7 @@ buildscript {
|
||||||
jcenter()
|
jcenter()
|
||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
|
ext.kotlin_version = '1.2.51'
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||||
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
||||||
|
|
Loading…
Reference in a new issue