diff --git a/app/src/main/java/com/ravenwallet/presenter/activities/InputWordsActivity.java b/app/src/main/java/com/ravenwallet/presenter/activities/InputWordsActivity.java index 40b53edde..17279a5dd 100644 --- a/app/src/main/java/com/ravenwallet/presenter/activities/InputWordsActivity.java +++ b/app/src/main/java/com/ravenwallet/presenter/activities/InputWordsActivity.java @@ -47,6 +47,7 @@ public class InputWordsActivity extends BRActivity { private EditText word10; private EditText word11; private EditText word12; + private EditText fastRestoreKey; private String debugPhrase; private TextView title; @@ -88,7 +89,9 @@ protected void onCreate(Bundle savedInstanceState) { word10 = (EditText) findViewById(R.id.word10); word11 = (EditText) findViewById(R.id.word11); word12 = (EditText) findViewById(R.id.word12); + fastRestoreKey = (EditText) findViewById(R.id.fast_restore_key); ImageButton faq = (ImageButton) findViewById(R.id.faq_button); + final ImageButton fast_restore_faq = (ImageButton) findViewById(R.id.fast_restore_faq); if (Utils.isUsingCustomInputMethod(this)) { BRDialog.showCustomDialog(this, getString(R.string.JailbreakWarnings_title), getString(R.string.Alert_customKeyboard_android), @@ -115,6 +118,20 @@ public void onClick(View v) { } }); + fast_restore_faq.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!BRAnimator.isClickAllowed()) return; + BRDialog.showCustomDialog(app, app.getString(R.string.RecoverWallet_fastrestore_title), app.getString(R.string.RecoverWallet_fastrestore_explanation), + app.getString(R.string.AccessibilityLabels_close), null, new BRDialogView.BROnClickListener() { + @Override + public void onClick(BRDialogView brDialogView) { + brDialogView.dismissWithAnimation(); + } + }, null, null, 0); + } + }); + FocusListener focusListener = new FocusListener(); word1.setOnFocusChangeListener(focusListener); word2.setOnFocusChangeListener(focusListener); @@ -163,19 +180,22 @@ public void onClick(View v) { if (!BRAnimator.isClickAllowed()) return; final Activity app = InputWordsActivity.this; String phraseToCheck = getPhrase(); + int fastRestoreKey = getFastRestoreKey(); if (Utils.isEmulatorOrDebug(app) && !Utils.isNullOrEmpty(debugPhrase)) { phraseToCheck = debugPhrase; } if (phraseToCheck == null) { return; } + if (fastRestoreKey == -1) { + return; + } String cleanPhrase = SmartValidator.cleanPaperKey(app, phraseToCheck); if (Utils.isNullOrEmpty(cleanPhrase)) { BRReportsManager.reportBug(new NullPointerException("cleanPhrase is null or empty!")); return; } if (SmartValidator.isPaperKeyValid(app, cleanPhrase)) { - if (restore || resetPin) { if (SmartValidator.isPaperKeyCorrect(cleanPhrase, app)) { Utils.hideKeyboard(app); @@ -232,6 +252,10 @@ public void onClick(BRDialogView brDialogView) { m.wipeWalletButKeystore(app); m.wipeKeyStore(app); PostAuth.getInstance().setPhraseForKeyStore(cleanPhrase); + + if(fastRestoreKey > 0) { + BRSharedPrefs.putKnownSeedTime(app, Utils.getSeedTimeFromFastRestoreKey(fastRestoreKey)); + } BRSharedPrefs.putAllowSpend(app, BRSharedPrefs.getCurrentWalletIso(app), false); //if this screen is shown then we did not upgrade to the new app, we installed it BRSharedPrefs.putGreetingsShown(app, true); @@ -341,6 +365,33 @@ private String getPhrase() { return w(w1) + " " + w(w2) + " " + w(w3) + " " + w(w4) + " " + w(w5) + " " + w(w6) + " " + w(w7) + " " + w(w8) + " " + w(w9) + " " + w(w10) + " " + w(w11) + " " + w(w12); } + private int getFastRestoreKey() { + boolean success = true; + + String fastRestoreKeyText = fastRestoreKey.getText().toString().toLowerCase(); + int fastRestoreKeyValue = 0; + + //Optional field, so empty is just 0 + if(Utils.isNullOrEmpty(fastRestoreKeyText)) { + success = true; + } else { + try { + fastRestoreKeyValue = Integer.parseInt(fastRestoreKeyText); + long nowFastRestoreKey = Utils.getCurrentFastRestoreKey(); + if (fastRestoreKeyValue < 0 || fastRestoreKeyValue >= nowFastRestoreKey) { + throw new RuntimeException("Invalid fast restore key"); + } + } catch (Exception ex) { + SpringAnimator.failShakeAnimation(this, fastRestoreKey); + success = false; + } + } + + if (!success) return -1; + + return fastRestoreKeyValue; + } + private String w(String word) { return word.replaceAll(" ", ""); } @@ -358,6 +409,7 @@ private void clearWords() { word10.setText(""); word11.setText(""); word12.setText(""); + fastRestoreKey.setText(""); } @Override diff --git a/app/src/main/java/com/ravenwallet/presenter/activities/PaperKeyActivity.java b/app/src/main/java/com/ravenwallet/presenter/activities/PaperKeyActivity.java index 7d3ddf0aa..ebb7380de 100644 --- a/app/src/main/java/com/ravenwallet/presenter/activities/PaperKeyActivity.java +++ b/app/src/main/java/com/ravenwallet/presenter/activities/PaperKeyActivity.java @@ -6,6 +6,10 @@ import android.os.Bundle; import androidx.legacy.app.FragmentPagerAdapter; import androidx.viewpager.widget.ViewPager; + +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.style.UnderlineSpan; import android.util.SparseArray; import android.util.TypedValue; import android.view.View; @@ -14,6 +18,7 @@ import android.widget.ImageButton; import android.widget.TextView; +import com.google.android.gms.common.util.AndroidUtilsLight; import com.ravenwallet.R; import com.ravenwallet.presenter.activities.util.BRActivity; import com.ravenwallet.presenter.customviews.BRDialogView; @@ -93,9 +98,24 @@ public void onClick(View v) { @Override public void onClick(View v) { updateWordView(false); - } }); + itemIndexText.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!BRAnimator.isClickAllowed()) return; + //Only allow clicking on the final page which has the fast-restore key + if(wordViewPager.getCurrentItem() != 12) return; + BRDialog.showCustomDialog(app, app.getString(R.string.RecoverWallet_fastrestore_title), app.getString(R.string.SecurityCenter_fastrestoreExplanation), + app.getString(R.string.AccessibilityLabels_close), null, new BRDialogView.BROnClickListener() { + @Override + public void onClick(BRDialogView brDialogView) { + brDialogView.dismissWithAnimation(); + } + }, null, null, 0); + } + }); + String cleanPhrase = getIntent().getExtras() == null ? null : getIntent().getStringExtra("phrase"); wordMap = new SparseArray<>(); @@ -120,6 +140,7 @@ public void onClick(BRDialogView brDialogView) { } WordPagerAdapter adapter = new WordPagerAdapter(getFragmentManager()); adapter.setWords(wordArray); + adapter.setFastRestoreKey(Utils.getCurrentFastRestoreKey()); wordViewPager.setAdapter(adapter); for (int i = 0; i < wordArray.length; i++) { wordMap.append(i, wordArray[i]); @@ -133,7 +154,7 @@ private void updateWordView(boolean isNext) { int currentIndex = wordViewPager.getCurrentItem(); if (isNext) { setButtonEnabled(true); - if (currentIndex >= 11) { + if (currentIndex >= wordMap.size()) { PostAuth.getInstance().onPhraseProveAuth(this, false); } else { wordViewPager.setCurrentItem(currentIndex + 1); @@ -170,7 +191,14 @@ protected void onPause() { } private void updateItemIndexText() { - String text = String.format(Locale.getDefault(), getString(R.string.WritePaperPhrase_step), wordViewPager.getCurrentItem() + 1, wordMap.size()); + SpannableString text; + if(wordViewPager.getCurrentItem() == wordMap.size()){ + //Last item: AKA the fast restore key, Underline it to show it is clickable + text = new SpannableString(String.format(Locale.getDefault(), getString(R.string.SecurityCenter_fastrestoreDetails))); + text.setSpan(new UnderlineSpan(), 0, text.length(), 0); + } else { + text = new SpannableString(String.format(Locale.getDefault(), getString(R.string.WritePaperPhrase_step), wordViewPager.getCurrentItem() + 1, wordMap.size())); + } itemIndexText.setText(text); } @@ -184,6 +212,7 @@ public void onBackPressed() { private class WordPagerAdapter extends FragmentPagerAdapter { private String[] words; + private int fastRestoreKey = -1; public WordPagerAdapter(FragmentManager fm) { super(fm); @@ -192,15 +221,22 @@ public WordPagerAdapter(FragmentManager fm) { public void setWords(String[] words) { this.words = words; } + public void setFastRestoreKey(int fastRestoreKey) { this.fastRestoreKey = fastRestoreKey; } @Override public Fragment getItem(int pos) { - return FragmentPhraseWord.newInstance(words[pos]); + if(pos < words.length) + return FragmentPhraseWord.newInstance(words[pos]); + else + return FragmentPhraseWord.newInstance(Integer.toString(fastRestoreKey)); } @Override public int getCount() { - return words == null ? 0 : words.length; + if(words == null) + return 0; + else + return words.length + (fastRestoreKey == -1 ? 0 : 1); } } diff --git a/app/src/main/java/com/ravenwallet/tools/manager/BRSharedPrefs.java b/app/src/main/java/com/ravenwallet/tools/manager/BRSharedPrefs.java index 4305273d6..68dde1b0b 100644 --- a/app/src/main/java/com/ravenwallet/tools/manager/BRSharedPrefs.java +++ b/app/src/main/java/com/ravenwallet/tools/manager/BRSharedPrefs.java @@ -49,6 +49,7 @@ public class BRSharedPrefs { private static final String CURRENT_WALLET_CURRENCY_CODE = "currentWalletIso"; private static final String LAST_RESCAN_MODE_USED = "lastRescanModeUsed_RVN"; private static final String LAST_SEND_TRANSACTION_BLOCK_HEIGHT = "lastSendTransactionBlockheight_RVN"; + private static final String KNOWN_SEED_TIME = "knownSeedTime_RVN"; private static final String RESCAN_TIME = "rescanTime_RVN"; public interface OnIsoChangedListener { @@ -278,6 +279,18 @@ public static void putLastSendTransactionBlockheight(Context activity, long bloc editor.apply(); } + public static long getKnownSeedTime(Context activity) { + SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + return prefs.getLong(KNOWN_SEED_TIME, 0); + } + + public static void putKnownSeedTime(Context activity, long seedTime) { + SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putLong(KNOWN_SEED_TIME, seedTime); + editor.apply(); + } + public static long getFeeTime(Context activity, String iso) { SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); return prefs.getLong("feeTime_" + iso.toUpperCase(), 0); diff --git a/app/src/main/java/com/ravenwallet/tools/security/PostAuth.java b/app/src/main/java/com/ravenwallet/tools/security/PostAuth.java index bc42c3775..345493a3f 100644 --- a/app/src/main/java/com/ravenwallet/tools/security/PostAuth.java +++ b/app/src/main/java/com/ravenwallet/tools/security/PostAuth.java @@ -59,6 +59,9 @@ public void onCreateWalletAuth(Activity app, boolean authAsked) { long start = System.currentTimeMillis(); boolean success = WalletsMaster.getInstance(app).generateRandomSeed(app); if (success) { + //initWallets() will immediately begin sync, we must set known timestamp first + //a brand new wallet can always use the latest time, since it will start from a checkpoint anyway + BRSharedPrefs.putKnownSeedTime(app, Utils.getCurrentUnixTimestamp()); WalletsMaster.getInstance(app).initWallets(app); Intent intent = new Intent(app, WriteDownActivity.class); app.startActivity(intent); diff --git a/app/src/main/java/com/ravenwallet/tools/services/SyncService.java b/app/src/main/java/com/ravenwallet/tools/services/SyncService.java index eda9ceab1..35500890a 100644 --- a/app/src/main/java/com/ravenwallet/tools/services/SyncService.java +++ b/app/src/main/java/com/ravenwallet/tools/services/SyncService.java @@ -155,9 +155,9 @@ public static void startService(Context context, String currencyCode) { private void startSyncPolling(Context context, String walletIso) { final BaseWalletManager walletManager = WalletsMaster.getInstance(context).getWalletByIso(context, walletIso); if (walletManager == null) return; - final double progress = walletManager.getSyncProgress(BRSharedPrefs.getStartHeight(context, - BRSharedPrefs.getCurrentWalletIso(context))); - Log.e(TAG, "startSyncPolling: Progress:" + progress + " Wallet: " + walletIso); + long currentHeight = BRSharedPrefs.getStartHeight(context,BRSharedPrefs.getCurrentWalletIso(context)); + final double progress = walletManager.getSyncProgress(currentHeight); + Log.e(TAG, "startSyncPolling: Height: " + currentHeight + " Progress:" + progress + " Wallet: " + walletIso); if (progress > PROGRESS_START && progress < PROGRESS_FINISH) { AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); diff --git a/app/src/main/java/com/ravenwallet/tools/util/BRConstants.java b/app/src/main/java/com/ravenwallet/tools/util/BRConstants.java index bbf350feb..4bb93ff6b 100644 --- a/app/src/main/java/com/ravenwallet/tools/util/BRConstants.java +++ b/app/src/main/java/com/ravenwallet/tools/util/BRConstants.java @@ -105,6 +105,10 @@ public class BRConstants { public static final long UNIQUE_FEE = 5L; public static final long CONFIRMS_COUNT = 5L; + //The Genesis time as specified by RIP-20 + public static final long GENESIS_TIMESTAMP = 1514962800; + public static final long FAST_SYNC_INTERVAL_SECONDS = 60 * 60 * 24 * 7; + /** * Support Center article ids. */ diff --git a/app/src/main/java/com/ravenwallet/tools/util/Utils.java b/app/src/main/java/com/ravenwallet/tools/util/Utils.java index 015b5c133..7e14bfd3f 100644 --- a/app/src/main/java/com/ravenwallet/tools/util/Utils.java +++ b/app/src/main/java/com/ravenwallet/tools/util/Utils.java @@ -226,6 +226,19 @@ public static String retrieveAddressChunk(String scanResult) { return null; } + public static long getCurrentUnixTimestamp() { + return System.currentTimeMillis() / 1000; + } + + //Integer division will always effectively floor the results here + public static int getCurrentFastRestoreKey() { + return (int)((getCurrentUnixTimestamp() - BRConstants.GENESIS_TIMESTAMP) / BRConstants.FAST_SYNC_INTERVAL_SECONDS); + } + + public static long getSeedTimeFromFastRestoreKey(int fastRestoreKey) { + return (((long)fastRestoreKey) * BRConstants.FAST_SYNC_INTERVAL_SECONDS) + BRConstants.GENESIS_TIMESTAMP; + } + public static String getIpfsUrlFromHash(Context app, String hash) { //TODO: Hash Validation? return String.format(BRConstants.IPFS_URL_FORMAT, BRSharedPrefs.getPreferredIPFSGateway(app), hash); diff --git a/app/src/main/java/com/ravenwallet/wallet/RvnWalletManager.java b/app/src/main/java/com/ravenwallet/wallet/RvnWalletManager.java index 92d7cd59f..1d21de052 100644 --- a/app/src/main/java/com/ravenwallet/wallet/RvnWalletManager.java +++ b/app/src/main/java/com/ravenwallet/wallet/RvnWalletManager.java @@ -135,6 +135,24 @@ public synchronized static RvnWalletManager getInstance(Context app) { // long time = 1519190488; // int time = (int) (System.currentTimeMillis() / DateUtils.SECOND_IN_MILLIS); long time = BRKeyStore.getWalletCreationTime(app); + if(time == 0 && BRSharedPrefs.getKnownSeedTime(app) > 0) { + //This case happens when we are a brand new restore and we have a know seed time. + //The seed time (based on week, so somewhat in-accurate) is converted to a rough block number + //The oldest checkpoint (every 4 block-months) before 'time' is used as a starting point + //The estimated height is used to determine the point at which to start downloading full blocks + long seedTime = BRSharedPrefs.getKnownSeedTime(app); + long currentTime = System.currentTimeMillis() / 1000; + + if(seedTime > 0 && seedTime < currentTime){ + time = seedTime; + Log.d(TAG, "getInstance: resuming from known block time: " + time); + long estimatedHeight = (time - BRConstants.GENESIS_TIMESTAMP) / (60); + BRSharedPrefs.putStartHeight(app, BRSharedPrefs.getCurrentWalletIso(app), estimatedHeight); + } else { + //Invalid timestamp used. Ignore + time = 0; + } + } instance = new RvnWalletManager(app, pubKey, BuildConfig.TESTNET ? BRCoreChainParams.testnetChainParams : BRCoreChainParams.mainnetChainParams, time); } @@ -162,13 +180,18 @@ private RvnWalletManager(final Context app, BRCoreMasterPubKey masterPubKey, BREventManager.getInstance().pushEvent("wallet.didUseDefaultFeePerKB"); } getWallet().setFeePerKb(BRSharedPrefs.getFavorStandardFee(app, getIso(app)) ? fee : economyFee); - if (BRSharedPrefs.getStartHeight(app, getIso(app)) == 0) + if (BRSharedPrefs.getStartHeight(app, getIso(app)) == 0) { BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { @Override public void run() { - BRSharedPrefs.putStartHeight(app, getIso(app), getPeerManager().getLastBlockHeight()); + long lastBlockHeight = getPeerManager().getLastBlockHeight(); + Log.d(TAG, "connectWallet: starting from last block height " + lastBlockHeight); + BRSharedPrefs.putStartHeight(app, getIso(app), lastBlockHeight); } }); + } else { + Log.d(TAG, "connectWallet: starting from block " + BRSharedPrefs.getStartHeight(app, getIso(app))); + } WalletsMaster.getInstance(app).updateFixedPeer(app, this); // balanceListeners = new ArrayList<>(); @@ -802,7 +825,10 @@ public void syncStopped(final String error) { BRExecutor.getInstance().forMainThreadTasks().execute(new Runnable() { @Override public void run() { - Toast.makeText(app, "SyncStopped " + getIso(app) + " err(" + error + ") ", Toast.LENGTH_LONG).show(); + if (Utils.isNullOrEmpty(error)) + Toast.makeText(app, "SyncStopped " + getIso(app), Toast.LENGTH_LONG).show(); + else + Toast.makeText(app, "SyncStopped " + getIso(app) + " err(" + error + ") ", Toast.LENGTH_LONG).show(); } }); diff --git a/app/src/main/jni/core/BRPeerManager.c b/app/src/main/jni/core/BRPeerManager.c index 8f05ad960..5c8c645b5 100755 --- a/app/src/main/jni/core/BRPeerManager.c +++ b/app/src/main/jni/core/BRPeerManager.c @@ -71,15 +71,27 @@ static const struct { uint32_t height; const char *hash; uint32_t timestamp; uin { 340704, "000000000001c5e10e9e94b761464548efd2bbcf22509ac6bfec35757da58687", 1535711279, 0x1b024145 }, { 673344, "00000000000041bbd71687002a65c4dac458cb8a775e7ca094d623ac50300979", 1555813777, 0x1a62c6e4 }, { 844704, "0000000000006361abddc32b21bef2bf1df669f731ec18a13adbd426cced17b6", 1566164045, 0x1a78e5ab }, - { 899136, "00000000000015d1f71d3fac92e98d3a01f7ec310c10e90a966dcf1b64369239", 1569424946, 0x1a379754 }, - { 322560, "000000000001d50eaf12266c6ecaefec473fecd9daa7993db05b89e6ab381388", 1533209846, 0x1b04cb9e }, - { 340704, "000000000001c5e10e9e94b761464548efd2bbcf22509ac6bfec35757da58687", 1535711279, 0x1b024145 }, - { 673344, "00000000000041bbd71687002a65c4dac458cb8a775e7ca094d623ac50300979", 1555813777, 0x1a62c6e4 }, - { 844704, "0000000000006361abddc32b21bef2bf1df669f731ec18a13adbd426cced17b6", 1566164045, 0x1a78e5ab }, - { 899136, "00000000000015d1f71d3fac92e98d3a01f7ec310c10e90a966dcf1b64369239", 1569424946, 0x1a379754 }, - { 1196000, "0000000000000636f7177046a677203ea191d540b1aefdff63fd43eec538dd3b", 1587354658, 0x1a254bfb } + { 899136, "00000000000015d1f71d3fac92e98d3a01f7ec310c10e90a966dcf1b64369239", 1569424946, 0x1a379754 }, //Sep 15 2019 + { 1196000, "0000000000000636f7177046a677203ea191d540b1aefdff63fd43eec538dd3b", 1587354658, 0x1a254bfb }, //Apr 19 2020 + //I'm not sure if there is a major problem with checkpoints each month (40,320 blocks) + //Mostly doing it just to give variable-start wallet restore plenty of times to work with + { 1216160, "00000000000020daebec36516ef5f867b0fccce33e68646e7652f80ea96186e7", 1588572488, 0x1a259da6 }, //May 4 2020 + { 1256480, "000000000000e62500d1e41ee8c468d626ccf475eea3f291f76c142560a3ad83", 1590977350, 0x1b016954 }, //May 31 2020 + { 1296800, "000000000001846f32ad426f8a7576bd49b06861aa6cd506cb0dbc9cf105cd85", 1593411973, 0x1b018cff }, //Jun 29 2020 + { 1337120, "0000000000013b6afd6fa8c4cd57143b33138d648bba5b9aefc4be2381611a50", 1595848826, 0x1b02425d }, //Jul 27 2020 + { 1377440, "0000000000017dce6b055831e2eec4a62d529e870d0fc9a7ff4f896a18786c8f", 1598283070, 0x1b01fc40 }, //Aug 24 2020 + { 1417760, "000000000000cc0ad4fc9c2adc673f57bbd09812e1f5f3fda2b2e3d0eb1c5a93", 1600720000, 0x1b02927b }, //Sep 21 2020 + { 1458080, "0000000000002668ed1ffdd0bf1982a48d637a9175c8b030f1d5e300a4a0b029", 1603153479, 0x1b023835 }, //Oct 20 2020 + { 1498400, "00000000000036f29125709aced6507829b96138ea6c1e07bdfbed2f703fffb8", 1605588946, 0x1b025e47 }, //Nov 17 2020 + { 1538720, "000000000000d8755b12242895c515c93f7cfe8094bcb85138954728e7f5dc85", 1608024058, 0x1b02df01 }, //Dec 15 2020 + { 1579040, "000000000001b776ee39f65d67c8d4cfa2f200c359a9b5787c85f31503bb0fab", 1610459367, 0x1b02be6f }, //Jan 12 2021 + { 1619360, "000000000001cb5fde4bb9c89b5c0060a1903ae2ba5f170d7459022ef59965a6", 1612893283, 0x1b023b77 }, //Feb 9 2021 + { 1659680, "000000000000258aa2b5bf5d0003b5717d066303819553b64296e935154773c4", 1615320605, 0x1b0086e1 }, //Mar 9 2021 + //{1700000}, //Almost.... }; +//1 week = 10,080, 4 weeks = 40,320 + static const char *dns_seeds[] = { // "127.0.0.1", NULL "seed-raven.ravencoin.com", "seed-raven.ravencoin.org.", "seed-raven.bitactivate.com.", NULL diff --git a/app/src/main/res/layout/activity_input_words.xml b/app/src/main/res/layout/activity_input_words.xml index cf9398cd2..689a2ccbc 100644 --- a/app/src/main/res/layout/activity_input_words.xml +++ b/app/src/main/res/layout/activity_input_words.xml @@ -106,6 +106,7 @@ app:customTFont="CircularPro-Bold.otf" /> @@ -543,12 +545,52 @@ + + + + + + Reset PIN Enter Paper Key + + Fast-Restore Key (Optional) + + The Fast-Restore Key is an optional feature, used to speed up synchronization of newly installed clients for both new and restored wallets.\n\nUsing this will skip downloading blocks that happened before your wallet was created, rounded to the nearest hard-coded checkpoint (roughly monthly).\n\nIf you don\'t know your code, leave this blank and you will just download from the start. Recover your Ravencoin Wallet with your paper key. @@ -330,6 +334,10 @@ The only way to access your bitcoin if you lose or upgrade your phone. Paper Key + + Fast-Restore Key (Click for Details) + + The Fast-Restore Key is an optional feature, used to speed up synchronization of newly installed clients for both new and restored wallets.\n\nUsing this will skip downloading blocks that happened before your wallet was created, rounded to the nearest hard-coded checkpoint (roughly monthly). Protects your Ravencoin Wallet from unauthorized users.