diff --git a/app/build.gradle b/app/build.gradle index 13bcdb41..e2c74084 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -115,6 +115,10 @@ dependencies { implementation 'org.slf4j:slf4j-nop:1.7.25' implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2' + // https://mvnrepository.com/artifact/com.github.aelstad/keccakj + implementation 'com.github.aelstad:keccakj:1.1.0' + + testImplementation "junit:junit:$rootProject.ext.junitVersion" testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion" diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java index 186236bb..15189594 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java @@ -16,6 +16,7 @@ package com.m2049r.xmrwallet; +import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; @@ -33,6 +34,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.LinearLayout; @@ -45,6 +47,7 @@ import com.m2049r.xmrwallet.util.FingerprintHelper; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.KeyStoreHelper; import com.m2049r.xmrwallet.util.RestoreHeight; +import com.m2049r.xmrwallet.util.ledger.Monero; import com.m2049r.xmrwallet.widget.Toolbar; import com.nulabinc.zxcvbn.Strength; import com.nulabinc.zxcvbn.Zxcvbn; @@ -240,9 +243,7 @@ public class GenerateFragment extends Fragment { } }); etWalletAddress.setVisibility(View.VISIBLE); - etWalletAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() - - { + etWalletAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { @@ -274,9 +275,7 @@ public class GenerateFragment extends Fragment { } if (type.equals(TYPE_KEY)) { etWalletSpendKey.setVisibility(View.VISIBLE); - etWalletSpendKey.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() - - { + etWalletSpendKey.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { @@ -303,9 +302,7 @@ public class GenerateFragment extends Fragment { } }); } - bGenerate.setOnClickListener(new View.OnClickListener() - - { + bGenerate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Helper.hideKeyboard(getActivity()); @@ -616,4 +613,80 @@ public class GenerateFragment extends Fragment { } super.onCreateOptionsMenu(menu, inflater); } + + AlertDialog ledgerDialog = null; + + public void convertLedgerSeed() { + if (ledgerDialog != null) return; + final Activity activity = getActivity(); + View promptsView = getLayoutInflater().inflate(R.layout.prompt_ledger_seed, null); + android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity); + alertDialogBuilder.setView(promptsView); + + final TextInputLayout etSeed = promptsView.findViewById(R.id.etSeed); + final TextInputLayout etPassphrase = promptsView.findViewById(R.id.etPassphrase); + + etSeed.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable s) { + if (etSeed.getError() != null) { + etSeed.setError(null); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, + int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, + int before, int count) { + } + }); + + alertDialogBuilder + .setCancelable(false) + .setPositiveButton(getString(R.string.label_ok), null) + .setNegativeButton(getString(R.string.label_cancel), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Helper.hideKeyboardAlways(activity); + etWalletMnemonic.getEditText().getText().clear(); + dialog.cancel(); + ledgerDialog = null; + } + }); + + ledgerDialog = alertDialogBuilder.create(); + + ledgerDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String ledgerSeed = etSeed.getEditText().getText().toString(); + String ledgerPassphrase = etPassphrase.getEditText().getText().toString(); + String moneroSeed = Monero.convert(ledgerSeed, ledgerPassphrase); + if (moneroSeed != null) { + etWalletMnemonic.getEditText().setText(moneroSeed); + ledgerDialog.dismiss(); + ledgerDialog = null; + } else { + etSeed.setError(getString(R.string.bad_ledger_seed)); + } + } + }); + } + }); + + // set FLAG_SECURE to prevent screenshots in Release Mode + if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) { + ledgerDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + } + + ledgerDialog.show(); + } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java index 59a330fa..cc6d9083 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java @@ -33,6 +33,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.ImageButton; @@ -620,6 +621,11 @@ public class GenerateReviewFragment extends Fragment { return false; } }); + // set FLAG_SECURE to prevent screenshots in Release Mode + if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) { + openDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + } + return openDialog; } diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java index dc338a21..87f59ab8 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java @@ -1229,6 +1229,12 @@ public class LoginActivity extends BaseActivity case R.id.action_language: onChangeLocale(); return true; + case R.id.action_ledger_seed: + Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container); + if (f instanceof GenerateFragment) { + ((GenerateFragment) f).convertLedgerSeed(); + } + return true; default: return super.onOptionsItemSelected(item); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java b/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java index 13fefa75..45188e01 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java @@ -32,6 +32,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.TextView; @@ -505,6 +506,10 @@ public class NodeFragment extends Fragment }); } }); + // set FLAG_SECURE to prevent screenshots in Release Mode + if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) { + editDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + } etNodePass.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java index 9c634326..696656f6 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java +++ b/app/src/main/java/com/m2049r/xmrwallet/dialog/ProgressDialog.java @@ -62,9 +62,6 @@ public class ProgressDialog extends AlertDialog { pbBar = view.findViewById(R.id.pbBar); tvProgress = view.findViewById(R.id.tvProgress); setView(view); - //setTitle("blabla"); - //super.setMessage("bubbu"); -// view.invalidate(); setIndeterminate(indeterminate); if (maxValue > 0) { setMax(maxValue); diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java index 5c49c90d..f3d29f54 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendBtcConfirmWizardFragment.java @@ -27,11 +27,13 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; +import com.m2049r.xmrwallet.BuildConfig; import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.data.TxData; import com.m2049r.xmrwallet.data.TxDataBtc; @@ -435,6 +437,11 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements return false; } }); + // set FLAG_SECURE to prevent screenshots in Release Mode + if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) { + passwordDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + } + passwordDialog.show(); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java index e7838ba7..f04df155 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendConfirmWizardFragment.java @@ -27,10 +27,12 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.TextView; +import com.m2049r.xmrwallet.BuildConfig; import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.data.TxData; import com.m2049r.xmrwallet.model.PendingTransaction; @@ -322,6 +324,11 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen return false; } }); + // set FLAG_SECURE to prevent screenshots in Release Mode + if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) { + passwordDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + } + passwordDialog.show(); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/ledger/ECsecp256k1.java b/app/src/main/java/com/m2049r/xmrwallet/util/ledger/ECsecp256k1.java new file mode 100644 index 00000000..85015e46 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/util/ledger/ECsecp256k1.java @@ -0,0 +1,81 @@ +/* + * Based on + * https://stackoverflow.com/a/19943894 + * + * Curve parameters from + * https://en.bitcoin.it/wiki/Secp256k1 + * + * Copyright (c) 2019 m2049r + * Copyright (c) 2013 ChiaraHsieh + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.util.ledger; + +import java.math.BigInteger; +import java.security.spec.ECPoint; + +public class ECsecp256k1 { + static private final BigInteger TWO = new BigInteger("2"); + static public final BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16); + static public final BigInteger a = new BigInteger("0000000000000000000000000000000000000000000000000000000000000000", 16); + static public final BigInteger b = new BigInteger("0000000000000000000000000000000000000000000000000000000000000007", 16); + static public final BigInteger n = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16); + static public final ECPoint G = new ECPoint( + new BigInteger("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16), + new BigInteger("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16)); + + public static ECPoint scalmult(BigInteger kin, ECPoint P) { + ECPoint R = ECPoint.POINT_INFINITY, S = P; + BigInteger k = kin.mod(n); // not necessary b/c that's how curves work + int length = k.bitLength(); + byte[] binarray = new byte[length]; + for (int i = 0; i <= length - 1; i++) { + binarray[i] = k.mod(TWO).byteValue(); + k = k.divide(TWO); + } + for (int i = length - 1; i >= 0; i--) { + // i should start at length-1 not -2 because the MSB of binary may not be 1 + R = doublePoint(R); + if (binarray[i] == 1) + R = addPoint(R, S); + } + return R; + } + + public static ECPoint addPoint(ECPoint r, ECPoint s) { + if (r.equals(s)) + return doublePoint(r); + else if (r.equals(ECPoint.POINT_INFINITY)) + return s; + else if (s.equals(ECPoint.POINT_INFINITY)) + return r; + BigInteger slope = (r.getAffineY().subtract(s.getAffineY())).multiply(r.getAffineX().subtract(s.getAffineX()).modInverse(p)).mod(p); + BigInteger Xout = (slope.modPow(TWO, p).subtract(r.getAffineX())).subtract(s.getAffineX()).mod(p); + BigInteger Yout = s.getAffineY().negate().mod(p); + Yout = Yout.add(slope.multiply(s.getAffineX().subtract(Xout))).mod(p); + return new ECPoint(Xout, Yout); + } + + public static ECPoint doublePoint(ECPoint r) { + if (r.equals(ECPoint.POINT_INFINITY)) + return r; + BigInteger slope = (r.getAffineX().pow(2)).multiply(new BigInteger("3")); + slope = slope.add(a); + slope = slope.multiply((r.getAffineY().multiply(TWO)).modInverse(p)); + BigInteger Xout = slope.pow(2).subtract(r.getAffineX().multiply(TWO)).mod(p); + BigInteger Yout = (r.getAffineY().negate()).add(slope.multiply(r.getAffineX().subtract(Xout))).mod(p); + return new ECPoint(Xout, Yout); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/ledger/Monero.java b/app/src/main/java/com/m2049r/xmrwallet/util/ledger/Monero.java new file mode 100644 index 00000000..f4a3fdde --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/util/ledger/Monero.java @@ -0,0 +1,1866 @@ +/* + * A quick and hacky Java implementation of most of + * https://github.com/LedgerHQ/ledger-app-monero/blob/master/tools/python/src/ledger/monero/seedconv.py + * + * Copyright (c) 2019 m2049r + * Copyright 2018 Cedric Mesnil , Ledger SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.util.ledger; + +import com.theromus.sha.Keccak; +import com.theromus.sha.Parameters; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.ECPoint; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.text.Normalizer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.zip.CRC32; + +import javax.crypto.Mac; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import timber.log.Timber; + +public class Monero { + + static public String convert(String mnemonic, String passphrase) { + String[] words = mnemonic.toLowerCase().split("\\s"); + StringBuilder normalizedMnemonic = new StringBuilder(); + int wordCount = 0; + for (String word : words) { + if (word.length() == 0) continue; + if (wordCount > 0) normalizedMnemonic.append(" "); + wordCount++; + normalizedMnemonic.append(word); + } + if ((wordCount != 12) && (wordCount != 24) && (wordCount != 18)) return null; + Monero seed = new Monero(); + try { + return seed.getMnemonic(normalizedMnemonic.toString(), passphrase); + } catch (IllegalStateException | IllegalArgumentException ex) { + return null; + } + } + + private byte[] seed; + private byte[] mkey; + private byte[] mchain; + private byte[] monero_ki; + private byte[] monero_ci; + private byte[] view_key; + private byte[] spend_key; + + private static byte[] NKFDbytes(String str) { + return Normalizer.normalize(str, Normalizer.Form.NFKD).getBytes(); + } + + private static char[] NKFDchars(String str) { + return Normalizer.normalize(str, Normalizer.Form.NFKD).toCharArray(); + } + + private static byte[] fixByteArray32(byte[] b) { + if ((b.length > 33)) throw new IllegalStateException(); + if ((b.length == 33) && (b[0] != 0)) throw new IllegalStateException(); + if (b.length == 33) + return Arrays.copyOfRange(b, 1, 33); + else + return b; + } + + private static byte[] intToBytes(int i, int bytes) { + ByteBuffer buffer = ByteBuffer.allocate(bytes); + buffer.putInt(i); + return buffer.array(); + } + + private static void reverse(byte[] b) { + for (int i = 0; i < b.length / 2; i++) { + byte temp = b[i]; + b[i] = b[b.length - i - 1]; + b[b.length - i - 1] = temp; + } + } + + private void derive(String path) + throws NoSuchAlgorithmException, InvalidKeyException { + byte[] kpar = Arrays.copyOf(mkey, 32); + byte[] cpar = Arrays.copyOf(mchain, 32); + + String[] pathSegments = path.split("/"); + if (!pathSegments[0].equals("m")) + throw new IllegalArgumentException("Path must start with 'm'"); + for (int i = 1; i < pathSegments.length; i++) { + String child = pathSegments[i]; + boolean hardened = child.charAt(child.length() - 1) == '\''; + + byte[] data = new byte[33 + 4]; + if (hardened) { + int c = Integer.parseInt(child.substring(0, child.length() - 1)); + c += 0x80000000; + data[0] = 0; + System.arraycopy(kpar, 0, data, 1, kpar.length); + System.arraycopy(intToBytes(c, 4), 0, data, 1 + kpar.length, 4); + } else { + int c = Integer.parseInt(child); + BigInteger k = new BigInteger(1, kpar); + ECPoint kG = ECsecp256k1.scalmult(k, ECsecp256k1.G); + byte[] xBytes = fixByteArray32(kG.getAffineX().toByteArray()); + byte[] Wpar = new byte[33]; + System.arraycopy(xBytes, 0, Wpar, 33 - xBytes.length, xBytes.length); + byte[] yBytes = fixByteArray32(kG.getAffineY().toByteArray()); + if ((yBytes[yBytes.length - 1] & 1) == 0) + Wpar[0] = 0x02; + else + Wpar[0] = 0x03; + System.arraycopy(Wpar, 0, data, 0, Wpar.length); + System.arraycopy(intToBytes(c, 4), 0, data, Wpar.length, 4); + } + + SecretKeySpec keySpec = new SecretKeySpec(cpar, "HmacSHA512"); + Mac mac = Mac.getInstance("HmacSHA512"); + mac.init(keySpec); + byte[] I = mac.doFinal(data); + BigInteger Il = new BigInteger(1, Arrays.copyOfRange(I, 0, 32)); + BigInteger kparInt = new BigInteger(1, kpar); + Il = Il.add(kparInt).mod(ECsecp256k1.n); + byte[] IlBytes = fixByteArray32(Il.toByteArray()); + kpar = new byte[32]; + System.arraycopy(IlBytes, 0, kpar, 0, 32); + System.arraycopy(I, 32, cpar, 0, I.length - 32); + } + monero_ki = kpar; + monero_ci = cpar; + } + + private void makeSeed(String mnemonic, String passphrase) + throws NoSuchAlgorithmException, InvalidKeySpecException { + SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2withHmacSHA512"); + KeySpec spec = new PBEKeySpec(NKFDchars(mnemonic), NKFDbytes("mnemonic" + passphrase), 2048, 512); + seed = skf.generateSecret(spec).getEncoded(); + } + + private void makeMasterKey() + throws NoSuchAlgorithmException, InvalidKeyException { + SecretKeySpec keySpec = new SecretKeySpec( + NKFDbytes("Bitcoin seed"), + "HmacSHA512"); + Mac mac = Mac.getInstance("HmacSHA512"); + mac.init(keySpec); + byte[] result = mac.doFinal(seed); + mkey = Arrays.copyOfRange(result, 0, 32); + mchain = Arrays.copyOfRange(result, 32, 64); + } + + private void makeKeys() { + BigInteger l = new BigInteger("1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed", 16); + Keccak keccak = new Keccak(); + final byte[] b = keccak.getHash(monero_ki, Parameters.KECCAK_256); + reverse(b); + BigInteger ble = new BigInteger(1, b).mod(l); + spend_key = fixByteArray32(ble.toByteArray()); + reverse(spend_key); + byte[] a = keccak.getHash(spend_key, Parameters.KECCAK_256); + reverse(a); + BigInteger ale = new BigInteger(1, a).mod(l); + view_key = fixByteArray32(ale.toByteArray()); + reverse(view_key); + } + + private String getWords() { + if (spend_key.length != 32) throw new IllegalArgumentException(); + String[] wordList = ENGLISH_WORDS; + List words = new ArrayList<>(); + for (int i = 0; i < spend_key.length / 4; i++) { + long val = ((long) (spend_key[i * 4 + 0] & 0xff) << 0) | + ((long) (spend_key[i * 4 + 1] & 0xff) << 8) | + ((long) (spend_key[i * 4 + 2] & 0xff) << 16) | + ((long) (spend_key[i * 4 + 3] & 0xff) << 24); + long w1 = val % wordList.length; + long w2 = ((val / wordList.length) + w1) % wordList.length; + long w3 = (((val / wordList.length) / wordList.length) + w2) % wordList.length; + + words.add(wordList[(int) w1]); + words.add(wordList[(int) w2]); + words.add(wordList[(int) w3]); + } + + StringBuilder mnemonic = new StringBuilder(); + StringBuilder trimmedWords = new StringBuilder(); + + for (String word : words) { + mnemonic.append(word).append(" "); + trimmedWords.append(word.substring(0, ENGLISH_PREFIX_LENGTH)); + } + CRC32 crc32 = new CRC32(); + crc32.update(trimmedWords.toString().getBytes(StandardCharsets.UTF_8)); + long checksum = crc32.getValue(); + mnemonic.append(words.get((int) (checksum % 24))); + return mnemonic.toString(); + } + + private String getMnemonic(String ledgerMnemonic, String passphrase) { + try { + makeSeed(ledgerMnemonic, passphrase); + makeMasterKey(); + derive("m/44'/128'/0'/0/0"); + makeKeys(); + return getWords(); + } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException ex) { + Timber.e(ex); + } + return null; + } + + public static final int ENGLISH_PREFIX_LENGTH = 3; + public static final String[] ENGLISH_WORDS = { + "abbey", + "abducts", + "ability", + "ablaze", + "abnormal", + "abort", + "abrasive", + "absorb", + "abyss", + "academy", + "aces", + "aching", + "acidic", + "acoustic", + "acquire", + "across", + "actress", + "acumen", + "adapt", + "addicted", + "adept", + "adhesive", + "adjust", + "adopt", + "adrenalin", + "adult", + "adventure", + "aerial", + "afar", + "affair", + "afield", + "afloat", + "afoot", + "afraid", + "after", + "against", + "agenda", + "aggravate", + "agile", + "aglow", + "agnostic", + "agony", + "agreed", + "ahead", + "aided", + "ailments", + "aimless", + "airport", + "aisle", + "ajar", + "akin", + "alarms", + "album", + "alchemy", + "alerts", + "algebra", + "alkaline", + "alley", + "almost", + "aloof", + "alpine", + "already", + "also", + "altitude", + "alumni", + "always", + "amaze", + "ambush", + "amended", + "amidst", + "ammo", + "amnesty", + "among", + "amply", + "amused", + "anchor", + "android", + "anecdote", + "angled", + "ankle", + "annoyed", + "answers", + "antics", + "anvil", + "anxiety", + "anybody", + "apart", + "apex", + "aphid", + "aplomb", + "apology", + "apply", + "apricot", + "aptitude", + "aquarium", + "arbitrary", + "archer", + "ardent", + "arena", + "argue", + "arises", + "army", + "around", + "arrow", + "arsenic", + "artistic", + "ascend", + "ashtray", + "aside", + "asked", + "asleep", + "aspire", + "assorted", + "asylum", + "athlete", + "atlas", + "atom", + "atrium", + "attire", + "auburn", + "auctions", + "audio", + "august", + "aunt", + "austere", + "autumn", + "avatar", + "avidly", + "avoid", + "awakened", + "awesome", + "awful", + "awkward", + "awning", + "awoken", + "axes", + "axis", + "axle", + "aztec", + "azure", + "baby", + "bacon", + "badge", + "baffles", + "bagpipe", + "bailed", + "bakery", + "balding", + "bamboo", + "banjo", + "baptism", + "basin", + "batch", + "bawled", + "bays", + "because", + "beer", + "befit", + "begun", + "behind", + "being", + "below", + "bemused", + "benches", + "berries", + "bested", + "betting", + "bevel", + "beware", + "beyond", + "bias", + "bicycle", + "bids", + "bifocals", + "biggest", + "bikini", + "bimonthly", + "binocular", + "biology", + "biplane", + "birth", + "biscuit", + "bite", + "biweekly", + "blender", + "blip", + "bluntly", + "boat", + "bobsled", + "bodies", + "bogeys", + "boil", + "boldly", + "bomb", + "border", + "boss", + "both", + "bounced", + "bovine", + "bowling", + "boxes", + "boyfriend", + "broken", + "brunt", + "bubble", + "buckets", + "budget", + "buffet", + "bugs", + "building", + "bulb", + "bumper", + "bunch", + "business", + "butter", + "buying", + "buzzer", + "bygones", + "byline", + "bypass", + "cabin", + "cactus", + "cadets", + "cafe", + "cage", + "cajun", + "cake", + "calamity", + "camp", + "candy", + "casket", + "catch", + "cause", + "cavernous", + "cease", + "cedar", + "ceiling", + "cell", + "cement", + "cent", + "certain", + "chlorine", + "chrome", + "cider", + "cigar", + "cinema", + "circle", + "cistern", + "citadel", + "civilian", + "claim", + "click", + "clue", + "coal", + "cobra", + "cocoa", + "code", + "coexist", + "coffee", + "cogs", + "cohesive", + "coils", + "colony", + "comb", + "cool", + "copy", + "corrode", + "costume", + "cottage", + "cousin", + "cowl", + "criminal", + "cube", + "cucumber", + "cuddled", + "cuffs", + "cuisine", + "cunning", + "cupcake", + "custom", + "cycling", + "cylinder", + "cynical", + "dabbing", + "dads", + "daft", + "dagger", + "daily", + "damp", + "dangerous", + "dapper", + "darted", + "dash", + "dating", + "dauntless", + "dawn", + "daytime", + "dazed", + "debut", + "decay", + "dedicated", + "deepest", + "deftly", + "degrees", + "dehydrate", + "deity", + "dejected", + "delayed", + "demonstrate", + "dented", + "deodorant", + "depth", + "desk", + "devoid", + "dewdrop", + "dexterity", + "dialect", + "dice", + "diet", + "different", + "digit", + "dilute", + "dime", + "dinner", + "diode", + "diplomat", + "directed", + "distance", + "ditch", + "divers", + "dizzy", + "doctor", + "dodge", + "does", + "dogs", + "doing", + "dolphin", + "domestic", + "donuts", + "doorway", + "dormant", + "dosage", + "dotted", + "double", + "dove", + "down", + "dozen", + "dreams", + "drinks", + "drowning", + "drunk", + "drying", + "dual", + "dubbed", + "duckling", + "dude", + "duets", + "duke", + "dullness", + "dummy", + "dunes", + "duplex", + "duration", + "dusted", + "duties", + "dwarf", + "dwelt", + "dwindling", + "dying", + "dynamite", + "dyslexic", + "each", + "eagle", + "earth", + "easy", + "eating", + "eavesdrop", + "eccentric", + "echo", + "eclipse", + "economics", + "ecstatic", + "eden", + "edgy", + "edited", + "educated", + "eels", + "efficient", + "eggs", + "egotistic", + "eight", + "either", + "eject", + "elapse", + "elbow", + "eldest", + "eleven", + "elite", + "elope", + "else", + "eluded", + "emails", + "ember", + "emerge", + "emit", + "emotion", + "empty", + "emulate", + "energy", + "enforce", + "enhanced", + "enigma", + "enjoy", + "enlist", + "enmity", + "enough", + "enraged", + "ensign", + "entrance", + "envy", + "epoxy", + "equip", + "erase", + "erected", + "erosion", + "error", + "eskimos", + "espionage", + "essential", + "estate", + "etched", + "eternal", + "ethics", + "etiquette", + "evaluate", + "evenings", + "evicted", + "evolved", + "examine", + "excess", + "exhale", + "exit", + "exotic", + "exquisite", + "extra", + "exult", + "fabrics", + "factual", + "fading", + "fainted", + "faked", + "fall", + "family", + "fancy", + "farming", + "fatal", + "faulty", + "fawns", + "faxed", + "fazed", + "feast", + "february", + "federal", + "feel", + "feline", + "females", + "fences", + "ferry", + "festival", + "fetches", + "fever", + "fewest", + "fiat", + "fibula", + "fictional", + "fidget", + "fierce", + "fifteen", + "fight", + "films", + "firm", + "fishing", + "fitting", + "five", + "fixate", + "fizzle", + "fleet", + "flippant", + "flying", + "foamy", + "focus", + "foes", + "foggy", + "foiled", + "folding", + "fonts", + "foolish", + "fossil", + "fountain", + "fowls", + "foxes", + "foyer", + "framed", + "friendly", + "frown", + "fruit", + "frying", + "fudge", + "fuel", + "fugitive", + "fully", + "fuming", + "fungal", + "furnished", + "fuselage", + "future", + "fuzzy", + "gables", + "gadget", + "gags", + "gained", + "galaxy", + "gambit", + "gang", + "gasp", + "gather", + "gauze", + "gave", + "gawk", + "gaze", + "gearbox", + "gecko", + "geek", + "gels", + "gemstone", + "general", + "geometry", + "germs", + "gesture", + "getting", + "geyser", + "ghetto", + "ghost", + "giant", + "giddy", + "gifts", + "gigantic", + "gills", + "gimmick", + "ginger", + "girth", + "giving", + "glass", + "gleeful", + "glide", + "gnaw", + "gnome", + "goat", + "goblet", + "godfather", + "goes", + "goggles", + "going", + "goldfish", + "gone", + "goodbye", + "gopher", + "gorilla", + "gossip", + "gotten", + "gourmet", + "governing", + "gown", + "greater", + "grunt", + "guarded", + "guest", + "guide", + "gulp", + "gumball", + "gur", + "gusts", + "gutter", + "guys", + "gymnast", + "gypsy", + "gyrate", + "habitat", + "hacksaw", + "haggled", + "hairy", + "hamburger", + "happens", + "hashing", + "hatchet", + "haunted", + "having", + "hawk", + "haystack", + "hazard", + "hectare", + "hedgehog", + "heels", + "hefty", + "height", + "hemlock", + "hence", + "heron", + "hesitate", + "hexagon", + "hickory", + "hiding", + "highway", + "hijack", + "hiker", + "hills", + "himself", + "hinder", + "hippo", + "hire", + "history", + "hitched", + "hive", + "hoax", + "hobby", + "hockey", + "hoisting", + "hold", + "honked", + "hookup", + "hope", + "hornet", + "hospital", + "hotel", + "hounded", + "hover", + "howls", + "hubcaps", + "huddle", + "huge", + "hull", + "humid", + "hunter", + "hurried", + "husband", + "huts", + "hybrid", + "hydrogen", + "hyper", + "iceberg", + "icing", + "icon", + "identity", + "idiom", + "idled", + "idols", + "igloo", + "ignore", + "iguana", + "illness", + "imagine", + "imbalance", + "imitate", + "impel", + "inactive", + "inbound", + "incur", + "industrial", + "inexact", + "inflamed", + "ingested", + "initiate", + "injury", + "inkling", + "inline", + "inmate", + "innocent", + "inorganic", + "input", + "inquest", + "inroads", + "insult", + "intended", + "inundate", + "invoke", + "inwardly", + "ionic", + "irate", + "iris", + "irony", + "irritate", + "island", + "isolated", + "issued", + "italics", + "itches", + "items", + "itinerary", + "itself", + "ivory", + "jabbed", + "jackets", + "jaded", + "jagged", + "jailed", + "jamming", + "january", + "jargon", + "jaunt", + "javelin", + "jaws", + "jazz", + "jeans", + "jeers", + "jellyfish", + "jeopardy", + "jerseys", + "jester", + "jetting", + "jewels", + "jigsaw", + "jingle", + "jittery", + "jive", + "jobs", + "jockey", + "jogger", + "joining", + "joking", + "jolted", + "jostle", + "journal", + "joyous", + "jubilee", + "judge", + "juggled", + "juicy", + "jukebox", + "july", + "jump", + "junk", + "jury", + "justice", + "juvenile", + "kangaroo", + "karate", + "keep", + "kennel", + "kept", + "kernels", + "kettle", + "keyboard", + "kickoff", + "kidneys", + "king", + "kiosk", + "kisses", + "kitchens", + "kiwi", + "knapsack", + "knee", + "knife", + "knowledge", + "knuckle", + "koala", + "laboratory", + "ladder", + "lagoon", + "lair", + "lakes", + "lamb", + "language", + "laptop", + "large", + "last", + "later", + "launching", + "lava", + "lawsuit", + "layout", + "lazy", + "lectures", + "ledge", + "leech", + "left", + "legion", + "leisure", + "lemon", + "lending", + "leopard", + "lesson", + "lettuce", + "lexicon", + "liar", + "library", + "licks", + "lids", + "lied", + "lifestyle", + "light", + "likewise", + "lilac", + "limits", + "linen", + "lion", + "lipstick", + "liquid", + "listen", + "lively", + "loaded", + "lobster", + "locker", + "lodge", + "lofty", + "logic", + "loincloth", + "long", + "looking", + "lopped", + "lordship", + "losing", + "lottery", + "loudly", + "love", + "lower", + "loyal", + "lucky", + "luggage", + "lukewarm", + "lullaby", + "lumber", + "lunar", + "lurk", + "lush", + "luxury", + "lymph", + "lynx", + "lyrics", + "macro", + "madness", + "magically", + "mailed", + "major", + "makeup", + "malady", + "mammal", + "maps", + "masterful", + "match", + "maul", + "maverick", + "maximum", + "mayor", + "maze", + "meant", + "mechanic", + "medicate", + "meeting", + "megabyte", + "melting", + "memoir", + "men", + "merger", + "mesh", + "metro", + "mews", + "mice", + "midst", + "mighty", + "mime", + "mirror", + "misery", + "mittens", + "mixture", + "moat", + "mobile", + "mocked", + "mohawk", + "moisture", + "molten", + "moment", + "money", + "moon", + "mops", + "morsel", + "mostly", + "motherly", + "mouth", + "movement", + "mowing", + "much", + "muddy", + "muffin", + "mugged", + "mullet", + "mumble", + "mundane", + "muppet", + "mural", + "musical", + "muzzle", + "myriad", + "mystery", + "myth", + "nabbing", + "nagged", + "nail", + "names", + "nanny", + "napkin", + "narrate", + "nasty", + "natural", + "nautical", + "navy", + "nearby", + "necklace", + "needed", + "negative", + "neither", + "neon", + "nephew", + "nerves", + "nestle", + "network", + "neutral", + "never", + "newt", + "nexus", + "nibs", + "niche", + "niece", + "nifty", + "nightly", + "nimbly", + "nineteen", + "nirvana", + "nitrogen", + "nobody", + "nocturnal", + "nodes", + "noises", + "nomad", + "noodles", + "northern", + "nostril", + "noted", + "nouns", + "novelty", + "nowhere", + "nozzle", + "nuance", + "nucleus", + "nudged", + "nugget", + "nuisance", + "null", + "number", + "nuns", + "nurse", + "nutshell", + "nylon", + "oaks", + "oars", + "oasis", + "oatmeal", + "obedient", + "object", + "obliged", + "obnoxious", + "observant", + "obtains", + "obvious", + "occur", + "ocean", + "october", + "odds", + "odometer", + "offend", + "often", + "oilfield", + "ointment", + "okay", + "older", + "olive", + "olympics", + "omega", + "omission", + "omnibus", + "onboard", + "oncoming", + "oneself", + "ongoing", + "onion", + "online", + "onslaught", + "onto", + "onward", + "oozed", + "opacity", + "opened", + "opposite", + "optical", + "opus", + "orange", + "orbit", + "orchid", + "orders", + "organs", + "origin", + "ornament", + "orphans", + "oscar", + "ostrich", + "otherwise", + "otter", + "ouch", + "ought", + "ounce", + "ourselves", + "oust", + "outbreak", + "oval", + "oven", + "owed", + "owls", + "owner", + "oxidant", + "oxygen", + "oyster", + "ozone", + "pact", + "paddles", + "pager", + "pairing", + "palace", + "pamphlet", + "pancakes", + "paper", + "paradise", + "pastry", + "patio", + "pause", + "pavements", + "pawnshop", + "payment", + "peaches", + "pebbles", + "peculiar", + "pedantic", + "peeled", + "pegs", + "pelican", + "pencil", + "people", + "pepper", + "perfect", + "pests", + "petals", + "phase", + "pheasants", + "phone", + "phrases", + "physics", + "piano", + "picked", + "pierce", + "pigment", + "piloted", + "pimple", + "pinched", + "pioneer", + "pipeline", + "pirate", + "pistons", + "pitched", + "pivot", + "pixels", + "pizza", + "playful", + "pledge", + "pliers", + "plotting", + "plus", + "plywood", + "poaching", + "pockets", + "podcast", + "poetry", + "point", + "poker", + "polar", + "ponies", + "pool", + "popular", + "portents", + "possible", + "potato", + "pouch", + "poverty", + "powder", + "pram", + "present", + "pride", + "problems", + "pruned", + "prying", + "psychic", + "public", + "puck", + "puddle", + "puffin", + "pulp", + "pumpkins", + "punch", + "puppy", + "purged", + "push", + "putty", + "puzzled", + "pylons", + "pyramid", + "python", + "queen", + "quick", + "quote", + "rabbits", + "racetrack", + "radar", + "rafts", + "rage", + "railway", + "raking", + "rally", + "ramped", + "randomly", + "rapid", + "rarest", + "rash", + "rated", + "ravine", + "rays", + "razor", + "react", + "rebel", + "recipe", + "reduce", + "reef", + "refer", + "regular", + "reheat", + "reinvest", + "rejoices", + "rekindle", + "relic", + "remedy", + "renting", + "reorder", + "repent", + "request", + "reruns", + "rest", + "return", + "reunion", + "revamp", + "rewind", + "rhino", + "rhythm", + "ribbon", + "richly", + "ridges", + "rift", + "rigid", + "rims", + "ringing", + "riots", + "ripped", + "rising", + "ritual", + "river", + "roared", + "robot", + "rockets", + "rodent", + "rogue", + "roles", + "romance", + "roomy", + "roped", + "roster", + "rotate", + "rounded", + "rover", + "rowboat", + "royal", + "ruby", + "rudely", + "ruffled", + "rugged", + "ruined", + "ruling", + "rumble", + "runway", + "rural", + "rustled", + "ruthless", + "sabotage", + "sack", + "sadness", + "safety", + "saga", + "sailor", + "sake", + "salads", + "sample", + "sanity", + "sapling", + "sarcasm", + "sash", + "satin", + "saucepan", + "saved", + "sawmill", + "saxophone", + "sayings", + "scamper", + "scenic", + "school", + "science", + "scoop", + "scrub", + "scuba", + "seasons", + "second", + "sedan", + "seeded", + "segments", + "seismic", + "selfish", + "semifinal", + "sensible", + "september", + "sequence", + "serving", + "session", + "setup", + "seventh", + "sewage", + "shackles", + "shelter", + "shipped", + "shocking", + "shrugged", + "shuffled", + "shyness", + "siblings", + "sickness", + "sidekick", + "sieve", + "sifting", + "sighting", + "silk", + "simplest", + "sincerely", + "sipped", + "siren", + "situated", + "sixteen", + "sizes", + "skater", + "skew", + "skirting", + "skulls", + "skydive", + "slackens", + "sleepless", + "slid", + "slower", + "slug", + "smash", + "smelting", + "smidgen", + "smog", + "smuggled", + "snake", + "sneeze", + "sniff", + "snout", + "snug", + "soapy", + "sober", + "soccer", + "soda", + "software", + "soggy", + "soil", + "solved", + "somewhere", + "sonic", + "soothe", + "soprano", + "sorry", + "southern", + "sovereign", + "sowed", + "soya", + "space", + "speedy", + "sphere", + "spiders", + "splendid", + "spout", + "sprig", + "spud", + "spying", + "square", + "stacking", + "stellar", + "stick", + "stockpile", + "strained", + "stunning", + "stylishly", + "subtly", + "succeed", + "suddenly", + "suede", + "suffice", + "sugar", + "suitcase", + "sulking", + "summon", + "sunken", + "superior", + "surfer", + "sushi", + "suture", + "swagger", + "swept", + "swiftly", + "sword", + "swung", + "syllabus", + "symptoms", + "syndrome", + "syringe", + "system", + "taboo", + "tacit", + "tadpoles", + "tagged", + "tail", + "taken", + "talent", + "tamper", + "tanks", + "tapestry", + "tarnished", + "tasked", + "tattoo", + "taunts", + "tavern", + "tawny", + "taxi", + "teardrop", + "technical", + "tedious", + "teeming", + "tell", + "template", + "tender", + "tepid", + "tequila", + "terminal", + "testing", + "tether", + "textbook", + "thaw", + "theatrics", + "thirsty", + "thorn", + "threaten", + "thumbs", + "thwart", + "ticket", + "tidy", + "tiers", + "tiger", + "tilt", + "timber", + "tinted", + "tipsy", + "tirade", + "tissue", + "titans", + "toaster", + "tobacco", + "today", + "toenail", + "toffee", + "together", + "toilet", + "token", + "tolerant", + "tomorrow", + "tonic", + "toolbox", + "topic", + "torch", + "tossed", + "total", + "touchy", + "towel", + "toxic", + "toyed", + "trash", + "trendy", + "tribal", + "trolling", + "truth", + "trying", + "tsunami", + "tubes", + "tucks", + "tudor", + "tuesday", + "tufts", + "tugs", + "tuition", + "tulips", + "tumbling", + "tunnel", + "turnip", + "tusks", + "tutor", + "tuxedo", + "twang", + "tweezers", + "twice", + "twofold", + "tycoon", + "typist", + "tyrant", + "ugly", + "ulcers", + "ultimate", + "umbrella", + "umpire", + "unafraid", + "unbending", + "uncle", + "under", + "uneven", + "unfit", + "ungainly", + "unhappy", + "union", + "unjustly", + "unknown", + "unlikely", + "unmask", + "unnoticed", + "unopened", + "unplugs", + "unquoted", + "unrest", + "unsafe", + "until", + "unusual", + "unveil", + "unwind", + "unzip", + "upbeat", + "upcoming", + "update", + "upgrade", + "uphill", + "upkeep", + "upload", + "upon", + "upper", + "upright", + "upstairs", + "uptight", + "upwards", + "urban", + "urchins", + "urgent", + "usage", + "useful", + "usher", + "using", + "usual", + "utensils", + "utility", + "utmost", + "utopia", + "uttered", + "vacation", + "vague", + "vain", + "value", + "vampire", + "vane", + "vapidly", + "vary", + "vastness", + "vats", + "vaults", + "vector", + "veered", + "vegan", + "vehicle", + "vein", + "velvet", + "venomous", + "verification", + "vessel", + "veteran", + "vexed", + "vials", + "vibrate", + "victim", + "video", + "viewpoint", + "vigilant", + "viking", + "village", + "vinegar", + "violin", + "vipers", + "virtual", + "visited", + "vitals", + "vivid", + "vixen", + "vocal", + "vogue", + "voice", + "volcano", + "vortex", + "voted", + "voucher", + "vowels", + "voyage", + "vulture", + "wade", + "waffle", + "wagtail", + "waist", + "waking", + "wallets", + "wanted", + "warped", + "washing", + "water", + "waveform", + "waxing", + "wayside", + "weavers", + "website", + "wedge", + "weekday", + "weird", + "welders", + "went", + "wept", + "were", + "western", + "wetsuit", + "whale", + "when", + "whipped", + "whole", + "wickets", + "width", + "wield", + "wife", + "wiggle", + "wildly", + "winter", + "wipeout", + "wiring", + "wise", + "withdrawn", + "wives", + "wizard", + "wobbly", + "woes", + "woken", + "wolf", + "womanly", + "wonders", + "woozy", + "worry", + "wounded", + "woven", + "wrap", + "wrist", + "wrong", + "yacht", + "yahoo", + "yanks", + "yard", + "yawning", + "yearbook", + "yellow", + "yesterday", + "yeti", + "yields", + "yodel", + "yoga", + "younger", + "yoyo", + "zapped", + "zeal", + "zebra", + "zero", + "zesty", + "zigzags", + "zinger", + "zippers", + "zodiac", + "zombie", + "zones", + "zoom" + }; +} diff --git a/app/src/main/java/com/theromus/sha/Keccak.java b/app/src/main/java/com/theromus/sha/Keccak.java new file mode 100644 index 00000000..0163a316 --- /dev/null +++ b/app/src/main/java/com/theromus/sha/Keccak.java @@ -0,0 +1,170 @@ +package com.theromus.sha; + +import static com.theromus.utils.HexUtils.leftRotate64; +import static com.theromus.utils.HexUtils.convertToUint; +import static com.theromus.utils.HexUtils.convertFromLittleEndianTo64; +import static com.theromus.utils.HexUtils.convertFrom64ToLittleEndian; +import static java.lang.Math.min; +import static java.lang.System.arraycopy; +import static java.util.Arrays.fill; + +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; + + +/** + * Keccak implementation. + * + * @author romus + */ +public class Keccak { + + private static BigInteger BIT_64 = new BigInteger("18446744073709551615"); + + /** + * Do hash. + * + * @param message input data + * @param parameter keccak param + * @return byte-array result + */ + public byte[] getHash(final byte[] message, final Parameters parameter) { + int[] uState = new int[200]; + int[] uMessage = convertToUint(message); + + + int rateInBytes = parameter.getRate() / 8; + int blockSize = 0; + int inputOffset = 0; + + // Absorbing phase + while (inputOffset < uMessage.length) { + blockSize = min(uMessage.length - inputOffset, rateInBytes); + for (int i = 0; i < blockSize; i++) { + uState[i] = uState[i] ^ uMessage[i + inputOffset]; + } + + inputOffset = inputOffset + blockSize; + if (blockSize == rateInBytes) { + doKeccakf(uState); + blockSize = 0; + } + } + + // Padding phase + uState[blockSize] = uState[blockSize] ^ parameter.getD(); + if ((parameter.getD() & 0x80) != 0 && blockSize == (rateInBytes - 1)) { + doKeccakf(uState); + } + + uState[rateInBytes - 1] = uState[rateInBytes - 1] ^ 0x80; + doKeccakf(uState); + + // Squeezing phase + ByteArrayOutputStream byteResults = new ByteArrayOutputStream(); + int tOutputLen = parameter.getOutputLen() / 8; + while (tOutputLen > 0) { + blockSize = min(tOutputLen, rateInBytes); + for (int i = 0; i < blockSize; i++) { + byteResults.write((byte) uState[i]); + } + + tOutputLen -= blockSize; + if (tOutputLen > 0) { + doKeccakf(uState); + } + } + + return byteResults.toByteArray(); + } + + private void doKeccakf(final int[] uState) { + BigInteger[][] lState = new BigInteger[5][5]; + + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + int[] data = new int[8]; + arraycopy(uState, 8 * (i + 5 * j), data, 0, data.length); + lState[i][j] = convertFromLittleEndianTo64(data); + } + } + roundB(lState); + + fill(uState, 0); + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + int[] data = convertFrom64ToLittleEndian(lState[i][j]); + arraycopy(data, 0, uState, 8 * (i + 5 * j), data.length); + } + } + + } + + /** + * Permutation on the given state. + * + * @param state state + */ + private void roundB(final BigInteger[][] state) { + int LFSRstate = 1; + for (int round = 0; round < 24; round++) { + BigInteger[] C = new BigInteger[5]; + BigInteger[] D = new BigInteger[5]; + + // θ step + for (int i = 0; i < 5; i++) { + C[i] = state[i][0].xor(state[i][1]).xor(state[i][2]).xor(state[i][3]).xor(state[i][4]); + } + + for (int i = 0; i < 5; i++) { + D[i] = C[(i + 4) % 5].xor(leftRotate64(C[(i + 1) % 5], 1)); + } + + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) { + state[i][j] = state[i][j].xor(D[i]); + } + } + + //ρ and π steps + int x = 1, y = 0; + BigInteger current = state[x][y]; + for (int i = 0; i < 24; i++) { + int tX = x; + x = y; + y = (2 * tX + 3 * y) % 5; + + BigInteger shiftValue = current; + current = state[x][y]; + + state[x][y] = leftRotate64(shiftValue, (i + 1) * (i + 2) / 2); + } + + //χ step + for (int j = 0; j < 5; j++) { + BigInteger[] t = new BigInteger[5]; + for (int i = 0; i < 5; i++) { + t[i] = state[i][j]; + } + + for (int i = 0; i < 5; i++) { + // ~t[(i + 1) % 5] + BigInteger invertVal = t[(i + 1) % 5].xor(BIT_64); + // t[i] ^ ((~t[(i + 1) % 5]) & t[(i + 2) % 5]) + state[i][j] = t[i].xor(invertVal.and(t[(i + 2) % 5])); + } + } + + //ι step + for (int i = 0; i < 7; i++) { + LFSRstate = ((LFSRstate << 1) ^ ((LFSRstate >> 7) * 0x71)) % 256; + // pow(2, i) - 1 + int bitPosition = (1 << i) - 1; + if ((LFSRstate & 2) != 0) { + state[0][0] = state[0][0].xor(new BigInteger("1").shiftLeft(bitPosition)); + } + } + } + } + +} diff --git a/app/src/main/java/com/theromus/sha/Parameters.java b/app/src/main/java/com/theromus/sha/Parameters.java new file mode 100644 index 00000000..6835b5a2 --- /dev/null +++ b/app/src/main/java/com/theromus/sha/Parameters.java @@ -0,0 +1,51 @@ +package com.theromus.sha; + +/** + * The parameters defining the standard FIPS 202. + * + * @author romus + */ +public enum Parameters { + KECCAK_224 (1152, 0x01, 224), + KECCAK_256 (1088, 0x01, 256), + KECCAK_384 (832, 0x01, 384), + KECCAK_512 (576, 0x01, 512), + + SHA3_224 (1152, 0x06, 224), + SHA3_256 (1088, 0x06, 256), + SHA3_384 (832, 0x06, 384), + SHA3_512 (576, 0x06, 512), + + SHAKE128 (1344, 0x1F, 256), + SHAKE256 (1088, 0x1F, 512); + + private final int rate; + + /** + * Delimited suffix. + */ + public final int d; + + /** + * Output length (bits). + */ + public final int outputLen; + + Parameters(int rate, int d, int outputLen) { + this.rate = rate; + this.d = d; + this.outputLen = outputLen; + } + + public int getRate() { + return rate; + } + + public int getD() { + return d; + } + + public int getOutputLen() { + return outputLen; + } +} diff --git a/app/src/main/java/com/theromus/utils/HexUtils.java b/app/src/main/java/com/theromus/utils/HexUtils.java new file mode 100644 index 00000000..d1fc9038 --- /dev/null +++ b/app/src/main/java/com/theromus/utils/HexUtils.java @@ -0,0 +1,97 @@ +package com.theromus.utils; + + +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; + +/** + * Hex-utils. + * + * @author romus + */ +public class HexUtils { + + private static final byte[] ENCODE_BYTE_TABLE = { + (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', + (byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' + }; + + /** + * Convert byte array to unsigned array. + * + * @param data byte array + * @return unsigned array + */ + public static int[] convertToUint(final byte[] data) { + int[] converted = new int[data.length]; + for (int i = 0; i < data.length; i++) { + converted[i] = data[i] & 0xFF; + } + + return converted; + } + + /** + * Convert LE to 64-bit value (unsigned long). + * + * @param data data + * @return 64-bit value (unsigned long) + */ + public static BigInteger convertFromLittleEndianTo64(final int[] data) { + BigInteger uLong = new BigInteger("0"); + for (int i = 0; i < 8; i++) { + uLong = uLong.add(new BigInteger(Integer.toString(data[i])).shiftLeft(8 * i)); + } + + return uLong; + } + + /** + * Convert 64-bit (unsigned long) value to LE. + * + * @param uLong 64-bit value (unsigned long) + * @return LE + */ + public static int[] convertFrom64ToLittleEndian(final BigInteger uLong) { + int[] data = new int[8]; + BigInteger mod256 = new BigInteger("256"); + for (int i = 0; i < 8; i++) { + data[i] = uLong.shiftRight((8 * i)).mod(mod256).intValue(); + } + + return data; + } + + /** + * Bitwise rotate left. + * + * @param value unsigned long value + * @param rotate rotate left + * @return result + */ + public static BigInteger leftRotate64(final BigInteger value, final int rotate) { + BigInteger lp = value.shiftRight(64 - (rotate % 64)); + BigInteger rp = value.shiftLeft(rotate % 64); + + return lp.add(rp).mod(new BigInteger("18446744073709551616")); + } + + /** + * Convert bytes to string. + * + * @param data bytes array + * @return string + */ + public static String convertBytesToString(final byte[] data) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + for (int i = 0; i < data.length; i++) { + int uVal = data[i] & 0xFF; + + buffer.write(ENCODE_BYTE_TABLE[(uVal >>> 4)]); + buffer.write(ENCODE_BYTE_TABLE[uVal & 0xF]); + } + + return new String(buffer.toByteArray()); + } + +} diff --git a/app/src/main/res/layout/prompt_ledger_seed.xml b/app/src/main/res/layout/prompt_ledger_seed.xml new file mode 100644 index 00000000..01acef47 --- /dev/null +++ b/app/src/main/res/layout/prompt_ledger_seed.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/create_wallet_seed.xml b/app/src/main/res/menu/create_wallet_seed.xml index f6cc145e..0c89dd49 100644 --- a/app/src/main/res/menu/create_wallet_seed.xml +++ b/app/src/main/res/menu/create_wallet_seed.xml @@ -7,6 +7,13 @@ android:icon="@drawable/ic_help_white_24dp" android:orderInCategory="100" android:title="@string/menu_help" - app:showAsAction="always" /> + app:showAsAction="ifRoom" /> + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 40fd9876..3d0912f9 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -376,4 +376,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 0f7c309c..822115b0 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -375,4 +375,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 7396d020..66e4ad08 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -375,4 +375,10 @@ Tro da mendoj ĈIO! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 77ef302a..fb9cea86 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -365,4 +365,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 36aa87f2..baa8e8f7 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -373,4 +373,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 2f6ced8b..1b0d0c98 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -379,4 +379,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 947e6721..864ae515 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -377,4 +377,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 519ab87d..37a594c2 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -378,4 +378,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 8ba5f191..3ffc104c 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -421,4 +421,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index fbdb010b..39e4420b 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -375,4 +375,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 9ae7b4e5..d872b6ff 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -375,4 +375,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index ad4e4c33..c0904def 100755 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -367,4 +367,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index ac12b1da..95d23f15 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -379,4 +379,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 44f6e6f9..7a5be8f9 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -375,4 +375,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 4a08897a..7272bc21 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -379,4 +379,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 9cfb742e..e2209572 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -377,4 +377,9 @@ VŠETKO! + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 20ad413f..90bee264 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -360,4 +360,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-ua/strings.xml b/app/src/main/res/values-ua/strings.xml index 1091be2b..b7d760a8 100644 --- a/app/src/main/res/values-ua/strings.xml +++ b/app/src/main/res/values-ua/strings.xml @@ -379,4 +379,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c2c7427f..4dbf4d8e 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -373,4 +373,10 @@ Too many requests EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index dedf9905..71a432a1 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -374,4 +374,10 @@ 請求過於頻繁 全部! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (optional) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 99ae8c19..3cc97b49 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -424,4 +424,10 @@ XMR.TO Service Error EVERYTHING! + + Convert Ledger Seed + Ledger Seed Words + Ledger Passphrase (advanced) + Invalid Ledger Seed! + Entering you Ledger Seed here is a major security risk! diff --git a/app/src/test/java/com/m2049r/xmrwallet/util/ledger/MoneroTest.java b/app/src/test/java/com/m2049r/xmrwallet/util/ledger/MoneroTest.java new file mode 100644 index 00000000..5f32fa0b --- /dev/null +++ b/app/src/test/java/com/m2049r/xmrwallet/util/ledger/MoneroTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2019 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.util.ledger; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class MoneroTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void aRealTest() { + String ledgerMnemonic = "weird cloth shiver soda music slight system slender daughter magic design story gospel bulk teach between spice kangaroo inside satoshi convince load morning income"; + String ledgerPassphrase = ""; + String monero_mnemonic = "maverick aimless laptop eating vibrate sensible bugs dreams " + + "journal sincerely renting obtains boss mullet rustled cuddled " + + "goblet nightly jailed hamburger getting benches haggled hesitate laptop"; + String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase); + assertTrue(monero_mnemonic.equals(test_monero)); + } + + @Test + public void bRealTest() { + String ledgerMnemonic = "weird cloth shiver soda music slight system slender daughter magic design story gospel bulk teach between spice kangaroo inside satoshi convince load morning income"; + String ledgerPassphrase = "secret"; + String monero_mnemonic = "surfer hemlock afraid huddle mostly yanks revamp pairing " + + "northern yodel obliged vials azure huddle mowing melting " + + "ruthless subtly civilian midst playful vats nabbing nowhere mowing"; + String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase); + assertTrue(monero_mnemonic.equals(test_monero)); + } + + @Test + public void aTest() { + String ledgerMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + String ledgerPassphrase = ""; + String monero_mnemonic = "tavern judge beyond bifocals deepest mural onward dummy " + + "eagle diode gained vacation rally cause firm idled " + + "jerseys moat vigilant upload bobsled jobs cunning doing jobs"; + String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase); + assertTrue(monero_mnemonic.equals(test_monero)); + } + + @Test + public void bTest() { + String ledgerMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + String ledgerPassphrase = "xyz"; + String monero_mnemonic = "gambit observant swiftly metro hoax pheasants agile oozed " + + "fibula nuns picked stellar nibs cause gained phase " + + "lettuce tomorrow pierce awakened pistons pheasants sorry tedious gambit"; + String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase); + assertTrue(monero_mnemonic.equals(test_monero)); + } + + @Test + public void whitespaceTest() { + String ledgerMnemonic = " abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + String ledgerPassphrase = "xyz"; + String monero_mnemonic = "gambit observant swiftly metro hoax pheasants agile oozed " + + "fibula nuns picked stellar nibs cause gained phase " + + "lettuce tomorrow pierce awakened pistons pheasants sorry tedious gambit"; + String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase); + assertTrue(monero_mnemonic.equals(test_monero)); + } + + @Test + public void caseTest() { + String ledgerMnemonic = "Abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + String ledgerPassphrase = "xyz"; + String monero_mnemonic = "gambit observant swiftly metro hoax pheasants agile oozed " + + "fibula nuns picked stellar nibs cause gained phase " + + "lettuce tomorrow pierce awakened pistons pheasants sorry tedious gambit"; + String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase); + assertTrue(monero_mnemonic.equals(test_monero)); + } + + @Test + public void nullTest() { + String ledgerMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + String ledgerPassphrase = "xyz"; + String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase); + assertNull(test_monero); + } +} diff --git a/build.gradle b/build.gradle index bffdd3c1..3f32129d 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,9 @@ allprojects { maven { url "https://maven.google.com" } + maven { + url "https://repository.mulesoft.org/nexus/content/repositories/public/" + } google() } }