diff --git a/.circleci/config.yml b/.circleci/config.yml index 05dca093a..0effc1b33 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: circleci/node:10.16.3 + - image: circleci/node:10.24.1 working_directory: ~/repo diff --git a/.eslintrc b/.eslintrc index 689b2ff8b..b53de32e0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,19 +1,22 @@ { - "parser": "babel-eslint", + "parser": "@typescript-eslint/parser", "plugins": [ - "react-native", // for no-inline-styles rule + "@typescript-eslint", + "react-native" // for no-inline-styles rule ], "extends": [ "standard", "standard-react", + "standard-jsx", "plugin:react-hooks/recommended", - // "@react-native-community", - "plugin:prettier/recommended", - "prettier/react", - "prettier/standard", + "plugin:react/recommended", + "plugin:@typescript-eslint/recommended", + // "@react-native-community", // TODO: try to enable + "plugin:prettier/recommended" // removes all eslint rules that can mess up with prettier ], "rules": { "react/jsx-handler-names": "off", // activated by standard-react config + "react/display-name": "off", "react-native/no-inline-styles": "error", "prettier/prettier": [ "warn", @@ -23,9 +26,40 @@ "trailingComma": "all", "arrowParens": "avoid" } - ] + ], + "@typescript-eslint/no-empty-function": "off", // used often in the codebase, useful e.g. in testing + "@typescript-eslint/ban-ts-comment": [ + "error", + { + "ts-expect-error": "allow-with-description", + "ts-ignore": "allow-with-description", // temporary allow to ease the migration + "ts-nocheck": true, + "ts-check": false + } + ], + "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], + + // disable rules that are superseded by @typescript-eslint rules + "no-unused-vars": "off", + "no-use-before-define": "off", + + // disable rules that we want to enforce only for typescript files + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/no-use-before-define": "off" }, - "env":{ + "overrides": [ + { + // enable the rule specifically for TypeScript files + "files": ["*.ts", "*.tsx"], + "rules": { + "@typescript-eslint/explicit-module-boundary-types": ["error"], + "@typescript-eslint/no-var-requires": ["error"], + "@typescript-eslint/no-use-before-define": ["error", { "variables": false }] + } + } + ], + "env": { "es6": true }, "globals": { "fetch": false } diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index 4bead39d6..000000000 --- a/.flowconfig +++ /dev/null @@ -1,75 +0,0 @@ -[ignore] -; We fork some components by platform -.*/*[.]android.js - -; Ignore "BUCK" generated dirs -/\.buckd/ - -; Ignore polyfills -node_modules/react-native/Libraries/polyfills/.* - -; These should not be required directly -; require from fbjs/lib instead: require('fbjs/lib/warning') -node_modules/warning/.* - -; Flow doesn't support platforms -.*/Libraries/Utilities/LoadingView.js - -[untyped] -.*/node_modules/@react-native-community/cli/.*/.* - -[include] - -[libs] -node_modules/react-native/interface.js -node_modules/react-native/flow/ - -[options] -emoji=true - -esproposal.optional_chaining=enable -esproposal.nullish_coalescing=enable - -module.file_ext=.js -module.file_ext=.json -module.file_ext=.ios.js - -munge_underscores=true - -module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1' -module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' - -suppress_type=$FlowIssue -suppress_type=$FlowFixMe -suppress_type=$FlowFixMeProps -suppress_type=$FlowFixMeState - -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError - -[lints] -sketchy-null-number=warn -sketchy-null-mixed=warn -sketchy-number=warn -untyped-type-import=warn -nonstrict-import=warn -deprecated-type=warn -unsafe-getters-setters=warn -inexact-spread=warn -unnecessary-invariant=warn -signature-verification-failure=warn -deprecated-utility=error - -[strict] -deprecated-type -nonstrict-import -sketchy-null -unclear-type -unsafe-getters-setters -untyped-import -untyped-type-import - -[version] -^0.122.0 - \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index 7c669b1f5..d6149af24 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ *.pbxproj -text *.patch -text -# specific for windows script files +# Windows files should use crlf line endings +# https://help.github.com/articles/dealing-with-line-endings/ *.bat text eol=crlf \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/issue-template.md b/.github/ISSUE_TEMPLATE/issue-template.md new file mode 100644 index 000000000..d89b00f94 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue-template.md @@ -0,0 +1,23 @@ +--- +name: Issue template +about: general issue template +title: '' +labels: '' +assignees: '' + +--- + +## Do you need support? Just email bluewallet@bluewallet.io + +## Are you reporting a bug? + +Please provide: + +* your phone model and OS version +* BlueWallet app version (settings->about->scroll down) +* self-test passes? Open settings->about->scroll down, tap "Run self-test" +* unique ID for our crash reporting service (settings->about->scroll down, tap "copy") + +## Proposing a feature? + +Go right ahead diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6fc791ef..edb578971 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: [pull_request] jobs: test: - runs-on: macos-latest + runs-on: macos-10.15 # tmp fix for https://github.com/ReactiveCircus/android-emulator-runner/issues/160 steps: - name: Checkout project uses: actions/checkout@v2 @@ -44,6 +44,9 @@ jobs: HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }} HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }} HD_MNEMONIC_BREAD: ${{ secrets.HD_MNEMONIC_BREAD }} + FAULTY_ZPUB: ${{ secrets.FAULTY_ZPUB }} + MNEMONICS_COBO: ${{ secrets.MNEMONICS_COBO }} + MNEMONICS_COLDCARD: ${{ secrets.MNEMONICS_COLDCARD }} e2e: runs-on: macos-latest @@ -89,6 +92,7 @@ jobs: uses: reactivecircus/android-emulator-runner@v2 with: api-level: 29 + emulator-build: 6110076 # tmp fix for https://github.com/ReactiveCircus/android-emulator-runner/issues/160 target: google_apis avd-name: Pixel_API_29_AOSP emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim -camera-back none -camera-front none diff --git a/.github/workflows/pull-request-reviewer-reminder.yml b/.github/workflows/pull-request-reviewer-reminder.yml deleted file mode 100644 index 6e0faa464..000000000 --- a/.github/workflows/pull-request-reviewer-reminder.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: 'Pull request reviewer reminder' -on: - schedule: - # Check reviews every weekday, 10:00 and 17:00 - - cron: '0 10,17 * * 1-5' - -jobs: - pull-request-reviewer-reminder: - runs-on: ubuntu-latest - steps: - - uses: tommykw/pull-request-reviewer-reminder-action@v1 - with: - GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" - reminder_message: 'One business day has passed since the review started. Give priority to reviews as much as possible.' # Required. Messages to send to reviewers on Github. - review_turnaround_hours: 24 # Required. This is the deadline for reviews. If this time is exceeded, a reminder wil be send. \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index a4d9f4f1f..ac7f7ca07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -90,5 +90,3 @@ script: - npm i -g detox-cli - npm run e2e:release-build - npm run e2e:release-test || npm run e2e:release-test || npm run e2e:release-test - -after_failure: ./tests/e2e/upload-artifacts.sh diff --git a/App.js b/App.js index e7cb32709..7229c67c7 100644 --- a/App.js +++ b/App.js @@ -21,8 +21,6 @@ import * as NavigationService from './NavigationService'; import { BlueTextCentered, BlueButton, SecondButton } from './BlueComponents'; import ReactNativeHapticFeedback from 'react-native-haptic-feedback'; import { Chain } from './models/bitcoinUnits'; -import QuickActions from 'react-native-quick-actions'; -import * as Sentry from '@sentry/react-native'; import OnAppLaunch from './class/on-app-launch'; import DeeplinkSchemaMatch from './class/deeplink-schema-match'; import loc from './loc'; @@ -30,6 +28,7 @@ import { BlueDefaultTheme, BlueDarkTheme, BlueCurrentTheme } from './components/ import BottomModal from './components/BottomModal'; import InitRoot from './Navigation'; import BlueClipboard from './blue_modules/clipboard'; +import { isDesktop } from './blue_modules/environment'; import { BlueStorageContext } from './blue_modules/storage-context'; import WatchConnectivity from './WatchConnectivity'; import DeviceQuickActions from './class/quick-actions'; @@ -42,12 +41,6 @@ const A = require('./blue_modules/analytics'); const eventEmitter = new NativeEventEmitter(NativeModules.EventEmitter); -if (process.env.NODE_ENV !== 'development') { - Sentry.init({ - dsn: 'https://23377936131848ca8003448a893cb622@sentry.io/1295736', - }); -} - const ClipboardContentType = Object.freeze({ BITCOIN: 'BITCOIN', LIGHTNING: 'LIGHTNING', @@ -125,7 +118,7 @@ const App = () => { Linking.addEventListener('url', handleOpenURL); AppState.addEventListener('change', handleAppStateChange); DeviceEventEmitter.addListener('quickActionShortcut', walletQuickActions); - QuickActions.popInitialAction().then(popInitialAction); + DeviceQuickActions.popInitialAction().then(popInitialAction); handleAppStateChange(undefined); /* When a notification on iOS is shown while the app is on foreground; @@ -343,8 +336,8 @@ const App = () => { {renderClipboardContentModal()} + {walletsInitialized && !isDesktop && } - diff --git a/BlueApp.js b/BlueApp.js index 8a5e53e7e..acdce2a0a 100644 --- a/BlueApp.js +++ b/BlueApp.js @@ -4,8 +4,8 @@ import { Platform } from 'react-native'; import loc from './loc'; const prompt = require('./blue_modules/prompt'); const currency = require('./blue_modules/currency'); -const BlueElectrum = require('./blue_modules/BlueElectrum'); // eslint-disable-line no-unused-vars -const BlueApp: AppStorage = new AppStorage(); +const BlueElectrum = require('./blue_modules/BlueElectrum'); // eslint-disable-line @typescript-eslint/no-unused-vars +const BlueApp = new AppStorage(); // If attempt reaches 10, a wipe keychain option will be provided to the user. let unlockAttempt = 0; @@ -15,6 +15,7 @@ const startAndDecrypt = async retry => { console.log('App already has some wallets, so we are in already started state, exiting startAndDecrypt'); return true; } + await BlueApp.migrateKeys(); let password = false; if (await BlueApp.storageIsEncrypted()) { do { @@ -28,7 +29,7 @@ const startAndDecrypt = async retry => { } catch (error) { // in case of exception reading from keystore, lets retry instead of assuming there is no storage and // proceeding with no wallets - console.warn(error); + console.warn('exception loading from disk:', error); wasException = true; } @@ -37,7 +38,9 @@ const startAndDecrypt = async retry => { try { await new Promise(resolve => setTimeout(resolve, 3000)); // sleep success = await BlueApp.loadFromDisk(password); - } catch (_) {} + } catch (error) { + console.warn('second exception loading from disk:', error); + } } if (success) { diff --git a/BlueComponents.js b/BlueComponents.js index 620ce51bd..c7aa3bb22 100644 --- a/BlueComponents.js +++ b/BlueComponents.js @@ -34,12 +34,12 @@ import WalletGradient from './class/wallet-gradient'; import { BlurView } from '@react-native-community/blur'; import NetworkTransactionFees, { NetworkTransactionFee, NetworkTransactionFeeType } from './models/networkTransactionFees'; import Biometric from './class/biometrics'; -import { encodeUR } from 'bc-ur/dist'; +import { encodeUR } from './blue_modules/ur'; import QRCode from 'react-native-qrcode-svg'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useNavigation, useTheme } from '@react-navigation/native'; import { BlueCurrentTheme } from './components/themes'; -import loc, { formatBalance, formatBalanceWithoutSuffix, transactionTimeToReadable } from './loc'; +import loc, { formatBalance, formatStringAddTwoWhiteSpaces, formatBalanceWithoutSuffix, transactionTimeToReadable } from './loc'; import Lnurl from './class/lnurl'; import { BlueStorageContext } from './blue_modules/storage-context'; import ToolTipMenu from './components/TooltipMenu'; @@ -80,6 +80,7 @@ export const BlueButton = props => { alignItems: 'center', paddingHorizontal: 16, }} + accessibilityRole="button" {...props} > @@ -101,6 +102,7 @@ export const SecondButton = forwardRef((props, ref) => { return ( { export const BitcoinButton = props => { const { colors } = useTheme(); return ( - + { - {loc.wallets.add_bitcoin} - {loc.wallets.add_bitcoin_explain} + + {loc.wallets.add_bitcoin} + + + {loc.wallets.add_bitcoin_explain} + @@ -158,7 +171,7 @@ export const BitcoinButton = props => { export const VaultButton = props => { const { colors } = useTheme(); return ( - + { - {loc.multisig.multisig_vault} - + + {loc.multisig.multisig_vault} + + {loc.multisig.multisig_vault_explain} @@ -190,7 +219,7 @@ export const VaultButton = props => { export const LightningButton = props => { const { colors } = useTheme(); return ( - + { - {loc.wallets.add_lightning} - {loc.wallets.add_lightning_explain} + + {loc.wallets.add_lightning} + + + {loc.wallets.add_lightning_explain} + @@ -383,6 +425,7 @@ export class BlueWalletNavigationHeader extends Component { } /> {this.state.wallet.type === LightningCustodianWallet.type && this.state.allowOnchainAddress && ( - + )} {this.state.wallet.type === MultisigHDWallet.type && ( - + } + */ export const BlueButtonLink = forwardRef((props, ref) => { const { colors } = useTheme(); return ( { export const BlueCopyToClipboardButton = ({ stringToCopy, displayText = false }) => { return ( - Clipboard.setString(stringToCopy)}> + Clipboard.setString(stringToCopy)}> {displayText || loc.transactions.details_copy} ); @@ -559,8 +608,13 @@ export class BlueCopyTextToClipboard extends Component { render() { return ( - - + + {this.state.address} @@ -596,7 +650,9 @@ export const BlueText = props => { {...props} style={{ color: colors.foregroundColor, + ...props.style, + writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', }} /> ); @@ -670,7 +726,17 @@ export const BlueListItem = React.memo(props => { export const BlueFormLabel = props => { const { colors } = useTheme(); - return ; + return ( + + ); }; export const BlueFormInput = props => { @@ -770,6 +836,7 @@ export const BlueHeaderDefaultSub = props => { export const BlueHeaderDefaultMain = props => { const { colors } = useTheme(); const { isDrawerList } = props; + const { isImportingWallet } = useContext(BlueStorageContext); return (
{ bottomDivider={false} topDivider={false} backgroundColor={isDrawerList ? colors.elevated : colors.background} - rightComponent={} + rightComponent={isImportingWallet ? undefined : } /> ); }; @@ -1141,7 +1208,7 @@ export const BlueReceiveButtonIcon = props => { const { colors } = useTheme(); return ( - + { backgroundColor: 'transparent', }} > - {loc.receive.header} + {formatStringAddTwoWhiteSpaces(loc.receive.header)} @@ -1349,13 +1416,7 @@ export const BlueTransactionListItem = React.memo(({ item, itemPriceUnit = Bitco if (item.hash) { navigate('TransactionStatus', { hash: item.hash }); } else if (item.type === 'user_invoice' || item.type === 'payment_request' || item.type === 'paid_invoice') { - const lightningWallet = wallets.filter(wallet => { - if (typeof wallet === 'object') { - if ('secret' in wallet) { - return wallet.getSecret() === item.fromWallet; - } - } - }); + const lightningWallet = wallets.filter(wallet => wallet?.getID() === item.walletID); if (lightningWallet.length === 1) { try { // is it a successful lnurl-pay? @@ -1567,7 +1628,7 @@ export class BlueReplaceFeeSuggestions extends Component { active: selectedFeeType === NetworkTransactionFeeType.FAST, }, { - label: loc.send.fee_medium, + label: formatStringAddTwoWhiteSpaces(loc.send.fee_medium), time: loc.send.fee_3h, type: NetworkTransactionFeeType.MEDIUM, rate: networkFees.mediumFee, @@ -1582,6 +1643,7 @@ export class BlueReplaceFeeSuggestions extends Component { }, ].map(({ label, type, time, rate, active }, index) => ( this.onFeeSelected(type)} style={[ @@ -1608,6 +1670,7 @@ export class BlueReplaceFeeSuggestions extends Component { ))} this.customTextInput.focus()} style={[ { paddingHorizontal: 16, paddingVertical: 8, marginBottom: 10 }, @@ -1618,7 +1681,9 @@ export class BlueReplaceFeeSuggestions extends Component { ]} > - {loc.send.fee_custom} + + {formatStringAddTwoWhiteSpaces(loc.send.fee_custom)} + ( {tabs.map((Tab, i) => ( onSwitch(i)} style={[ tabsStyles.tabRoot, @@ -1813,18 +1879,21 @@ export class DynamicQRCode extends Component { {loc.send.dynamic_prev} {this.state.intervalHandler ? loc.send.dynamic_stop : loc.send.dynamic_start} diff --git a/Navigation.js b/Navigation.js index 4c250e91d..68b197ef6 100644 --- a/Navigation.js +++ b/Navigation.js @@ -80,7 +80,7 @@ import LnurlPay from './screen/lnd/lnurlPay'; import LnurlPaySuccess from './screen/lnd/lnurlPaySuccess'; import UnlockWith from './UnlockWith'; import DrawerList from './screen/wallets/drawerList'; -import { isCatalyst, isTablet } from './blue_modules/environment'; +import { isDesktop, isTablet } from './blue_modules/environment'; import SettingsPrivacy from './screen/settings/SettingsPrivacy'; import LNDViewAdditionalInvoicePreImage from './screen/lnd/lndViewAdditionalInvoicePreImage'; @@ -89,7 +89,6 @@ const defaultScreenOptions = ? ({ route, navigation }) => ({ gestureEnabled: true, cardOverlayEnabled: true, - cardStyle: { backgroundColor: '#FFFFFF' }, headerStatusBarHeight: navigation.dangerouslyGetState().routes.indexOf(route) > 0 ? 10 : undefined, ...TransitionPresets.ModalPresentationIOS, gestureResponseDistance: { vertical: Dimensions.get('window').height, horizontal: 50 }, @@ -104,7 +103,6 @@ const defaultStackScreenOptions = ? { gestureEnabled: true, cardOverlayEnabled: true, - cardStyle: { backgroundColor: '#FFFFFF' }, headerStatusBarHeight: 10, } : { @@ -349,7 +347,7 @@ const Drawer = createDrawerNavigator(); function DrawerRoot() { const dimensions = useWindowDimensions(); const isLargeScreen = - Platform.OS === 'android' ? isTablet() : dimensions.width >= Dimensions.get('screen').width / 2 && (isTablet() || isCatalyst); + Platform.OS === 'android' ? isTablet() : dimensions.width >= Dimensions.get('screen').width / 2 && (isTablet() || isDesktop); const drawerStyle = { width: '0%' }; return ( diff --git a/README.md b/README.md index 3ef0db295..f1a3fa39e 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Built with React Native and Electrum. [![Appstore](https://bluewallet.io/uploads/app-store-badge-blue.svg)](https://itunes.apple.com/us/app/bluewallet-bitcoin-wallet/id1376878040?l=ru&ls=1&mt=8) [![Playstore](https://bluewallet.io/uploads/play-store-badge-blue.svg)](https://play.google.com/store/apps/details?id=io.bluewallet.bluewallet) -Website: [bluewallet.io](http://bluewallet.io) +Website: [bluewallet.io](https://bluewallet.io) Community: [telegram group](https://t.me/bluewallet) diff --git a/UnlockWith.js b/UnlockWith.js index adf93c014..8b1d3de4c 100644 --- a/UnlockWith.js +++ b/UnlockWith.js @@ -97,13 +97,13 @@ const UnlockWith = () => { const color = colorScheme === 'dark' ? '#FFFFFF' : '#000000'; if ((biometricType === Biometric.TouchID || biometricType === Biometric.Biometrics) && !isStorageEncryptedEnabled) { return ( - + ); } else if (biometricType === Biometric.FaceID && !isStorageEncryptedEnabled) { return ( - + { ); } else if (isStorageEncryptedEnabled) { return ( - + ); diff --git a/WatchConnectivity.ios.js b/WatchConnectivity.ios.js index 5a21ad043..1ed1c29c5 100644 --- a/WatchConnectivity.ios.js +++ b/WatchConnectivity.ios.js @@ -45,6 +45,10 @@ function WatchConnectivity() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [walletsInitialized, wallets, isReachable, isInstalled]); + useEffect(() => { + updateApplicationContext({ isWalletsInitialized: walletsInitialized, randomID: Math.floor(Math.random() * 11) }); + }, [walletsInitialized]); + useEffect(() => { if (isInstalled && isReachable && walletsInitialized && preferredFiatCurrency) { const preferredFiatCurrencyParsed = JSON.parse(preferredFiatCurrency); @@ -69,11 +73,17 @@ function WatchConnectivity() { if (message.request === 'createInvoice') { handleLightningInvoiceCreateRequest(message.walletIndex, message.amount, message.description) .then(createInvoiceRequest => reply({ invoicePaymentRequest: createInvoiceRequest })) - .catch(e => console.log(e)); + .catch(e => { + console.log(e); + reply({}); + }); } else if (message.message === 'sendApplicationContext') { sendWalletsToWatch(); + reply({}); } else if (message.message === 'fetchTransactions') { - fetchWalletTransactions().then(() => saveToDisk()); + fetchWalletTransactions() + .then(() => saveToDisk()) + .finally(() => reply({})); } else if (message.message === 'hideBalance') { const walletIndex = message.walletIndex; const wallet = wallets[walletIndex]; @@ -110,34 +120,31 @@ function WatchConnectivity() { if (!Array.isArray(wallets)) { console.log('No Wallets set to sync with Watch app. Exiting...'); return; - } else if (wallets.length === 0) { - console.log('Wallets array is set. No Wallets set to sync with Watch app. Exiting...'); - updateApplicationContext({ wallets: [], randomID: Math.floor(Math.random() * 11) }); + } + if (!walletsInitialized) { + console.log('Wallets not initialized. Exiting...'); return; } - const walletsToProcess = []; for (const wallet of wallets) { let receiveAddress; - if (wallet.getAddressAsync) { - if (wallet.chain === Chain.ONCHAIN) { - try { - receiveAddress = await wallet.getAddressAsync(); - } catch (_) {} - if (!receiveAddress) { - // either sleep expired or getAddressAsync threw an exception - receiveAddress = wallet._getExternalAddressByIndex(wallet.next_free_address_index); - } - } else if (wallet.chain === Chain.OFFCHAIN) { - try { - await wallet.getAddressAsync(); - receiveAddress = wallet.getAddress(); - } catch (_) {} - if (!receiveAddress) { - // either sleep expired or getAddressAsync threw an exception - receiveAddress = wallet.getAddress(); - } + if (wallet.chain === Chain.ONCHAIN) { + try { + receiveAddress = await wallet.getAddressAsync(); + } catch (_) {} + if (!receiveAddress) { + // either sleep expired or getAddressAsync threw an exception + receiveAddress = wallet._getExternalAddressByIndex(wallet.next_free_address_index); + } + } else if (wallet.chain === Chain.OFFCHAIN) { + try { + await wallet.getAddressAsync(); + receiveAddress = wallet.getAddress(); + } catch (_) {} + if (!receiveAddress) { + // either sleep expired or getAddressAsync threw an exception + receiveAddress = wallet.getAddress(); } } const transactions = wallet.getTransactions(10); @@ -207,7 +214,7 @@ function WatchConnectivity() { hideBalance: wallet.hideBalance, }; if (wallet.chain === Chain.ONCHAIN && wallet.type !== MultisigHDWallet.type) { - walletInformation.push({ xpub: wallet.getXpub() ? wallet.getXpub() : wallet.getSecret() }); + walletInformation.xpub = wallet.getXpub() ? wallet.getXpub() : wallet.getSecret(); } walletsToProcess.push(walletInformation); } diff --git a/__mocks__/@react-native-async-storage/async-storage.js b/__mocks__/@react-native-async-storage/async-storage.js index 32e39bc7a..ef43ca918 100644 --- a/__mocks__/@react-native-async-storage/async-storage.js +++ b/__mocks__/@react-native-async-storage/async-storage.js @@ -1 +1,3 @@ -export default from '@react-native-async-storage/async-storage/jest/async-storage-mock' +import AsyncStorageMock from '@react-native-async-storage/async-storage/jest/async-storage-mock'; + +export default AsyncStorageMock; diff --git a/_editorconfig b/_editorconfig new file mode 100644 index 000000000..358a10245 --- /dev/null +++ b/_editorconfig @@ -0,0 +1,4 @@ +# Windows files +[*.bat] +end_of_line = crlf + diff --git a/android/app/build.gradle b/android/app/build.gradle index e74b4cb29..975fd5c52 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -20,7 +20,7 @@ import com.android.build.OutputFile * // default. Can be overridden with ENTRY_FILE environment variable. * entryFile: "index.android.js", * - * // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format + * // https://reactnative.dev/docs/performance#enable-the-ram-format * bundleCommand: "ram-bundle", * * // whether to bundle JS and assets in debug mode @@ -121,7 +121,10 @@ def jscFlavor = 'org.webkit:android-jsc-intl:+' def enableHermes = project.ext.react.get("enableHermes", false); android { + ndkVersion rootProject.ext.ndkVersion + compileSdkVersion rootProject.ext.compileSdkVersion + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -136,7 +139,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 - versionName "6.1.2" + versionName "6.2.3" multiDexEnabled true missingDimensionStrategy 'react-native-camera', 'general' testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type @@ -150,10 +153,11 @@ android { include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" } } + buildTypes { release { // Caution! In production, you need to generate your own keystore file. - // see https://facebook.github.io/react-native/docs/signed-apk-android. + // see https://reactnative.dev/docs/signed-apk-android. minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" } @@ -164,11 +168,11 @@ android { variant.outputs.each { output -> // For each separate APK per architecture, set a unique version code as described here: // https://developer.android.com/studio/build/configure-apk-splits.html + // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc. def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] def abi = output.getFilter(OutputFile.ABI) if (abi != null) { // null for the universal-debug, universal-release variants - output.versionCodeOverride = - versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + output.versionCodeOverride = versionCodes.get(abi) * 1048576 + defaultConfig.versionCode } } @@ -177,13 +181,8 @@ android { dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) - implementation files("../../node_modules/react-native-tor/android/libs/sifir_android.aar") //noinspection GradleDynamicVersion - - androidTestImplementation('com.wix:detox:+') { - exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8' - } - + implementation files("../../node_modules/react-native-tor/android/libs/sifir_android.aar") implementation "com.facebook.react:react-native:+" // From node_modules implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" @@ -194,12 +193,17 @@ dependencies { debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { exclude group:'com.facebook.flipper' + exclude group:'com.squareup.okhttp3', module:'okhttp' } debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { exclude group:'com.facebook.flipper' } + androidTestImplementation('com.wix:detox:+') { + exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8' + } + if (enableHermes) { def hermesPath = "../../node_modules/hermes-engine/android/"; debugImplementation files(hermesPath + "hermes-debug.aar") @@ -217,4 +221,4 @@ task copyDownloadableDepsToLibs(type: Copy) { } apply plugin: 'com.google.gms.google-services' // Google Services plugin -apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) +apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index fa26aa56e..0c4927bcc 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -4,5 +4,7 @@ - + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1f2c164b8..91cc9df31 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -93,7 +93,6 @@ /> - diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java index 5d8770559..792d8899d 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java @@ -81,4 +81,3 @@ public class MainApplication extends Application implements ReactApplication { } } } - \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 62fe59fa4..9fab0be74 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,7 +1,7 @@ - diff --git a/android/build.gradle b/android/build.gradle index 96ba7d079..b1c44c08d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -8,18 +8,20 @@ buildscript { compileSdkVersion = 30 targetSdkVersion = 30 googlePlayServicesVersion = "16.+" + googlePlayServicesIidVersion = "16.0.1" firebaseVersion = "17.3.4" firebaseMessagingVersion = "20.2.1" + ndkVersion = "20.1.5948944" } repositories { google() - jcenter() + mavenCentral() } - ext.kotlinVersion = '1.3.+' + ext.kotlinVersion = '1.4.32' dependencies { - classpath('com.android.tools.build:gradle:4.0.1') + classpath('com.android.tools.build:gradle:4.2.1') classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" - classpath 'com.google.gms:google-services:4.3.3' // Google Services plugin + classpath 'com.google.gms:google-services:4.3.5' // Google Services plugin // NOTE: Do not place your application dependencies here; they belong @@ -29,6 +31,25 @@ buildscript { allprojects { repositories { + jcenter() { + content { + includeModule("com.facebook.yoga", "proguard-annotations") + includeModule("com.facebook.fbjni", "fbjni-java-only") + includeModule("com.facebook.fresco", "fresco") + includeModule("com.facebook.fresco", "stetho") + includeModule("com.facebook.fresco", "fbcore") + includeModule("com.facebook.fresco", "drawee") + includeModule("com.facebook.fresco", "imagepipeline") + includeModule("com.facebook.fresco", "imagepipeline-native") + includeModule("com.facebook.fresco", "memory-type-native") + includeModule("com.facebook.fresco", "memory-type-java") + includeModule("com.facebook.fresco", "nativeimagefilters") + includeModule("com.facebook.stetho", "stetho") + includeModule("com.wei.android.lib", "fingerprintidentify") + includeModule("com.eightbitlab", "blurview") + } + } + mavenCentral() mavenLocal() maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm @@ -43,7 +64,6 @@ allprojects { url "$rootDir/../node_modules/detox/Detox-android" } google() - jcenter() maven { url 'https://www.jitpack.io' } } } @@ -54,6 +74,10 @@ subprojects { android { compileSdkVersion 30 buildToolsVersion '30.0.3' + compileSdkVersion 29 + defaultConfig { + minSdkVersion 28 + } } } } diff --git a/android/gradle.properties b/android/gradle.properties index 2bd906514..6b94a20f9 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -31,4 +31,4 @@ org.gradle.configureondemand=true org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 # Version of flipper SDK to use with React Native -FLIPPER_VERSION=0.54.0 \ No newline at end of file +FLIPPER_VERSION=0.75.1 \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 020856ca8..b02efd0d6 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/android/gradlew.bat b/android/gradlew.bat index 9991c5032..e3ecc7d77 100644 --- a/android/gradlew.bat +++ b/android/gradlew.bat @@ -37,7 +37,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -51,7 +51,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -82,8 +82,7 @@ set CMD_LINE_ARGS=%* set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd diff --git a/appcenter-post-build.sh b/appcenter-post-build.sh index 313bf807c..eeff0bf8a 100755 --- a/appcenter-post-build.sh +++ b/appcenter-post-build.sh @@ -18,13 +18,7 @@ if [ -f $FILENAME ]; then PR=`node scripts/appcenter-post-build-get-pr-number.js` echo PR: $PR - # uploading file - HASH=`date +%s` - FILENAME_UNIQ="$APPCENTER_OUTPUT_DIRECTORY/$BRANCH-$HASH.apk" - cp "$FILENAME" "$FILENAME_UNIQ" - curl "http://filestorage.bluewallet.io:1488/upload.php" -F "fileToUpload=@$FILENAME_UNIQ" - rm "$FILENAME_UNIQ" - DLOAD_APK="http://filestorage.bluewallet.io:1488/$BRANCH-$HASH.apk" + DLOAD_APK="https://lambda-download-android-build.herokuapp.com/download/$BUILD_BUILDID" curl -X POST --data "{\"body\":\"♫ This was a triumph. I'm making a note here: HUGE SUCCESS ♫\n\n [android in browser] $APPURL\n\n[download apk]($DLOAD_APK) \"}" -u "$GITHUB" "https://api.github.com/repos/BlueWallet/BlueWallet/issues/$PR/comments" fi diff --git a/babel.config.js b/babel.config.js index f842b77fc..fff45f944 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,4 @@ module.exports = { presets: ['module:metro-react-native-babel-preset'], + plugins: ['react-native-reanimated/plugin'], // required by react-native-reanimated v2 https://docs.swmansion.com/react-native-reanimated/docs/installation/ }; diff --git a/blue_modules/BlueElectrum.js b/blue_modules/BlueElectrum.js index 40b0b3b02..49187a3c9 100644 --- a/blue_modules/BlueElectrum.js +++ b/blue_modules/BlueElectrum.js @@ -1,17 +1,23 @@ /* global alert */ import AsyncStorage from '@react-native-async-storage/async-storage'; import { Alert } from 'react-native'; -import { AppStorage, LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet } from '../class'; +import { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet } from '../class'; import DefaultPreference from 'react-native-default-preference'; -import RNWidgetCenter from 'react-native-widget-center'; import loc from '../loc'; +import { isTorCapable } from './environment'; +import WidgetCommunication from './WidgetCommunication'; const bitcoin = require('bitcoinjs-lib'); const ElectrumClient = require('electrum-client'); const reverse = require('buffer-reverse'); const BigNumber = require('bignumber.js'); -const torrific = require('../blue_modules/torrific'); +const torrific = require('./torrific'); const Realm = require('realm'); +const ELECTRUM_HOST = 'electrum_host'; +const ELECTRUM_TCP_PORT = 'electrum_tcp_port'; +const ELECTRUM_SSL_PORT = 'electrum_ssl_port'; +const ELECTRUM_SERVER_HISTORY = 'electrum_server_history'; + let _realm; async function _getRealm() { if (_realm) return _realm; @@ -79,16 +85,16 @@ async function connectMain() { try { if (usingPeer.host.endsWith('onion')) { const randomPeer = await getRandomHardcodedPeer(); - await DefaultPreference.set(AppStorage.ELECTRUM_HOST, randomPeer.host); - await DefaultPreference.set(AppStorage.ELECTRUM_TCP_PORT, randomPeer.tcp); - await DefaultPreference.set(AppStorage.ELECTRUM_SSL_PORT, randomPeer.ssl); + await DefaultPreference.set(ELECTRUM_HOST, randomPeer.host); + await DefaultPreference.set(ELECTRUM_TCP_PORT, randomPeer.tcp); + await DefaultPreference.set(ELECTRUM_SSL_PORT, randomPeer.ssl); } else { - await DefaultPreference.set(AppStorage.ELECTRUM_HOST, usingPeer.host); - await DefaultPreference.set(AppStorage.ELECTRUM_TCP_PORT, usingPeer.tcp); - await DefaultPreference.set(AppStorage.ELECTRUM_SSL_PORT, usingPeer.ssl); + await DefaultPreference.set(ELECTRUM_HOST, usingPeer.host); + await DefaultPreference.set(ELECTRUM_TCP_PORT, usingPeer.tcp); + await DefaultPreference.set(ELECTRUM_SSL_PORT, usingPeer.ssl); } - RNWidgetCenter.reloadAllTimelines(); + WidgetCommunication.reloadAllTimelines(); } catch (e) { // Must be running on Android console.log(e); @@ -97,12 +103,13 @@ async function connectMain() { try { console.log('begin connection:', JSON.stringify(usingPeer)); mainClient = new ElectrumClient( - usingPeer.host.endsWith('.onion') ? torrific : global.net, + usingPeer.host.endsWith('.onion') && isTorCapable ? torrific : global.net, global.tls, usingPeer.ssl || usingPeer.tcp, usingPeer.host, usingPeer.ssl ? 'tls' : 'tcp', ); + mainClient.onError = function (e) { console.log('electrum mainClient.onError():', e.message); if (mainConnected) { @@ -187,15 +194,15 @@ async function presentNetworkErrorAlert(usingPeer) { text: loc._.ok, style: 'destructive', onPress: async () => { - await AsyncStorage.setItem(AppStorage.ELECTRUM_HOST, ''); - await AsyncStorage.setItem(AppStorage.ELECTRUM_TCP_PORT, ''); - await AsyncStorage.setItem(AppStorage.ELECTRUM_SSL_PORT, ''); + await AsyncStorage.setItem(ELECTRUM_HOST, ''); + await AsyncStorage.setItem(ELECTRUM_TCP_PORT, ''); + await AsyncStorage.setItem(ELECTRUM_SSL_PORT, ''); try { await DefaultPreference.setName('group.io.bluewallet.bluewallet'); - await DefaultPreference.clear(AppStorage.ELECTRUM_HOST); - await DefaultPreference.clear(AppStorage.ELECTRUM_SSL_PORT); - await DefaultPreference.clear(AppStorage.ELECTRUM_TCP_PORT); - RNWidgetCenter.reloadAllTimelines(); + await DefaultPreference.clear(ELECTRUM_HOST); + await DefaultPreference.clear(ELECTRUM_SSL_PORT); + await DefaultPreference.clear(ELECTRUM_TCP_PORT); + WidgetCommunication.reloadAllTimelines(); } catch (e) { // Must be running on Android console.log(e); @@ -236,9 +243,9 @@ async function getRandomHardcodedPeer() { } async function getSavedPeer() { - const host = await AsyncStorage.getItem(AppStorage.ELECTRUM_HOST); - const port = await AsyncStorage.getItem(AppStorage.ELECTRUM_TCP_PORT); - const sslPort = await AsyncStorage.getItem(AppStorage.ELECTRUM_SSL_PORT); + const host = await AsyncStorage.getItem(ELECTRUM_HOST); + const port = await AsyncStorage.getItem(ELECTRUM_TCP_PORT); + const sslPort = await AsyncStorage.getItem(ELECTRUM_SSL_PORT); return { host, tcp: port, ssl: sslPort }; } @@ -590,7 +597,9 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver if (txdata.error && txdata.error.code === -32600) { // response too large // lets do single call, that should go through okay: - txdata.result = await mainClient.blockchainTransaction_get(txdata.param, verbose); + txdata.result = await mainClient.blockchainTransaction_get(txdata.param, false); + // since we used VERBOSE=false, server sent us plain txhex which we must decode on our end: + txdata.result = txhexToElectrumTransaction(txdata.result); } ret[txdata.param] = txdata.result; if (ret[txdata.param]) delete ret[txdata.param].hex; // compact @@ -801,7 +810,7 @@ module.exports.calculateBlockTime = function (height) { */ module.exports.testConnection = async function (host, tcpPort, sslPort) { const client = new ElectrumClient( - host.endsWith('.onion') ? torrific : global.net, + host.endsWith('.onion') && isTorCapable ? torrific : global.net, global.tls, sslPort || tcpPort, host, @@ -813,7 +822,7 @@ module.exports.testConnection = async function (host, tcpPort, sslPort) { try { const rez = await Promise.race([ new Promise(resolve => { - timeoutId = setTimeout(() => resolve('timeout'), host.endsWith('.onion') ? 21000 : 5000); + timeoutId = setTimeout(() => resolve('timeout'), host.endsWith('.onion') && isTorCapable ? 21000 : 5000); }), client.connect(), ]); @@ -845,6 +854,10 @@ module.exports.setBatchingEnabled = () => { module.exports.hardcodedPeers = hardcodedPeers; module.exports.getRandomHardcodedPeer = getRandomHardcodedPeer; +module.exports.ELECTRUM_HOST = ELECTRUM_HOST; +module.exports.ELECTRUM_TCP_PORT = ELECTRUM_TCP_PORT; +module.exports.ELECTRUM_SSL_PORT = ELECTRUM_SSL_PORT; +module.exports.ELECTRUM_SERVER_HISTORY = ELECTRUM_SERVER_HISTORY; const splitIntoChunks = function (arr, chunkSize) { const groups = []; diff --git a/blue_modules/WidgetCommunication.ios.js b/blue_modules/WidgetCommunication.ios.js index 892fbeb32..e926ec964 100644 --- a/blue_modules/WidgetCommunication.ios.js +++ b/blue_modules/WidgetCommunication.ios.js @@ -29,6 +29,10 @@ function WidgetCommunication() { setValues(); }; + WidgetCommunication.reloadAllTimelines = () => { + RNWidgetCenter.reloadAllTimelines(); + }; + const allWalletsBalanceAndTransactionTime = async () => { if ((await isStorageEncrypted()) || !(await WidgetCommunication.isBalanceDisplayAllowed())) { return { allWalletsBalance: 0, latestTransactionTime: 0 }; diff --git a/blue_modules/WidgetCommunication.android.js b/blue_modules/WidgetCommunication.js similarity index 57% rename from blue_modules/WidgetCommunication.android.js rename to blue_modules/WidgetCommunication.js index 437788d12..05cba00d8 100644 --- a/blue_modules/WidgetCommunication.android.js +++ b/blue_modules/WidgetCommunication.js @@ -1,6 +1,7 @@ function WidgetCommunication(props) { - WidgetCommunication.isBalanceDisplayAllowed = false; + WidgetCommunication.isBalanceDisplayAllowed = () => {}; WidgetCommunication.setBalanceDisplayAllowed = () => {}; + WidgetCommunication.reloadAllTimelines = () => {}; return null; } diff --git a/blue_modules/analytics.js b/blue_modules/analytics.js index 7fd0af86f..08502da57 100644 --- a/blue_modules/analytics.js +++ b/blue_modules/analytics.js @@ -1,6 +1,15 @@ +import * as Sentry from '@sentry/react-native'; import amplitude from 'amplitude-js'; -import { getVersion, getSystemName } from 'react-native-device-info'; +import { getVersion, getSystemName, getUniqueId } from 'react-native-device-info'; import { Platform } from 'react-native'; +const BlueApp = require('../BlueApp'); + +if (process.env.NODE_ENV !== 'development') { + Sentry.init({ + dsn: 'https://23377936131848ca8003448a893cb622@sentry.io/1295736', + }); + Sentry.setUser({ id: getUniqueId() }); +} amplitude.getInstance().init('8b7cf19e8eea3cdcf16340f5fbf16330', null, { useNativeDeviceInfo: true, @@ -8,6 +17,10 @@ amplitude.getInstance().init('8b7cf19e8eea3cdcf16340f5fbf16330', null, { }); amplitude.getInstance().setVersionName(getVersion()); amplitude.getInstance().options.apiEndpoint = 'api2.amplitude.com'; +BlueApp.isDoNotTrackEnabled().then(value => { + if (value) Sentry.close(); + amplitude.getInstance().setOptOut(value); +}); const A = async event => { console.log('posting analytics...', event); @@ -28,4 +41,9 @@ A.ENUM = { NAVIGATED_TO_WALLETS_HODLHODL: 'NAVIGATED_TO_WALLETS_HODLHODL', }; +A.setOptOut = value => { + if (value) Sentry.close(); + return amplitude.getInstance().setOptOut(value); +}; + module.exports = A; diff --git a/blue_modules/bip38/README.md b/blue_modules/bip38/README.md index 2c03c460c..8a245d9b6 100644 --- a/blue_modules/bip38/README.md +++ b/blue_modules/bip38/README.md @@ -1,8 +1,8 @@ # bip38 -[![build status](https://secure.travis-ci.org/bitcoinjs/bip38.svg)](http://travis-ci.org/bitcoinjs/bip38) +[![build status](https://secure.travis-ci.org/bitcoinjs/bip38.svg)](https://travis-ci.org/bitcoinjs/bip38) [![Coverage Status](https://img.shields.io/coveralls/cryptocoinjs/bip38.svg)](https://coveralls.io/r/cryptocoinjs/bip38) -[![Version](http://img.shields.io/npm/v/bip38.svg)](https://www.npmjs.org/package/bip38) +[![Version](https://img.shields.io/npm/v/bip38.svg)](https://www.npmjs.org/package/bip38) [![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) diff --git a/blue_modules/currency.js b/blue_modules/currency.js index 3b5498b19..88d32fae3 100644 --- a/blue_modules/currency.js +++ b/blue_modules/currency.js @@ -1,11 +1,12 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import DefaultPreference from 'react-native-default-preference'; -import RNWidgetCenter from 'react-native-widget-center'; import * as RNLocalize from 'react-native-localize'; import BigNumber from 'bignumber.js'; - -import { AppStorage } from '../class'; import { FiatUnit, getFiatRate } from '../models/fiatUnit'; +import WidgetCommunication from './WidgetCommunication'; + +const PREFERRED_CURRENCY = 'preferredCurrency'; +const EXCHANGE_RATES = 'currency'; let preferredFiatCurrency = FiatUnit.USD; const exchangeRates = {}; @@ -22,15 +23,15 @@ const STRUCT = { * @returns {Promise} */ async function setPrefferedCurrency(item) { - await AsyncStorage.setItem(AppStorage.PREFERRED_CURRENCY, JSON.stringify(item)); + await AsyncStorage.setItem(PREFERRED_CURRENCY, JSON.stringify(item)); await DefaultPreference.setName('group.io.bluewallet.bluewallet'); await DefaultPreference.set('preferredCurrency', item.endPointKey); await DefaultPreference.set('preferredCurrencyLocale', item.locale.replace('-', '_')); - RNWidgetCenter.reloadAllTimelines(); + WidgetCommunication.reloadAllTimelines(); } async function getPreferredCurrency() { - const preferredCurrency = await JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY)); + const preferredCurrency = await JSON.parse(await AsyncStorage.getItem(PREFERRED_CURRENCY)); await DefaultPreference.setName('group.io.bluewallet.bluewallet'); await DefaultPreference.set('preferredCurrency', preferredCurrency.endPointKey); await DefaultPreference.set('preferredCurrencyLocale', preferredCurrency.locale.replace('-', '_')); @@ -44,7 +45,7 @@ async function updateExchangeRate() { } try { - preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(AppStorage.PREFERRED_CURRENCY)); + preferredFiatCurrency = JSON.parse(await AsyncStorage.getItem(PREFERRED_CURRENCY)); if (preferredFiatCurrency === null) { throw Error('No Preferred Fiat selected'); } @@ -61,15 +62,15 @@ async function updateExchangeRate() { try { rate = await getFiatRate(preferredFiatCurrency.endPointKey); } catch (Err) { - const lastSavedExchangeRate = JSON.parse(await AsyncStorage.getItem(AppStorage.EXCHANGE_RATES)); + const lastSavedExchangeRate = JSON.parse(await AsyncStorage.getItem(EXCHANGE_RATES)); exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = lastSavedExchangeRate['BTC_' + preferredFiatCurrency.endPointKey] * 1; return; } exchangeRates[STRUCT.LAST_UPDATED] = +new Date(); exchangeRates['BTC_' + preferredFiatCurrency.endPointKey] = rate; - await AsyncStorage.setItem(AppStorage.EXCHANGE_RATES, JSON.stringify(exchangeRates)); - await AsyncStorage.setItem(AppStorage.PREFERRED_CURRENCY, JSON.stringify(preferredFiatCurrency)); + await AsyncStorage.setItem(EXCHANGE_RATES, JSON.stringify(exchangeRates)); + await AsyncStorage.setItem(PREFERRED_CURRENCY, JSON.stringify(preferredFiatCurrency)); await setPrefferedCurrency(preferredFiatCurrency); } @@ -179,3 +180,5 @@ module.exports.btcToSatoshi = btcToSatoshi; module.exports.getCurrencySymbol = getCurrencySymbol; module.exports._setPreferredFiatCurrency = _setPreferredFiatCurrency; // export it to mock data in tests module.exports._setExchangeRate = _setExchangeRate; // export it to mock data in tests +module.exports.PREFERRED_CURRENCY = PREFERRED_CURRENCY; +module.exports.EXCHANGE_RATES = EXCHANGE_RATES; diff --git a/blue_modules/environment.js b/blue_modules/environment.js index e708f0db4..0fe423f31 100644 --- a/blue_modules/environment.js +++ b/blue_modules/environment.js @@ -1,8 +1,18 @@ -import { getSystemName, isTablet } from 'react-native-device-info'; -import isCatalyst from 'react-native-is-catalyst'; +import { Platform } from 'react-native'; +import { getSystemName, isTablet, getDeviceType } from 'react-native-device-info'; const isMacCatalina = getSystemName() === 'Mac OS X'; +const isDesktop = getDeviceType() === 'Desktop'; +const getIsTorCapable = () => { + let capable = true; + if (Platform.OS === 'android' && Platform.Version < 26) { + capable = false; + } else if (isDesktop) { + capable = false; + } + return capable; +}; -module.exports.isMacCatalina = isMacCatalina; -module.exports.isCatalyst = isCatalyst; -module.exports.isTablet = isTablet; +export const isHandset = getDeviceType() === 'Handset'; +export const isTorCapable = getIsTorCapable(); +export { isMacCatalina, isDesktop, isTablet }; diff --git a/blue_modules/fs.js b/blue_modules/fs.js index 9376aa656..2c8d7ef27 100644 --- a/blue_modules/fs.js +++ b/blue_modules/fs.js @@ -4,20 +4,52 @@ import RNFS from 'react-native-fs'; import Share from 'react-native-share'; import loc from '../loc'; import DocumentPicker from 'react-native-document-picker'; -import isCatalyst from 'react-native-is-catalyst'; import { launchCamera, launchImageLibrary } from 'react-native-image-picker'; import { presentCameraNotAuthorizedAlert } from '../class/camera'; +import { isDesktop } from '../blue_modules/environment'; import ActionSheet from '../screen/ActionSheet'; import BlueClipboard from './clipboard'; const LocalQRCode = require('@remobile/react-native-qrcode-local-image'); +const writeFileAndExportToAndroidDestionation = async ({ filename, contents, destinationLocalizedString, destination }) => { + const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, { + title: loc.send.permission_storage_title, + message: loc.send.permission_storage_message, + buttonNeutral: loc.send.permission_storage_later, + buttonNegative: loc._.cancel, + buttonPositive: loc._.ok, + }); + if (granted === PermissionsAndroid.RESULTS.GRANTED) { + const filePath = destination + `/${filename}`; + try { + await RNFS.writeFile(filePath, contents); + alert(loc.formatString(loc._.file_saved, { filePath: filename, destination: destinationLocalizedString })); + } catch (e) { + console.log(e); + alert(e.message); + } + } else { + console.log('Storage Permission: Denied'); + Alert.alert(loc.send.permission_storage_title, loc.send.permission_storage_denied_message, [ + { + text: loc.send.open_settings, + onPress: () => { + Linking.openSettings(); + }, + style: 'default', + }, + { text: loc._.cancel, onPress: () => {}, style: 'cancel' }, + ]); + } +}; + const writeFileAndExport = async function (filename, contents) { if (Platform.OS === 'ios') { const filePath = RNFS.TemporaryDirectoryPath + `/${filename}`; await RNFS.writeFile(filePath, contents); Share.open({ url: 'file://' + filePath, - saveToFiles: isCatalyst, + saveToFiles: isDesktop, }) .catch(error => { console.log(error); @@ -26,37 +58,37 @@ const writeFileAndExport = async function (filename, contents) { RNFS.unlink(filePath); }); } else if (Platform.OS === 'android') { - const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, { - title: loc.send.permission_storage_title, - message: loc.send.permission_storage_message, - buttonNeutral: loc.send.permission_storage_later, - buttonNegative: loc._.cancel, - buttonPositive: loc._.ok, - }); + Alert.alert( + loc._.file_save_title, - if (granted === PermissionsAndroid.RESULTS.GRANTED) { - console.log('Storage Permission: Granted'); - const filePath = RNFS.DownloadDirectoryPath + `/${filename}`; - try { - await RNFS.writeFile(filePath, contents); - alert(loc.formatString(loc._.file_saved, { filePath: filename })); - } catch (e) { - console.log(e); - alert(e.message); - } - } else { - console.log('Storage Permission: Denied'); - Alert.alert(loc.send.permission_storage_title, loc.send.permission_storage_denied_message, [ - { - text: loc.send.open_settings, - onPress: () => { - Linking.openSettings(); - }, - style: 'default', - }, + loc.formatString(loc._.file_save_location, { filePath: filename }), + [ { text: loc._.cancel, onPress: () => {}, style: 'cancel' }, - ]); - } + { + text: loc._.downloads_folder, + onPress: () => { + writeFileAndExportToAndroidDestionation({ + filename, + contents, + destinationLocalizedString: loc._.downloads_folder, + destination: RNFS.DownloadDirectoryPath, + }); + }, + }, + { + text: loc._.external_storage, + onPress: async () => { + writeFileAndExportToAndroidDestionation({ + filename, + contents, + destination: RNFS.ExternalStorageDirectoryPath, + destinationLocalizedString: loc._.external_storage, + }); + }, + }, + ], + { cancelable: true }, + ); } }; @@ -103,6 +135,8 @@ const showImagePickerAndReadImage = () => { title: null, mediaType: 'photo', takePhotoButtonTitle: null, + maxHeight: 800, + maxWidth: 600, }, response => { if (response.uri) { diff --git a/blue_modules/scryptsy/CHANGELOG.md b/blue_modules/scryptsy/CHANGELOG.md deleted file mode 100644 index c15ed809e..000000000 --- a/blue_modules/scryptsy/CHANGELOG.md +++ /dev/null @@ -1,50 +0,0 @@ -3.0.0 / 2019-03-12 ------------------- -- **breaking** Import gives an object with two functions `scrypt` and `scryptSync` -- `scryptSync` is the old synchronus function. -- `scrypt` will return a promise with the buffer. - -2.0.0 / 2016-05-26 ------------------- -- **breaking** Node v0.10 not supported anymore. - -1.2.1 / 2015-03-01 ------------------- -- now using standard for code formatting -- now using `pbkdf2` module over `pbkdf2-sha256`, huge performance increase in Node - -1.2.0 / 2014-12-11 ------------------- -- upgraded `pbkdf2-sha256` from `1.0.1` to `1.1.0` -- removed `browser` field for `crypto`; not-necessary anymore - -1.1.0 / 2014-07-28 ------------------- -- added `progressCallback` (Nadav Ivgi / #4)[https://github.com/cryptocoinjs/scryptsy/pull/4] - -1.0.0 / 2014-06-10 ------------------- -- moved tests to fixtures -- removed semilcolons per http://cryptocoinjs.com/about/contributing/#semicolons -- changed `module.exports.scrypt = funct..` to `module.exports = funct...` -- removed `terst` from dev deps -- upgraded `"pbkdf2-sha256": "~0.1.1"` to `"pbkdf2-sha256": "^1.0.1"` -- added `crypto-browserify` dev dep for `pbkdf2-sha256` tests -- added TravisCI -- added Coveralls -- added testling - -0.2.0 / 2014-03-05 ------------------- -- made a lot of scrypt functions internal along with variables to make thread safe - -0.1.0 / 2014-02-18 ------------------- -- changed spacing from 4 to 2 -- removed unneeded JavaScript implementations. Using `pbkdf2-sha256` dep now. -- add browser test support -- convert from `Array` to typed arrays and `Buffer` - -0.0.1 / 2014-02-18 ------------------- -- initial release. Forked from https://github.com/cheongwy/node-scrypt-js and added tests. diff --git a/blue_modules/scryptsy/LICENSE b/blue_modules/scryptsy/LICENSE deleted file mode 100644 index 4d5f7a1c1..000000000 --- a/blue_modules/scryptsy/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 cryptocoinjs - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/blue_modules/scryptsy/README.md b/blue_modules/scryptsy/README.md deleted file mode 100644 index fee9b6062..000000000 --- a/blue_modules/scryptsy/README.md +++ /dev/null @@ -1,157 +0,0 @@ -scryptsy -======== - -[![build status](https://secure.travis-ci.org/cryptocoinjs/scryptsy.svg)](http://travis-ci.org/cryptocoinjs/scryptsy) -[![Coverage Status](https://img.shields.io/coveralls/cryptocoinjs/scryptsy.svg)](https://coveralls.io/r/cryptocoinjs/scryptsy) -[![Version](http://img.shields.io/npm/v/scryptsy.svg)](https://www.npmjs.org/package/scryptsy) - -`scryptsy` is a pure Javascript implementation of the [scrypt][wiki] key derivation function that is fully compatible with Node.js and the browser (via Browserify). - - -Why? ----- - -`Scrypt` is an integral part of many crypto currencies. It's a part of the [BIP38](https://github.com/bitcoin/bips/blob/master/bip-0038.mediawiki) standard for encrypting private Bitcoin keys. It also serves as the [proof-of-work system](http://en.wikipedia.org/wiki/Proof-of-work_system) for many crypto currencies, most notably: Litecoin and Dogecoin. - - - -Installation ------------- - - npm install --save scryptsy - - - -Browserify Note ------------- - -When using a browserified bundle, be sure to add `setImmediate` as a shim. - - - -Example -------- - -```js -const scrypt = require('scryptsy') - -async function main () { - var key = "pleaseletmein" - var salt = "SodiumChloride" - var data1 = scrypt(key, salt, 16384, 8, 1, 64) - console.log(data1.toString('hex')) - // => 7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887 - - // async is actually slower, but it will free up the event loop occasionally - // which will allow for front end GUI elements to update and cause it to not - // freeze up. - // See benchmarks below - // Passing 300 below means every 300 iterations internally will call setImmediate once - var data2 = await scrypt.async(key, salt, 16384, 8, 1, 64, undefined, 300) - console.log(data2.toString('hex')) - // => 7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887 -} -main().catch(console.error) -``` - - -Benchmarks -------- - -Internal iterations are N * p, so changing r doesn't affect the number of calls to setImmediate. -Decreasing pI decreases performance in exchange for more frequently freeing the event loop. -(pI Default is 5000 loops per setImmediate call) - -Note: these benchmarks were done on node v10 on a CPU with good single thread performance. -browsers show a much larger difference. Please tinker with the pI setting to balance between -performance and GUI responsiveness. - -If `pI >= N`, setImmediate will only be called `p * 2` times total (on the i = 0 of each for loop). - -``` ---------------------------- -time : type : (N,r,p,pI) (pI = promiseInterval) ---------------------------- -2266 ms : sync (2^16,16,1) -2548 ms : async (2^16,16,1,5000) -12.44% increase ---------------------------- -2616 ms : sync (2^16,1,16) -2995 ms : async (2^16,1,16,5000) -14.49% increase ---------------------------- -2685 ms : sync (2^20,1,1) -3090 ms : async (2^20,1,1,5000) -15.08% increase ---------------------------- -2235 ms : sync (2^16,16,1) -2627 ms : async (2^16,16,1,10) -17.54% increase ---------------------------- -2592 ms : sync (2^16,1,16) -3305 ms : async (2^16,1,16,10) -27.51% increase ---------------------------- -2705 ms : sync (2^20,1,1) -3363 ms : async (2^20,1,1,10) -24.33% increase ---------------------------- -2278 ms : sync (2^16,16,1) -2773 ms : async (2^16,16,1,1) -21.73% increase ---------------------------- -2617 ms : sync (2^16,1,16) -5632 ms : async (2^16,1,16,1) -115.21% increase ---------------------------- -2727 ms : sync (2^20,1,1) -5723 ms : async (2^20,1,1,1) -109.86% increase ---------------------------- -``` - -API ---- - -### scrypt(key, salt, N, r, p, keyLenBytes, [progressCallback]) - -- **key**: The key. Either `Buffer` or `string`. -- **salt**: The salt. Either `Buffer` or `string`. -- **N**: The number of iterations. `number` (integer) -- **r**: Memory factor. `number` (integer) -- **p**: Parallelization factor. `number` (integer) -- **keyLenBytes**: The number of bytes to return. `number` (integer) -- **progressCallback**: Call callback on every `1000` ops. Passes in `{current, total, percent}` as first parameter to `progressCallback()`. - -Returns `Buffer`. - -### scrypt.async(key, salt, N, r, p, keyLenBytes, [progressCallback, promiseInterval]) - -- **key**: The key. Either `Buffer` or `string`. -- **salt**: The salt. Either `Buffer` or `string`. -- **N**: The number of iterations. `number` (integer) -- **r**: Memory factor. `number` (integer) -- **p**: Parallelization factor. `number` (integer) -- **keyLenBytes**: The number of bytes to return. `number` (integer) -- **progressCallback**: Call callback on every `1000` ops. Passes in `{current, total, percent}` as first parameter to `progressCallback()`. -- **promiseInterval**: The number of internal iterations before calling setImmediate once to free the event loop. - -Returns `Promise`. - - - -Resources ---------- -- [Tarsnap Blurb on Scrypt][tarsnap] -- [Scrypt Whitepaper](http://www.tarsnap.com/scrypt/scrypt.pdf) -- [IETF Scrypt](https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-00) (Test vector params are [incorrect](https://twitter.com/dchest/status/247734446881640448).) - - -License -------- - -MIT - - -[wiki]: http://en.wikipedia.org/wiki/Scrypt -[tarsnap]: http://www.tarsnap.com/scrypt.html diff --git a/blue_modules/scryptsy/lib/index.js b/blue_modules/scryptsy/lib/index.js deleted file mode 100644 index 6e630bdbf..000000000 --- a/blue_modules/scryptsy/lib/index.js +++ /dev/null @@ -1,3 +0,0 @@ -const scrypt = require('./scryptSync') -scrypt.async = require('./scrypt') -module.exports = scrypt diff --git a/blue_modules/scryptsy/lib/scrypt.js b/blue_modules/scryptsy/lib/scrypt.js deleted file mode 100644 index bd98ccc69..000000000 --- a/blue_modules/scryptsy/lib/scrypt.js +++ /dev/null @@ -1,26 +0,0 @@ -let pbkdf2 = require('pbkdf2') -const { - checkAndInit, - smix -} = require('./utils') - -// N = Cpu cost, r = Memory cost, p = parallelization cost -async function scrypt (key, salt, N, r, p, dkLen, progressCallback, promiseInterval) { - const { - XY, - V, - B32, - x, - _X, - B, - tickCallback - } = checkAndInit(key, salt, N, r, p, dkLen, progressCallback) - - for (var i = 0; i < p; i++) { - await smix(B, i * 128 * r, r, N, V, XY, _X, B32, x, tickCallback, promiseInterval) - } - - return pbkdf2.pbkdf2Sync(key, B, 1, dkLen, 'sha256') -} - -module.exports = scrypt diff --git a/blue_modules/scryptsy/lib/scryptSync.js b/blue_modules/scryptsy/lib/scryptSync.js deleted file mode 100644 index b4352de88..000000000 --- a/blue_modules/scryptsy/lib/scryptSync.js +++ /dev/null @@ -1,26 +0,0 @@ -let pbkdf2 = require('pbkdf2') -const { - checkAndInit, - smixSync -} = require('./utils') - -// N = Cpu cost, r = Memory cost, p = parallelization cost -function scrypt (key, salt, N, r, p, dkLen, progressCallback) { - const { - XY, - V, - B32, - x, - _X, - B, - tickCallback - } = checkAndInit(key, salt, N, r, p, dkLen, progressCallback) - - for (var i = 0; i < p; i++) { - smixSync(B, i * 128 * r, r, N, V, XY, _X, B32, x, tickCallback) - } - - return pbkdf2.pbkdf2Sync(key, B, 1, dkLen, 'sha256') -} - -module.exports = scrypt diff --git a/blue_modules/scryptsy/lib/utils.js b/blue_modules/scryptsy/lib/utils.js deleted file mode 100644 index 425c4b42a..000000000 --- a/blue_modules/scryptsy/lib/utils.js +++ /dev/null @@ -1,216 +0,0 @@ -let pbkdf2 = require('pbkdf2') -const MAX_VALUE = 0x7fffffff -const DEFAULT_PROMISE_INTERVAL = 5000 -/* eslint-disable camelcase */ - -function checkAndInit (key, salt, N, r, p, dkLen, progressCallback) { - if (N === 0 || (N & (N - 1)) !== 0) throw Error('N must be > 0 and a power of 2') - - if (N > MAX_VALUE / 128 / r) throw Error('Parameter N is too large') - if (r > MAX_VALUE / 128 / p) throw Error('Parameter r is too large') - - let XY = Buffer.alloc(256 * r) - let V = Buffer.alloc(128 * r * N) - - // pseudo global - let B32 = new Int32Array(16) // salsa20_8 - let x = new Int32Array(16) // salsa20_8 - let _X = Buffer.alloc(64) // blockmix_salsa8 - - // pseudo global - let B = pbkdf2.pbkdf2Sync(key, salt, 1, p * 128 * r, 'sha256') - - let tickCallback - if (progressCallback) { - let totalOps = p * N * 2 - let currentOp = 0 - - tickCallback = function () { - ++currentOp - - // send progress notifications once every 1,000 ops - if (currentOp % 1000 === 0) { - progressCallback({ - current: currentOp, - total: totalOps, - percent: (currentOp / totalOps) * 100.0 - }) - } - } - } - return { - XY, - V, - B32, - x, - _X, - B, - tickCallback - } -} - -async function smix (B, Bi, r, N, V, XY, _X, B32, x, tickCallback, promiseInterval) { - promiseInterval = promiseInterval || DEFAULT_PROMISE_INTERVAL - let Xi = 0 - let Yi = 128 * r - let i - - B.copy(XY, Xi, Bi, Bi + Yi) - - for (i = 0; i < N; i++) { - XY.copy(V, i * Yi, Xi, Xi + Yi) - if (i % promiseInterval === 0) { - await new Promise(resolve => setImmediate(resolve)) - } - blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x) - - if (tickCallback) tickCallback() - } - - for (i = 0; i < N; i++) { - let offset = Xi + (2 * r - 1) * 64 - let j = XY.readUInt32LE(offset) & (N - 1) - blockxor(V, j * Yi, XY, Xi, Yi) - if (i % promiseInterval === 0) { - await new Promise(resolve => setImmediate(resolve)) - } - blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x) - - if (tickCallback) tickCallback() - } - - XY.copy(B, Bi, Xi, Xi + Yi) -} - -function smixSync (B, Bi, r, N, V, XY, _X, B32, x, tickCallback) { - let Xi = 0 - let Yi = 128 * r - let i - - B.copy(XY, Xi, Bi, Bi + Yi) - - for (i = 0; i < N; i++) { - XY.copy(V, i * Yi, Xi, Xi + Yi) - blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x) - - if (tickCallback) tickCallback() - } - - for (i = 0; i < N; i++) { - let offset = Xi + (2 * r - 1) * 64 - let j = XY.readUInt32LE(offset) & (N - 1) - blockxor(V, j * Yi, XY, Xi, Yi) - blockmix_salsa8(XY, Xi, Yi, r, _X, B32, x) - - if (tickCallback) tickCallback() - } - - XY.copy(B, Bi, Xi, Xi + Yi) -} - -function blockmix_salsa8 (BY, Bi, Yi, r, _X, B32, x) { - let i - - arraycopy(BY, Bi + (2 * r - 1) * 64, _X, 0, 64) - - for (i = 0; i < 2 * r; i++) { - blockxor(BY, i * 64, _X, 0, 64) - salsa20_8(_X, B32, x) - arraycopy(_X, 0, BY, Yi + (i * 64), 64) - } - - for (i = 0; i < r; i++) { - arraycopy(BY, Yi + (i * 2) * 64, BY, Bi + (i * 64), 64) - } - - for (i = 0; i < r; i++) { - arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64) - } -} - -function R (a, b) { - return (a << b) | (a >>> (32 - b)) -} - -function salsa20_8 (B, B32, x) { - let i - - for (i = 0; i < 16; i++) { - B32[i] = (B[i * 4 + 0] & 0xff) << 0 - B32[i] |= (B[i * 4 + 1] & 0xff) << 8 - B32[i] |= (B[i * 4 + 2] & 0xff) << 16 - B32[i] |= (B[i * 4 + 3] & 0xff) << 24 - // B32[i] = B.readUInt32LE(i*4) <--- this is signficantly slower even in Node.js - } - - arraycopy(B32, 0, x, 0, 16) - - for (i = 8; i > 0; i -= 2) { - x[4] ^= R(x[0] + x[12], 7) - x[8] ^= R(x[4] + x[0], 9) - x[12] ^= R(x[8] + x[4], 13) - x[0] ^= R(x[12] + x[8], 18) - x[9] ^= R(x[5] + x[1], 7) - x[13] ^= R(x[9] + x[5], 9) - x[1] ^= R(x[13] + x[9], 13) - x[5] ^= R(x[1] + x[13], 18) - x[14] ^= R(x[10] + x[6], 7) - x[2] ^= R(x[14] + x[10], 9) - x[6] ^= R(x[2] + x[14], 13) - x[10] ^= R(x[6] + x[2], 18) - x[3] ^= R(x[15] + x[11], 7) - x[7] ^= R(x[3] + x[15], 9) - x[11] ^= R(x[7] + x[3], 13) - x[15] ^= R(x[11] + x[7], 18) - x[1] ^= R(x[0] + x[3], 7) - x[2] ^= R(x[1] + x[0], 9) - x[3] ^= R(x[2] + x[1], 13) - x[0] ^= R(x[3] + x[2], 18) - x[6] ^= R(x[5] + x[4], 7) - x[7] ^= R(x[6] + x[5], 9) - x[4] ^= R(x[7] + x[6], 13) - x[5] ^= R(x[4] + x[7], 18) - x[11] ^= R(x[10] + x[9], 7) - x[8] ^= R(x[11] + x[10], 9) - x[9] ^= R(x[8] + x[11], 13) - x[10] ^= R(x[9] + x[8], 18) - x[12] ^= R(x[15] + x[14], 7) - x[13] ^= R(x[12] + x[15], 9) - x[14] ^= R(x[13] + x[12], 13) - x[15] ^= R(x[14] + x[13], 18) - } - - for (i = 0; i < 16; ++i) B32[i] = x[i] + B32[i] - - for (i = 0; i < 16; i++) { - let bi = i * 4 - B[bi + 0] = (B32[i] >> 0 & 0xff) - B[bi + 1] = (B32[i] >> 8 & 0xff) - B[bi + 2] = (B32[i] >> 16 & 0xff) - B[bi + 3] = (B32[i] >> 24 & 0xff) - // B.writeInt32LE(B32[i], i*4) //<--- this is signficantly slower even in Node.js - } -} - -// naive approach... going back to loop unrolling may yield additional performance -function blockxor (S, Si, D, Di, len) { - for (let i = 0; i < len; i++) { - D[Di + i] ^= S[Si + i] - } -} - -function arraycopy (src, srcPos, dest, destPos, length) { - if (Buffer.isBuffer(src) && Buffer.isBuffer(dest)) { - src.copy(dest, destPos, srcPos, srcPos + length) - } else { - while (length--) { - dest[destPos++] = src[srcPos++] - } - } -} - -module.exports = { - checkAndInit, - smix, - smixSync -} diff --git a/blue_modules/scryptsy/package.json b/blue_modules/scryptsy/package.json deleted file mode 100644 index a34d5468b..000000000 --- a/blue_modules/scryptsy/package.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "_from": "scryptsy@2.1.0", - "_id": "scryptsy@2.1.0", - "_inBundle": false, - "_integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==", - "_location": "/scryptsy", - "_phantomChildren": {}, - "_requested": { - "type": "version", - "registry": true, - "raw": "scryptsy@2.1.0", - "name": "scryptsy", - "escapedName": "scryptsy", - "rawSpec": "2.1.0", - "saveSpec": null, - "fetchSpec": "2.1.0" - }, - "_requiredBy": [ - "#USER", - "/" - ], - "_resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", - "_shasum": "8d1e8d0c025b58fdd25b6fa9a0dc905ee8faa790", - "_spec": "scryptsy@2.1.0", - "_where": "/home/overtorment/Documents/BlueWallet", - "author": "", - "bugs": { - "url": "https://github.com/cryptocoinjs/scryptsy/issues" - }, - "bundleDependencies": false, - "dependencies": {}, - "deprecated": false, - "description": "Pure JavaScript implementation of the scrypt key deriviation function that is fully compatible with Node.js and the browser.", - "devDependencies": {}, - "files": [ - "lib" - ], - "homepage": "https://github.com/cryptocoinjs/scryptsy#readme", - "keywords": [ - "crytpo", - "cryptography", - "scrypt", - "kdf", - "litecoin", - "dogecoin", - "bitcoin", - "bip38" - ], - "license": "MIT", - "main": "lib/index.js", - "name": "scryptsy", - "repository": { - "url": "git+ssh://git@github.com/cryptocoinjs/scryptsy.git", - "type": "git" - }, - "scripts": { - "browser-test": "mochify --wd -R spec", - "coverage": "nyc --check-coverage --statements 80 --branches 60 --functions 90 --lines 90 mocha", - "coveralls": "npm run-script coverage && coveralls < coverage/lcov.info", - "lint": "standard", - "test": "mocha --ui bdd", - "unit": "mocha" - }, - "version": "2.1.0" -} diff --git a/blue_modules/slip39/.eslintrc.js b/blue_modules/slip39/.eslintrc.js deleted file mode 100644 index 438c5c0e8..000000000 --- a/blue_modules/slip39/.eslintrc.js +++ /dev/null @@ -1,197 +0,0 @@ -//module.exports = { "extends": "standard" }; -module.exports = { - "env": { - "browser": true, - "node": true, - "es6": true - }, - "parserOptions": { - "sourceType": "module", - }, - "rules": { - - // - //Possible Errors - // - // The following rules point out areas where you might have made mistakes. - // - "comma-dangle": 2, // disallow or enforce trailing commas - "no-cond-assign": 2, // disallow assignment in conditional expressions - // NOTE: "no-console": 1, // disallow use of console (off by default in the node environment) - "no-constant-condition": 2, // disallow use of constant expressions in conditions - "no-control-regex": 2, // disallow control characters in regular expressions - "no-debugger": 2, // disallow use of debugger - "no-dupe-args": 2, // disallow duplicate arguments in functions - "no-dupe-keys": 2, // disallow duplicate keys when creating object literals - "no-duplicate-case": 2, // disallow a duplicate case label. - "no-empty": 2, // disallow empty statements - "no-empty-character-class": 2, // disallow the use of empty character classes in regular expressions - "no-ex-assign": 2, // disallow assigning to the exception in a catch block - "no-extra-boolean-cast": 2, // disallow double-negation boolean casts in a boolean context - "no-extra-parens": 0, // disallow unnecessary parentheses (off by default) - "no-extra-semi": 2, // disallow unnecessary semicolons - "no-func-assign": 2, // disallow overwriting functions written as function declarations - "no-inner-declarations": 2, // disallow function or variable declarations in nested blocks - "no-invalid-regexp": 2, // disallow invalid regular expression strings in the RegExp constructor - "no-irregular-whitespace": 2, // disallow irregular whitespace outside of strings and comments - "no-negated-in-lhs": 2, // disallow negation of the left operand of an in expression - "no-obj-calls": 2, // disallow the use of object properties of the global object (Math and JSON) as functions - "no-regex-spaces": 2, // disallow multiple spaces in a regular expression literal - "quote-props": 2, // disallow reserved words being used as object literal keys (off by default) - "no-sparse-arrays": 2, // disallow sparse arrays - "no-unreachable": 2, // disallow unreachable statements after a return, throw, continue, or break statement - "use-isnan": 2, // disallow comparisons with the value NaN - "valid-jsdoc": 2, // Ensure JSDoc comments are valid (off by default) - "valid-typeof": 2, // Ensure that the results of typeof are compared against a valid string - - // - // Best Practices - // - // These are rules designed to prevent you from making mistakes. - // They either prescribe a better way of doing something or help you avoid footguns. - // - "block-scoped-var": 0, // treat var statements as if they were block scoped (off by default). 0: deep destructuring is not compatible https://github.com/eslint/eslint/issues/1863 - "complexity": 0, // specify the maximum cyclomatic complexity allowed in a program (off by default) - //"consistent-return": 2, // require return statements to either always or never specify values - "curly": 2, // specify curly brace conventions for all control statements - "default-case": 2, // require default case in switch statements (off by default) - "dot-notation": 2, // encourages use of dot notation whenever possible - "eqeqeq": 2, // require the use of === and !== - //"guard-for-in": 2, // make sure for-in loops have an if statement (off by default) - "no-alert": 2, // disallow the use of alert, confirm, and prompt - "no-caller": 2, // disallow use of arguments.caller or arguments.callee - "no-div-regex": 2, // disallow division operators explicitly at beginning of regular expression (off by default) - "no-else-return": 2, // disallow else after a return in an if (off by default) - "no-labels": 2, // disallow use of labels for anything other then loops and switches - "no-eq-null": 2, // disallow comparisons to null without a type-checking operator (off by default) - "no-eval": 2, // disallow use of eval() - //"no-extend-native": [0, { "exceptions": ["Object", "String"] }], // disallow adding to native types - "no-extra-bind": 2, // disallow unnecessary function binding - "no-fallthrough": 2, // disallow fallthrough of case statements - "no-floating-decimal": 2, // disallow the use of leading or trailing decimal points in numeric literals (off by default) - "no-implied-eval": 2, // disallow use of eval()-like methods - "no-iterator": 2, // disallow usage of __iterator__ property - "no-labels": 2, // disallow use of labeled statements - "no-lone-blocks": 2, // disallow unnecessary nested blocks - "no-loop-func": 2, // disallow creation of functions within loops - "no-multi-spaces": 2, // disallow use of multiple spaces - "no-multi-str": 2, // disallow use of multiline strings - "no-native-reassign": 2, // disallow reassignments of native objects - "no-new": 2, // disallow use of new operator when not part of the assignment or comparison - "no-new-func": 2, // disallow use of new operator for Function object - "no-new-wrappers": 2, // disallows creating new instances of String,Number, and Boolean - "no-octal": 2, // disallow use of octal literals - "no-octal-escape": 2, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251"; - "no-param-reassign": 2, // disallow reassignment of function parameters (off by default) - "no-process-env": 2, // disallow use of process.env (off by default) - "no-proto": 2, // disallow usage of __proto__ property - "no-redeclare": 2, // disallow declaring the same variable more then once - "no-return-assign": 2, // disallow use of assignment in return statement - "no-script-url": 2, // disallow use of javascript: urls. - "no-self-compare": 2, // disallow comparisons where both sides are exactly the same (off by default) - "no-sequences": 2, // disallow use of comma operator - "no-throw-literal": 2, // restrict what can be thrown as an exception (off by default) - //"no-unused-expressions": 2, // disallow usage of expressions in statement position - "no-void": 2, // disallow use of void operator (off by default) - "no-warning-comments": [0, {"terms": ["todo", "fixme"], "location": "start"}], // disallow usage of configurable warning terms in comments": 2, // e.g. TODO or FIXME (off by default) - "no-with": 2, // disallow use of the with statement - "radix": 1, // require use of the second argument for parseInt() (off by default) - "vars-on-top": 2, // requires to declare all vars on top of their containing scope (off by default) - "wrap-iife": 2, // require immediate function invocation to be wrapped in parentheses (off by default) - "yoda": 2, // require or disallow Yoda conditions - - // - // Strict Mode - // - // These rules relate to using strict mode. - // - "strict": 0, // controls location of Use Strict Directives. 0: required by `babel-eslint` - - // - // Variables - // - // These rules have to do with variable declarations. - // - "no-catch-shadow": 2, // disallow the catch clause parameter name being the same as a variable in the outer scope (off by default in the node environment) - "no-delete-var": 2, // disallow deletion of variables - "no-label-var": 2, // disallow labels that share a name with a variable - "no-shadow": 2, // disallow declaration of variables already declared in the outer scope - "no-shadow-restricted-names": 2, // disallow shadowing of names such as arguments - // "no-undef": 2, // disallow use of undeclared variables unless mentioned in a /*global */ block - "no-undef-init": 2, // disallow use of undefined when initializing variables - "no-undefined": 2, // disallow use of undefined variable (off by default) - "no-unused-vars": 2, // disallow declaration of variables that are not used in the code - // "no-use-before-define": 2, // disallow use of variables before they are defined - - // - //Stylistic Issues - // - // These rules are purely matters of style and are quite subjective. - // - "indent": [1, 2], // this option sets a specific tab width for your code (off by default) - "brace-style": 1, // enforce one true brace style (off by default) - "camelcase": 1, // require camel case names - "comma-spacing": [1, {"before": false, "after": true}], // enforce spacing before and after comma - "comma-style": [1, "last"], // enforce one true comma style (off by default) - "consistent-this": [1, "_this"], // enforces consistent naming when capturing the current execution context (off by default) - "eol-last": 1, // enforce newline at the end of file, with no multiple empty lines - "func-names": 0, // require function expressions to have a name (off by default) - "func-style": 0, // enforces use of function declarations or expressions (off by default) - "key-spacing": [1, {"beforeColon": false, "afterColon": true}], // enforces spacing between keys and values in object literal properties - //"max-nested-callbacks": [1, 3], // specify the maximum depth callbacks can be nested (off by default) - "new-cap": [1, {newIsCap: true, capIsNew: false}], // require a capital letter for constructors - "new-parens": 1, // disallow the omission of parentheses when invoking a constructor with no arguments - "newline-after-var": 0, // allow/disallow an empty newline after var statement (off by default) - //"no-array-constructor": 1, // disallow use of the Array constructor - "no-inline-comments": 1, // disallow comments inline after code (off by default) - "no-lonely-if": 1, // disallow if as the only statement in an else block (off by default) - "no-mixed-spaces-and-tabs": 1, // disallow mixed spaces and tabs for indentation - "no-multiple-empty-lines": [1, {"max": 2}], // disallow multiple empty lines (off by default) - "no-nested-ternary": 1, // disallow nested ternary expressions (off by default) - "no-new-object": 1, // disallow use of the Object constructor - "no-spaced-func": 1, // disallow space between function identifier and application - "no-ternary": 0, // disallow the use of ternary operators (off by default) - "no-trailing-spaces": 1, // disallow trailing whitespace at the end of lines - "no-underscore-dangle": 1, // disallow dangling underscores in identifiers - "no-extra-parens": 1, // disallow wrapping of non-IIFE statements in parens - "one-var": [1, "never"], // allow just one var statement per function (off by default) - "operator-assignment": [1, "never"], // require assignment operator shorthand where possible or prohibit it entirely (off by default) - "padded-blocks": [1, "never"], // enforce padding within blocks (off by default) - "quote-props": [1, "as-needed"], // require quotes around object literal property names (off by default) - "quotes": [1, "single"], // specify whether double or single quotes should be used - "semi": [1, "always"], // require or disallow use of semicolons instead of ASI - "semi-spacing": [1, {"before": false, "after": true}], // enforce spacing before and after semicolons - "sort-vars": 0, // sort variables within the same declaration block (off by default) - "space-before-blocks": [1, "always"], // require or disallow space before blocks (off by default) - "space-before-function-paren": [1, {"anonymous": "always", "named": "never"}], // require or disallow space before function opening parenthesis (off by default) - "object-curly-spacing": [1, "never"], // require or disallow spaces inside brackets (off by default) - "space-in-parens": [1, "never"], // require or disallow spaces inside parentheses (off by default) - //"space-infix-ops": [1, "always"], // require spaces around operators - "keyword-spacing": 2, // require a space after return, throw, and case - "space-unary-ops": [1, {"words": true, "nonwords": false}], // Require or disallow spaces before/after unary operators (words on by default, nonwords off by default) - "spaced-comment": [1, "always"], // require or disallow a space immediately following the // in a line comment (off by default) - "wrap-regex": 0, // require regex literals to be wrapped in parentheses (off by default) - - // - // ECMAScript 6 - // - // These rules are only relevant to ES6 environments and are off by default. - // - "no-var": 2, // require let or const instead of var (off by default) - "generator-star-spacing": [2, "before"], // enforce the spacing around the * in generator functions (off by default) - - // - // Legacy - // - // The following rules are included for compatibility with JSHint and JSLint. - // While the names of the rules may not match up with the JSHint/JSLint counterpart, - // the functionality is the same. - // - "max-depth": [2, 3], // specify the maximum depth that blocks can be nested (off by default) - "max-len": [2, 100, 2, { "ignoreStrings": true, "ignoreTemplateLiterals": true, "ignoreUrls": true }], // specify the maximum length of a line in your program (off by default) - "max-params": [2, 8], // limits the number of parameters that can be used in the function declaration. (off by default) - "max-statements": 0, // specify the maximum number of statement allowed in a function (off by default) - "no-bitwise": 0, // disallow use of bitwise operators (off by default) - //"no-plusplus": 2, // disallow use of unary operators, ++ and -- (off by default) - } -} diff --git a/blue_modules/slip39/CHANGELOG.md b/blue_modules/slip39/CHANGELOG.md deleted file mode 100644 index f1df46012..000000000 --- a/blue_modules/slip39/CHANGELOG.md +++ /dev/null @@ -1,25 +0,0 @@ -v0.1.0 -* Initial release - -v0.1.1 -* Code clean up and addes some unit tests - -v0.1.2 -* Added length to encodeBigInt() - -v0.1.5-dev.1 -* Bumped version, changed versioning format - -v0.1.5 -* Bumped version -* Added nodejs.yml -* Merge pull requests from different contributors - -v0.1.6 -* Fixed ilap/slip39-js#12 -* Some cosmetic fixes - -v0.1.7 -* Merge pull requests from different contributors -* Fixed ilap/slip39-js#14 -* Fixed ilap/slip39-js#18 diff --git a/blue_modules/slip39/LICENSE b/blue_modules/slip39/LICENSE deleted file mode 100644 index 4a356fdc1..000000000 --- a/blue_modules/slip39/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 Pal Dorogi "ilap" - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/blue_modules/slip39/README.md b/blue_modules/slip39/README.md deleted file mode 100644 index 680f06f9d..000000000 --- a/blue_modules/slip39/README.md +++ /dev/null @@ -1,261 +0,0 @@ -# Bluewallet - -It's original [slip39](https://github.com/ilap/slip39-js/) but compiled with `babel-plugin-transform-bigint` to replace js `BigInt` with `JSBI`. - -To update: -- sync src folder -- run `npm build` - - -# SLIP39 - -[![npm](https://img.shields.io/npm/v/slip39.svg)](https://www.npmjs.org/package/slip39) - - -The javascript implementation of the [SLIP39](https://github.com/satoshilabs/slips/blob/master/slip-0039.md) for Shamir's Secret-Sharing for Mnemonic Codes. - -The code based on my [Dart implementation of SLIP-0039](https://github.com/ilap/slip39-dart/). - -# DISCLAIMER - -This project is still in early development phase. Use it at your own risk. - -## Description - - This SLIP39 implementation uses a 3 level height (l=3) of a 16 degree (d=16) tree (T), which is represented as an array of the level two nodes (groups, G). - - The degree (d) and the level (l) of the tree are 16 and 3 respectively, - which means that max d^(l-1), i.e. 16^2, leaf nodes (M) can be in a complete tree (or forest). - - The first level (l=1) node of the tree is the the root (R), the level 2 ones are the `SSS` groups (Gs or group nodes) e.g. `[G0, ..., Gd]`. - - The last, the third, level nodes are the only leafs (M, group members) which contains the generated mnemonics. - - Every node has two values: - - the N and - - M i.e. n(N,M). - - Whihc means, that N (`threshold`) number of M children are required to reconstruct the node's secret. - -## Format - -The tree's human friendly array representation only uses the group (l=2) nodes as arrays. -For example. : ``` [[1,1], [1,1], [3,5], [2,6]]``` -The group's first parameter is the `N` (group threshold) while the second is the `M`, the number of members in the group. See, and example in [Using](#Using). - -## Installing - -``` -npm install slip39 - -``` - -## Using -See `example/main.js` - - ``` javascript -const slip39 = require('../src/slip39.js'); -const assert = require('assert'); -// threshold (N) number of group shares required to reconstruct the master secret. -const threshold = 2; -const masterSecret = 'ABCDEFGHIJKLMNOP'.slip39EncodeHex(); -const passphrase = 'TREZOR'; - -/** - * 4 groups shares. - * = two for Alice - * = one for friends and - * = one for family members - * Two of these group shares are required to reconstruct the master secret. - */ -const groups = [ - // Alice group shares. 1 is enough to reconstruct a group share, - // therefore she needs at least two group shares to be reconstructed, - [1, 1], - [1, 1], - // 3 of 5 Friends' shares are required to reconstruct this group share - [3, 5], - // 2 of 6 Family's shares are required to reconstruct this group share - [2, 6] -]; - -const slip = slip39.fromArray({ - masterSecret: masterSecret, - passphrase: passphrase, - threshold: threshold, - groups: groups -}); - -// One of Alice's share -const aliceShare = slip.fromPath('r/0').mnemonics; - -// and any two of family's shares. -const familyShares = slip.fromPath('r/3/1').mnemonics - .concat(slip.fromPath('r/3/3').mnemonics); - -const allShares = aliceShare.concat(familyShares); - -console.log('Shares used for restoring the master secret:'); -allShares.forEach((s) => console.log(s)); - -const recoveredSecret = slip39.recoverSecret(allShares, passphrase); -console.log('Master secret: ' + masterSecret.slip39DecodeHex()); -console.log('Recovered one: ' + recoveredSecret.slip39DecodeHex()); -assert(masterSecret.slip39DecodeHex() === recoveredSecret.slip39DecodeHex()); -``` - -## Testing - -``` bash - $ npm install - $ npm test - - Basic Tests - Test threshold 1 with 5 of 7 shares of a group combinations - ✓ Test combination 0 1 2 3 4. - ✓ Test combination 0 1 2 3 5. - ✓ Test combination 0 1 2 3 6. - ✓ Test combination 0 1 2 4 5. - ✓ Test combination 0 1 2 4 6. - ✓ Test combination 0 1 2 5 6. - ✓ Test combination 0 1 3 4 5. - ✓ Test combination 0 1 3 4 6. - ✓ Test combination 0 1 3 5 6. - ✓ Test combination 0 1 4 5 6. - ✓ Test combination 0 2 3 4 5. - ✓ Test combination 0 2 3 4 6. - ✓ Test combination 0 2 3 5 6. - ✓ Test combination 0 2 4 5 6. - ✓ Test combination 0 3 4 5 6. - ✓ Test combination 1 2 3 4 5. - ✓ Test combination 1 2 3 4 6. - ✓ Test combination 1 2 3 5 6. - ✓ Test combination 1 2 4 5 6. - ✓ Test combination 1 3 4 5 6. - ✓ Test combination 2 3 4 5 6. - Test passhrase - ✓ should return valid mastersecret when user submits valid passphrse - ✓ should NOT return valid mastersecret when user submits invalid passphrse - ✓ should return valid mastersecret when user does not submit passphrse - Test iteration exponent - ✓ should return valid mastersecret when user apply valid iteration exponent (44ms) - ✓ should throw an Error when user submits invalid iteration exponent - - Group Shares Tests - Test all valid combinations of mnemonics - ✓ should return the valid mastersecret when valid mnemonics used for recovery - Original test vectors Tests - ✓ 1. Valid mnemonic without sharing (128 bits) - ✓ 2. Mnemonic with invalid checksum (128 bits) - ✓ 3. Mnemonic with invalid padding (128 bits) - ✓ 4. Basic sharing 2-of-3 (128 bits) - ✓ 5. Basic sharing 2-of-3 (128 bits) - ✓ 6. Mnemonics with different identifiers (128 bits) - ✓ 7. Mnemonics with different iteration exponents (128 bits) - ✓ 8. Mnemonics with mismatching group thresholds (128 bits) - ✓ 9. Mnemonics with mismatching group counts (128 bits) - ✓ 10. Mnemonics with greater group threshold than group counts (128 bits) - ✓ 11. Mnemonics with duplicate member indices (128 bits) - ✓ 12. Mnemonics with mismatching member thresholds (128 bits) - ✓ 13. Mnemonics giving an invalid digest (128 bits) - ✓ 14. Insufficient number of groups (128 bits, case 1) - ✓ 15. Insufficient number of groups (128 bits, case 2) - ✓ 16. Threshold number of groups, but insufficient number of members in one group (128 bits) - ✓ 17. Threshold number of groups and members in each group (128 bits, case 1) - ✓ 18. Threshold number of groups and members in each group (128 bits, case 2) - ✓ 19. Threshold number of groups and members in each group (128 bits, case 3) - ✓ 20. Valid mnemonic without sharing (256 bits) - ✓ 21. Mnemonic with invalid checksum (256 bits) - ✓ 22. Mnemonic with invalid padding (256 bits) - ✓ 23. Basic sharing 2-of-3 (256 bits) - ✓ 24. Basic sharing 2-of-3 (256 bits) - ✓ 25. Mnemonics with different identifiers (256 bits) - ✓ 26. Mnemonics with different iteration exponents (256 bits) - ✓ 27. Mnemonics with mismatching group thresholds (256 bits) - ✓ 28. Mnemonics with mismatching group counts (256 bits) - ✓ 29. Mnemonics with greater group threshold than group counts (256 bits) - ✓ 30. Mnemonics with duplicate member indices (256 bits) - ✓ 31. Mnemonics with mismatching member thresholds (256 bits) - ✓ 32. Mnemonics giving an invalid digest (256 bits) - ✓ 33. Insufficient number of groups (256 bits, case 1) - ✓ 34. Insufficient number of groups (256 bits, case 2) - ✓ 35. Threshold number of groups, but insufficient number of members in one group (256 bits) - ✓ 36. Threshold number of groups and members in each group (256 bits, case 1) - ✓ 37. Threshold number of groups and members in each group (256 bits, case 2) - ✓ 38. Threshold number of groups and members in each group (256 bits, case 3) - ✓ 39. Mnemonic with insufficient length - ✓ 40. Mnemonic with invalid master secret length - Invalid Shares - ✓ Short master secret - ✓ Odd length master secret - ✓ Group threshold exceeds number of groups - ✓ Invalid group threshold. - ✓ Member threshold exceeds number of members - ✓ Invalid member threshold - ✓ Group with multiple members and threshold 1 - - - 74 passing (477ms) - -``` - -## TODOS - -- [x] Add unit tests. -- [x] Test with the reference code's test vectors. -- [ ] Refactor the helpers to different helper classes e.g. `CryptoHelper()`, `ShamirHelper()` etc. -- [ ] Add `JSON` representation, see [JSON representation](#json-representation) below. -- [ ] Refactor to much simpler code. - -### JSON Representation - -``` json - { - "name": "Slip39", - "threshold": 2, - "shares": [ - { - "name": "My Primary", - "threshold": 1, - "shares": [ - "Primary" - ] - }, - { - "name": "My Secondary", - "threshold": 1, - "shares": [ - "Secondary" - ] - }, - { - "name": "Friends", - "threshold": 3, - "shares": [ - "Alice", - "Bob", - "Charlie", - "David", - "Erin" - ] - }, - { - "name": "Family", - "threshold": 2, - "shares": [ - "Adam", - "Brenda", - "Carol", - "Dan", - "Edward", - "Frank" - ] - } - ] -} -``` -# LICENSE - -CopyRight (c) 2019 Pal Dorogi `"iLap"` - -[MIT License](LICENSE) diff --git a/blue_modules/slip39/babel.config.js b/blue_modules/slip39/babel.config.js deleted file mode 100644 index d53544472..000000000 --- a/blue_modules/slip39/babel.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - "plugins": [ - "babel-plugin-transform-bigint", - ] -} diff --git a/blue_modules/slip39/dist/slip39.js b/blue_modules/slip39/dist/slip39.js deleted file mode 100644 index 5bd1aca37..000000000 --- a/blue_modules/slip39/dist/slip39.js +++ /dev/null @@ -1,236 +0,0 @@ -var maybeJSBI = { - BigInt: function BigInt(a) { - return JSBI.BigInt(a); - }, - toNumber: function toNumber(a) { - return typeof a === "object" ? JSBI.toNumber(a) : Number(a); - }, - add: function add(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.add(a, b) : a + b; - }, - subtract: function subtract(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.subtract(a, b) : a - b; - }, - multiply: function multiply(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.multiply(a, b) : a * b; - }, - divide: function divide(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.divide(a, b) : a / b; - }, - remainder: function remainder(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.remainder(a, b) : a % b; - }, - exponentiate: function exponentiate(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.exponentiate(a, b) : typeof a === "bigint" && typeof b === "bigint" ? new Function("a**b", "a", "b")(a, b) : Math.pow(a, b); - }, - leftShift: function leftShift(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.leftShift(a, b) : a << b; - }, - signedRightShift: function signedRightShift(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.signedRightShift(a, b) : a >> b; - }, - bitwiseAnd: function bitwiseAnd(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.bitwiseAnd(a, b) : a & b; - }, - bitwiseOr: function bitwiseOr(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.bitwiseOr(a, b) : a | b; - }, - bitwiseXor: function bitwiseXor(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.bitwiseXor(a, b) : a ^ b; - }, - lessThan: function lessThan(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.lessThan(a, b) : a < b; - }, - greaterThan: function greaterThan(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.greaterThan(a, b) : a > b; - }, - lessThanOrEqual: function lessOrEqualThan(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.lessThanOrEqual(a, b) : a <= b; - }, - greaterThanOrEqual: function greaterOrEqualThan(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.greaterThanOrEqual(a, b) : a >= b; - }, - equal: function equal(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.equal(a, b) : a === b; - }, - notEqual: function notEqual(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.notEqual(a, b) : a !== b; - }, - unaryMinus: function unaryMinus(a) { - return typeof a === "object" ? JSBI.unaryMinus(a) : -a; - }, - bitwiseNot: function bitwiseNot(a) { - return typeof a === "object" ? JSBI.bitwiseNot(a) : ~a; - } -}; -const JSBI = require("jsbi/dist/jsbi-cjs.js"); - -/* eslint-disable radix */ -const slipHelper = require('./slip39_helper.js'); - -const MAX_DEPTH = 2; -/** - * Slip39Node - * For root node, description refers to the whole set's title e.g. "Hardware wallet X SSSS shares" - * For children nodes, description refers to the group e.g. "Family group: mom, dad, sister, wife" - */ - -class Slip39Node { - constructor(index = 0, description = '', mnemonic = '', children = []) { - this.index = index; - this.description = description; - this.mnemonic = mnemonic; - this.children = children; - } - - get mnemonics() { - if (this.children.length === 0) { - return [this.mnemonic]; - } - - const result = this.children.reduce((prev, item) => { - return prev.concat(item.mnemonics); - }, []); - return result; - } - -} // -// The javascript implementation of the SLIP-0039: Shamir's Secret-Sharing for Mnemonic Codes -// see: https://github.com/satoshilabs/slips/blob/master/slip-0039.md) -// - - -class Slip39 { - constructor({ - iterationExponent = 0, - identifier, - groupCount, - groupThreshold - } = {}) { - this.iterationExponent = iterationExponent; - this.identifier = identifier; - this.groupCount = groupCount; - this.groupThreshold = groupThreshold; - } - - static fromArray(masterSecret, { - passphrase = '', - threshold = 1, - groups = [[1, 1, 'Default 1-of-1 group share']], - iterationExponent = 0, - title = 'My default slip39 shares' - } = {}) { - if (masterSecret.length * 8 < slipHelper.MIN_ENTROPY_BITS) { - throw Error(`The length of the master secret (${masterSecret.length} bytes) must be at least ${slipHelper.bitsToBytes(slipHelper.MIN_ENTROPY_BITS)} bytes.`); - } - - if (masterSecret.length % 2 !== 0) { - throw Error('The length of the master secret in bytes must be an even number.'); - } - - if (!/^[\x20-\x7E]*$/.test(passphrase)) { - throw Error('The passphrase must contain only printable ASCII characters (code points 32-126).'); - } - - if (maybeJSBI.greaterThan(threshold, groups.length)) { - throw Error(`The requested group threshold (${threshold}) must not exceed the number of groups (${groups.length}).`); - } - - groups.forEach(item => { - if (item[0] === 1 && item[1] > 1) { - throw Error(`Creating multiple member shares with member threshold 1 is not allowed. Use 1-of-1 member sharing instead. ${groups.join()}`); - } - }); - const identifier = slipHelper.generateIdentifier(); - const slip = new Slip39({ - iterationExponent: iterationExponent, - identifier: identifier, - groupCount: groups.length, - groupThreshold: threshold - }); - const encryptedMasterSecret = slipHelper.crypt(masterSecret, passphrase, iterationExponent, slip.identifier); - const root = slip.buildRecursive(new Slip39Node(0, title), groups, encryptedMasterSecret, threshold); - slip.root = root; - return slip; - } - - buildRecursive(currentNode, nodes, secret, threshold, index) { - // It means it's a leaf. - if (nodes.length === 0) { - const mnemonic = slipHelper.encodeMnemonic(this.identifier, this.iterationExponent, index, this.groupThreshold, this.groupCount, currentNode.index, threshold, secret); - currentNode.mnemonic = mnemonic; - return currentNode; - } - - const secretShares = slipHelper.splitSecret(threshold, nodes.length, secret); - let children = []; - let idx = 0; - nodes.forEach(item => { - // n=threshold - const n = item[0]; // m=members - - const m = item[1]; // d=description - - const d = item[2] || ''; // Generate leaf members, means their `m` is `0` - - const members = Array().slip39Generate(m, () => [n, 0, d]); - const node = new Slip39Node(idx, d); - const branch = this.buildRecursive(node, members, secretShares[idx], n, currentNode.index); - children = children.concat(branch); - idx = idx + 1; - }); - currentNode.children = children; - return currentNode; - } - - static recoverSecret(mnemonics, passphrase) { - return slipHelper.combineMnemonics(mnemonics, passphrase); - } - - static validateMnemonic(mnemonic) { - return slipHelper.validateMnemonic(mnemonic); - } - - fromPath(path) { - this.validatePath(path); - const children = this.parseChildren(path); - - if (typeof children === 'undefined' || children.length === 0) { - return this.root; - } - - return children.reduce((prev, childNumber) => { - let childrenLen = prev.children.length; - - if (childNumber >= childrenLen) { - throw new Error(`The path index (${childNumber}) exceeds the children index (${childrenLen - 1}).`); - } - - return prev.children[childNumber]; - }, this.root); - } - - validatePath(path) { - if (!path.match(/(^r)(\/\d{1,2}){0,2}$/)) { - throw new Error('Expected valid path e.g. "r/0/0".'); - } - - const depth = path.split('/'); - const pathLength = depth.length - 1; - - if (pathLength > MAX_DEPTH) { - throw new Error(`Path\'s (${path}) max depth (${MAX_DEPTH}) is exceeded (${pathLength}).`); - } - } - - parseChildren(path) { - const splitted = path.split('/').slice(1); - const result = splitted.map(pathFragment => { - return parseInt(pathFragment); - }); - return result; - } - -} - -exports = module.exports = Slip39; \ No newline at end of file diff --git a/blue_modules/slip39/dist/slip39_helper.js b/blue_modules/slip39/dist/slip39_helper.js deleted file mode 100644 index e8a91a9ac..000000000 --- a/blue_modules/slip39/dist/slip39_helper.js +++ /dev/null @@ -1,686 +0,0 @@ -var maybeJSBI = { - BigInt: function BigInt(a) { - return JSBI.BigInt(a); - }, - toNumber: function toNumber(a) { - return typeof a === "object" ? JSBI.toNumber(a) : Number(a); - }, - add: function add(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.add(a, b) : a + b; - }, - subtract: function subtract(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.subtract(a, b) : a - b; - }, - multiply: function multiply(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.multiply(a, b) : a * b; - }, - divide: function divide(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.divide(a, b) : a / b; - }, - remainder: function remainder(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.remainder(a, b) : a % b; - }, - exponentiate: function exponentiate(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.exponentiate(a, b) : typeof a === "bigint" && typeof b === "bigint" ? new Function("a**b", "a", "b")(a, b) : Math.pow(a, b); - }, - leftShift: function leftShift(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.leftShift(a, b) : a << b; - }, - signedRightShift: function signedRightShift(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.signedRightShift(a, b) : a >> b; - }, - bitwiseAnd: function bitwiseAnd(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.bitwiseAnd(a, b) : a & b; - }, - bitwiseOr: function bitwiseOr(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.bitwiseOr(a, b) : a | b; - }, - bitwiseXor: function bitwiseXor(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.bitwiseXor(a, b) : a ^ b; - }, - lessThan: function lessThan(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.lessThan(a, b) : a < b; - }, - greaterThan: function greaterThan(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.greaterThan(a, b) : a > b; - }, - lessThanOrEqual: function lessOrEqualThan(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.lessThanOrEqual(a, b) : a <= b; - }, - greaterThanOrEqual: function greaterOrEqualThan(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.greaterThanOrEqual(a, b) : a >= b; - }, - equal: function equal(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.equal(a, b) : a === b; - }, - notEqual: function notEqual(a, b) { - return typeof a === "object" && typeof b === "object" ? JSBI.notEqual(a, b) : a !== b; - }, - unaryMinus: function unaryMinus(a) { - return typeof a === "object" ? JSBI.unaryMinus(a) : -a; - }, - bitwiseNot: function bitwiseNot(a) { - return typeof a === "object" ? JSBI.bitwiseNot(a) : ~a; - } -}; -const JSBI = require("jsbi/dist/jsbi-cjs.js"); - -/* eslint-disable no-array-constructor */ -const pbkdf2 = require('pbkdf2'); - -const createHmac = require('create-hmac'); - -const randombytes = require('randombytes'); // The length of the radix in bits. - - -const RADIX_BITS = 10; // The length of the random identifier in bits. - -const ID_BITS_LENGTH = 15; // The length of the iteration exponent in bits. - -const ITERATION_EXP_BITS_LENGTH = 5; // The length of the random identifier and iteration exponent in words. - -const ITERATION_EXP_WORDS_LENGTH = parseInt((ID_BITS_LENGTH + ITERATION_EXP_BITS_LENGTH + RADIX_BITS - 1) / RADIX_BITS, 10); // The maximum iteration exponent - -const MAX_ITERATION_EXP = Math.pow(2, ITERATION_EXP_BITS_LENGTH); // The maximum number of shares that can be created. - -const MAX_SHARE_COUNT = 16; // The length of the RS1024 checksum in words. - -const CHECKSUM_WORDS_LENGTH = 3; // The length of the digest of the shared secret in bytes. - -const DIGEST_LENGTH = 4; // The customization string used in the RS1024 checksum and in the PBKDF2 salt. - -const SALT_STRING = 'shamir'; // The minimum allowed entropy of the master secret. - -const MIN_ENTROPY_BITS = 128; // The minimum allowed length of the mnemonic in words. - -const METADATA_WORDS_LENGTH = ITERATION_EXP_WORDS_LENGTH + 2 + CHECKSUM_WORDS_LENGTH; // The length of the mnemonic in words without the share value. - -const MNEMONICS_WORDS_LENGTH = parseInt(METADATA_WORDS_LENGTH + (MIN_ENTROPY_BITS + RADIX_BITS - 1) / RADIX_BITS, 10); // The minimum number of iterations to use in PBKDF2. - -const ITERATION_COUNT = 10000; // The number of rounds to use in the Feistel cipher. - -const ROUND_COUNT = 4; // The index of the share containing the digest of the shared secret. - -const DIGEST_INDEX = 254; // The index of the share containing the shared secret. - -const SECRET_INDEX = 255; // -// Helper functions for SLIP39 implementation. -// - -String.prototype.slip39EncodeHex = function () { - let bytes = []; - - for (let i = 0; i < this.length; ++i) { - bytes.push(this.charCodeAt(i)); - } - - return bytes; -}; - -Array.prototype.slip39DecodeHex = function () { - let str = []; - const hex = this.toString().split(','); - - for (let i = 0; i < hex.length; i++) { - str.push(String.fromCharCode(hex[i])); - } - - return str.toString().replace(/,/g, ''); -}; - -Array.prototype.slip39Generate = function (m, v = _ => _) { - let n = m || this.length; - - for (let i = 0; i < n; i++) { - this[i] = v(i); - } - - return this; -}; - -Array.prototype.toHexString = function () { - return Array.prototype.map.call(this, function (byte) { - return ('0' + (byte & 0xFF).toString(16)).slice(-2); - }).join(''); -}; - -Array.prototype.toByteArray = function (hexString) { - for (let i = 0; i < hexString.length; i = i + 2) { - this.push(parseInt(hexString.substr(i, 2), 16)); - } - - return this; -}; - -const BIGINT_WORD_BITS = JSBI.BigInt(8); - -function decodeBigInt(bytes) { - let result = JSBI.BigInt(0); - - for (let i = 0; i < bytes.length; i++) { - let b = JSBI.BigInt(bytes[bytes.length - i - 1]); - result = JSBI.add(result, JSBI.leftShift(b, JSBI.multiply(BIGINT_WORD_BITS, JSBI.BigInt(i)))); - } - - return result; -} - -function encodeBigInt(number, paddedLength = 0) { - let num = number; - const BYTE_MASK = JSBI.BigInt(0xff); - const BIGINT_ZERO = JSBI.BigInt(0); - let result = new Array(0); - - while (maybeJSBI.greaterThan(num, BIGINT_ZERO)) { - let i = parseInt(maybeJSBI.bitwiseAnd(num, BYTE_MASK), 10); - result.unshift(i); - num = maybeJSBI.signedRightShift(num, BIGINT_WORD_BITS); - } // Zero padding to the length - - - for (let i = result.length; maybeJSBI.lessThan(i, paddedLength); _x = i, i = maybeJSBI.add(i, maybeJSBI.BigInt(1)), _x) { - var _x; - - result.unshift(0); - } - - if (paddedLength !== 0 && maybeJSBI.greaterThan(result.length, paddedLength)) { - throw new Error(`Error in encoding BigInt value, expected less than ${paddedLength} length value, got ${result.length}`); - } - - return result; -} - -function bitsToBytes(n) { - const res = (n + 7) / 8; - const b = parseInt(res, RADIX_BITS); - return b; -} - -function bitsToWords(n) { - const res = (n + RADIX_BITS - 1) / RADIX_BITS; - const b = parseInt(res, RADIX_BITS); - return b; -} // -// Returns a randomly generated integer in the range 0, ... , 2**ID_LENGTH_BITS - 1. -// - - -function randomBytes(length = 32) { - let randoms = randombytes(length); - return Array.prototype.slice.call(randoms, 0); -} // -// The round function used internally by the Feistel cipher. -// - - -function roundFunction(round, passphrase, exp, salt, secret) { - const saltedSecret = salt.concat(secret); - const roundedPhrase = [round].concat(passphrase); - const count = (ITERATION_COUNT << exp) / ROUND_COUNT; - const key = pbkdf2.pbkdf2Sync(Buffer.from(roundedPhrase), Buffer.from(saltedSecret), count, secret.length, 'sha256'); - return Array.prototype.slice.call(key, 0); -} - -function crypt(masterSecret, passphrase, iterationExponent, identifier, encrypt = true) { - // Iteration exponent validated here. - if (iterationExponent < 0 || iterationExponent > MAX_ITERATION_EXP) { - throw Error(`Invalid iteration exponent (${iterationExponent}). Expected between 0 and ${MAX_ITERATION_EXP}`); - } - - let IL = masterSecret.slice().slice(0, masterSecret.length / 2); - let IR = masterSecret.slice().slice(masterSecret.length / 2); - const pwd = passphrase.slip39EncodeHex(); - const salt = getSalt(identifier); - let range = Array().slip39Generate(ROUND_COUNT); - range = encrypt ? range : range.reverse(); - range.forEach(round => { - const f = roundFunction(round, pwd, iterationExponent, salt, IR); - const t = xor(IL, f); - IL = IR; - IR = t; - }); - return IR.concat(IL); -} - -function createDigest(randomData, sharedSecret) { - const hmac = createHmac('sha256', Buffer.from(randomData)); - hmac.update(Buffer.from(sharedSecret)); - let result = hmac.digest(); - result = result.slice(0, 4); - return Array.prototype.slice.call(result, 0); -} - -function splitSecret(threshold, shareCount, sharedSecret) { - if (threshold <= 0) { - throw Error(`The requested threshold (${threshold}) must be a positive integer.`); - } - - if (threshold > shareCount) { - throw Error(`The requested threshold (${threshold}) must not exceed the number of shares (${shareCount}).`); - } - - if (shareCount > MAX_SHARE_COUNT) { - throw Error(`The requested number of shares (${shareCount}) must not exceed ${MAX_SHARE_COUNT}.`); - } // If the threshold is 1, then the digest of the shared secret is not used. - - - if (threshold === 1) { - return Array().slip39Generate(shareCount, () => sharedSecret); - } - - const randomShareCount = threshold - 2; - const randomPart = randomBytes(sharedSecret.length - DIGEST_LENGTH); - const digest = createDigest(randomPart, sharedSecret); - let baseShares = new Map(); - let shares = []; - - if (randomShareCount) { - shares = Array().slip39Generate(randomShareCount, () => randomBytes(sharedSecret.length)); - shares.forEach((item, idx) => { - baseShares.set(idx, item); - }); - } - - baseShares.set(DIGEST_INDEX, digest.concat(randomPart)); - baseShares.set(SECRET_INDEX, sharedSecret); - - for (let i = randomShareCount; i < shareCount; i++) { - const rr = interpolate(baseShares, i); - shares.push(rr); - } - - return shares; -} // -// Returns a randomly generated integer in the range 0, ... , 2**ID_BITS_LENGTH - 1. -// - - -function generateIdentifier() { - const byte = bitsToBytes(ID_BITS_LENGTH); - const bits = ID_BITS_LENGTH % 8; - const identifier = randomBytes(byte); - identifier[0] = identifier[0] & (1 << bits) - 1; - return identifier; -} - -function xor(a, b) { - if (maybeJSBI.notEqual(a.length, b.length)) { - throw new Error(`Invalid padding in mnemonic or insufficient length of mnemonics (${a.length} or ${b.length})`); - } - - return Array().slip39Generate(a.length, i => maybeJSBI.bitwiseXor(a[i], b[i])); -} - -function getSalt(identifier) { - const salt = SALT_STRING.slip39EncodeHex(); - return salt.concat(identifier); -} - -function interpolate(shares, x) { - let xCoord = new Set(shares.keys()); - let arr = Array.from(shares.values(), v => v.length); - let sharesValueLengths = new Set(arr); - - if (sharesValueLengths.size !== 1) { - throw new Error('Invalid set of shares. All share values must have the same length.'); - } - - if (xCoord.has(x)) { - shares.forEach((v, k) => { - if (maybeJSBI.equal(k, x)) { - return v; - } - }); - } // Logarithm of the product of (x_i - x) for i = 1, ... , k. - - - let logProd = 0; - shares.forEach((v, k) => { - logProd = logProd + LOG_TABLE[maybeJSBI.bitwiseXor(k, x)]; - }); - let results = Array().slip39Generate(sharesValueLengths.values().next().value, () => 0); - shares.forEach((v, k) => { - // The logarithm of the Lagrange basis polynomial evaluated at x. - let sum = 0; - shares.forEach((vv, kk) => { - sum = sum + LOG_TABLE[maybeJSBI.bitwiseXor(k, kk)]; - }); // FIXME: -18 % 255 = 237. IT shoulud be 237 and not -18 as it's - // implemented in javascript. - - const basis = (logProd - LOG_TABLE[maybeJSBI.bitwiseXor(k, x)] - sum) % 255; - const logBasisEval = basis < 0 ? 255 + basis : basis; - v.forEach((item, idx) => { - const shareVal = item; - const intermediateSum = results[idx]; - const r = shareVal !== 0 ? EXP_TABLE[(LOG_TABLE[shareVal] + logBasisEval) % 255] : 0; - const res = maybeJSBI.bitwiseXor(intermediateSum, r); - results[idx] = res; - }); - }); - return results; -} - -function rs1024Polymod(data) { - const GEN = [0xE0E040, 0x1C1C080, 0x3838100, 0x7070200, 0xE0E0009, 0x1C0C2412, 0x38086C24, 0x3090FC48, 0x21B1F890, 0x3F3F120]; - let chk = 1; - data.forEach(byte => { - const b = chk >> 20; - chk = (chk & 0xFFFFF) << 10 ^ byte; - - for (let i = 0; i < 10; i++) { - let gen = (b >> i & 1) !== 0 ? GEN[i] : 0; - chk = chk ^ gen; - } - }); - return chk; -} - -function rs1024CreateChecksum(data) { - const values = SALT_STRING.slip39EncodeHex().concat(data).concat(Array().slip39Generate(CHECKSUM_WORDS_LENGTH, () => 0)); - const polymod = rs1024Polymod(values) ^ 1; - const result = Array().slip39Generate(CHECKSUM_WORDS_LENGTH, i => polymod >> 10 * i & 1023).reverse(); - return result; -} - -function rs1024VerifyChecksum(data) { - return rs1024Polymod(SALT_STRING.slip39EncodeHex().concat(data)) === 1; -} // -// Converts a list of base 1024 indices in big endian order to an integer value. -// - - -function intFromIndices(indices) { - let value = JSBI.BigInt(0); - const radix = JSBI.BigInt(Math.pow(2, RADIX_BITS)); - indices.forEach(index => { - value = JSBI.add(maybeJSBI.multiply(value, radix), JSBI.BigInt(index)); - }); - return value; -} // -// Converts a Big integer value to indices in big endian order. -// - - -function intToIndices(value, length, bits) { - const mask = JSBI.BigInt((1 << bits) - 1); - const result = Array().slip39Generate(length, i => parseInt(JSBI.bitwiseAnd(JSBI.signedRightShift(value, JSBI.multiply(JSBI.BigInt(i), JSBI.BigInt(bits))), mask), 10)); - return result.reverse(); -} - -function mnemonicFromIndices(indices) { - const result = indices.map(index => { - return WORD_LIST[index]; - }); - return result.toString().split(',').join(' '); -} - -function mnemonicToIndices(mnemonic) { - if (typeof mnemonic !== 'string') { - throw new Error(`Mnemonic expected to be typeof string with white space separated words. Instead found typeof ${typeof mnemonic}.`); - } - - const words = mnemonic.toLowerCase().split(' '); - const result = words.reduce((prev, item) => { - const index = WORD_LIST_MAP[item]; - - if (typeof index === 'undefined') { - throw new Error(`Invalid mnemonic word ${item}.`); - } - - return prev.concat(index); - }, []); - return result; -} - -function recoverSecret(threshold, shares) { - // If the threshold is 1, then the digest of the shared secret is not used. - if (threshold === 1) { - return shares.values().next().value; - } - - const sharedSecret = interpolate(shares, SECRET_INDEX); - const digestShare = interpolate(shares, DIGEST_INDEX); - const digest = digestShare.slice(0, DIGEST_LENGTH); - const randomPart = digestShare.slice(DIGEST_LENGTH); - const recoveredDigest = createDigest(randomPart, sharedSecret); - - if (!listsAreEqual(digest, recoveredDigest)) { - throw new Error('Invalid digest of the shared secret.'); - } - - return sharedSecret; -} // -// Combines mnemonic shares to obtain the master secret which was previously -// split using Shamir's secret sharing scheme. -// - - -function combineMnemonics(mnemonics, passphrase = '') { - if (mnemonics === null || mnemonics.length === 0) { - throw new Error('The list of mnemonics is empty.'); - } - - const decoded = decodeMnemonics(mnemonics); - const identifier = decoded.identifier; - const iterationExponent = decoded.iterationExponent; - const groupThreshold = decoded.groupThreshold; - const groupCount = decoded.groupCount; - const groups = decoded.groups; - - if (maybeJSBI.lessThan(groups.size, groupThreshold)) { - throw new Error(`Insufficient number of mnemonic groups (${groups.size}). The required number of groups is ${groupThreshold}.`); - } - - if (maybeJSBI.notEqual(groups.size, groupThreshold)) { - throw new Error(`Wrong number of mnemonic groups. Expected ${groupThreshold} groups, but ${groups.size} were provided.`); - } - - let allShares = new Map(); - groups.forEach((members, groupIndex) => { - const threshold = members.keys().next().value; - const shares = members.values().next().value; - - if (maybeJSBI.notEqual(shares.size, threshold)) { - const prefix = groupPrefix(identifier, iterationExponent, groupIndex, groupThreshold, groupCount); - throw new Error(`Wrong number of mnemonics. Expected ${threshold} mnemonics starting with "${mnemonicFromIndices(prefix)}", \n but ${shares.size} were provided.`); - } - - const recovered = recoverSecret(threshold, shares); - allShares.set(groupIndex, recovered); - }); - const ems = recoverSecret(groupThreshold, allShares); - const id = intToIndices(JSBI.BigInt(identifier), ITERATION_EXP_WORDS_LENGTH, 8); - const ms = crypt(ems, passphrase, iterationExponent, id, false); - return ms; -} - -function decodeMnemonics(mnemonics) { - if (!(mnemonics instanceof Array)) { - throw new Error('Mnemonics should be an array of strings'); - } - - const identifiers = new Set(); - const iterationExponents = new Set(); - const groupThresholds = new Set(); - const groupCounts = new Set(); - const groups = new Map(); - mnemonics.forEach(mnemonic => { - const decoded = decodeMnemonic(mnemonic); - identifiers.add(decoded.identifier); - iterationExponents.add(decoded.iterationExponent); - const groupIndex = decoded.groupIndex; - groupThresholds.add(decoded.groupThreshold); - groupCounts.add(decoded.groupCount); - const memberIndex = decoded.memberIndex; - const memberThreshold = decoded.memberThreshold; - const share = decoded.share; - const group = !groups.has(groupIndex) ? new Map() : groups.get(groupIndex); - const member = !group.has(memberThreshold) ? new Map() : group.get(memberThreshold); - member.set(memberIndex, share); - group.set(memberThreshold, member); - - if (group.size !== 1) { - throw new Error('Invalid set of mnemonics. All mnemonics in a group must have the same member threshold.'); - } - - groups.set(groupIndex, group); - }); - - if (identifiers.size !== 1 || iterationExponents.size !== 1) { - throw new Error(`Invalid set of mnemonics. All mnemonics must begin with the same ${ITERATION_EXP_WORDS_LENGTH} words.`); - } - - if (groupThresholds.size !== 1) { - throw new Error('Invalid set of mnemonics. All mnemonics must have the same group threshold.'); - } - - if (groupCounts.size !== 1) { - throw new Error('Invalid set of mnemonics. All mnemonics must have the same group count.'); - } - - return { - identifier: identifiers.values().next().value, - iterationExponent: iterationExponents.values().next().value, - groupThreshold: groupThresholds.values().next().value, - groupCount: groupCounts.values().next().value, - groups: groups - }; -} // -// Converts a share mnemonic to share data. -// - - -function decodeMnemonic(mnemonic) { - const data = mnemonicToIndices(mnemonic); - - if (maybeJSBI.lessThan(data.length, MNEMONICS_WORDS_LENGTH)) { - throw new Error(`Invalid mnemonic length. The length of each mnemonic must be at least ${MNEMONICS_WORDS_LENGTH} words.`); - } - - const paddingLen = RADIX_BITS * (data.length - METADATA_WORDS_LENGTH) % 16; - - if (paddingLen > 8) { - throw new Error('Invalid mnemonic length.'); - } - - if (!rs1024VerifyChecksum(data)) { - throw new Error('Invalid mnemonic checksum'); - } - - const idExpInt = parseInt(intFromIndices(data.slice(0, ITERATION_EXP_WORDS_LENGTH)), 10); - const identifier = idExpInt >> ITERATION_EXP_BITS_LENGTH; - const iterationExponent = idExpInt & (1 << ITERATION_EXP_BITS_LENGTH) - 1; - const tmp = intFromIndices(data.slice(ITERATION_EXP_WORDS_LENGTH, ITERATION_EXP_WORDS_LENGTH + 2)); - const indices = intToIndices(tmp, 5, 4); - const groupIndex = indices[0]; - const groupThreshold = indices[1]; - const groupCount = indices[2]; - const memberIndex = indices[3]; - const memberThreshold = indices[4]; - const valueData = data.slice(ITERATION_EXP_WORDS_LENGTH + 2, data.length - CHECKSUM_WORDS_LENGTH); - - if (groupCount < groupThreshold) { - throw new Error(`Invalid mnemonic: ${mnemonic}.\n Group threshold (${groupThreshold}) cannot be greater than group count (${groupCount}).`); - } - - const valueInt = intFromIndices(valueData); - - try { - const valueByteCount = bitsToBytes(RADIX_BITS * valueData.length - paddingLen); - const share = encodeBigInt(valueInt, valueByteCount); - return { - identifier: identifier, - iterationExponent: iterationExponent, - groupIndex: groupIndex, - groupThreshold: groupThreshold + 1, - groupCount: groupCount + 1, - memberIndex: memberIndex, - memberThreshold: memberThreshold + 1, - share: share - }; - } catch (e) { - throw new Error(`Invalid mnemonic padding (${e})`); - } -} - -function validateMnemonic(mnemonic) { - try { - decodeMnemonic(mnemonic); - return true; - } catch (error) { - return false; - } -} - -function groupPrefix(identifier, iterationExponent, groupIndex, groupThreshold, groupCount) { - const idExpInt = JSBI.BigInt((identifier << ITERATION_EXP_BITS_LENGTH) + iterationExponent); - const indc = intToIndices(idExpInt, ITERATION_EXP_WORDS_LENGTH, RADIX_BITS); - const indc2 = (groupIndex << 6) + (groupThreshold - 1 << 2) + (groupCount - 1 >> 2); - indc.push(indc2); - return indc; -} - -function listsAreEqual(a, b) { - if (a === null || b === null || maybeJSBI.notEqual(a.length, b.length)) { - return false; - } - - let i = 0; - return a.every(item => { - return maybeJSBI.equal(b[i++], item); - }); -} // -// Converts share data to a share mnemonic. -// - - -function encodeMnemonic(identifier, iterationExponent, groupIndex, groupThreshold, groupCount, memberIndex, memberThreshold, value) { - // Convert the share value from bytes to wordlist indices. - const valueWordCount = bitsToWords(value.length * 8); - const valueInt = decodeBigInt(value); - let newIdentifier = parseInt(decodeBigInt(identifier), 10); - const gp = groupPrefix(newIdentifier, iterationExponent, groupIndex, groupThreshold, groupCount); - const tp = intToIndices(valueInt, valueWordCount, RADIX_BITS); - const calc = ((groupCount - 1 & 3) << 8) + (memberIndex << 4) + (memberThreshold - 1); - gp.push(calc); - const shareData = gp.concat(tp); - const checksum = rs1024CreateChecksum(shareData); - return mnemonicFromIndices(shareData.concat(checksum)); -} // The precomputed exponent and log tables. -// ``` -// const exp = List.filled(255, 0) -// const log = List.filled(256, 0) -// const poly = 1 -// -// for (let i = 0; i < exp.length; i++) { -// exp[i] = poly -// log[poly] = i -// // Multiply poly by the polynomial x + 1. -// poly = (poly << 1) ^ poly -// // Reduce poly by x^8 + x^4 + x^3 + x + 1. -// if (poly & 0x100 === 0x100) poly ^= 0x11B -// } -// ``` - - -const EXP_TABLE = [1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53, 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170, 229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49, 83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205, 76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136, 131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154, 181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163, 254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160, 251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65, 195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117, 159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128, 155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84, 252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202, 69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14, 18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23, 57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246]; -const LOG_TABLE = [0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3, 100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193, 125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120, 101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142, 150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56, 102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16, 126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186, 43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87, 175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232, 44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160, 127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183, 204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157, 151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209, 83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171, 68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165, 103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7]; // -// SLIP39 wordlist -// - -const WORD_LIST = ['academic', 'acid', 'acne', 'acquire', 'acrobat', 'activity', 'actress', 'adapt', 'adequate', 'adjust', 'admit', 'adorn', 'adult', 'advance', 'advocate', 'afraid', 'again', 'agency', 'agree', 'aide', 'aircraft', 'airline', 'airport', 'ajar', 'alarm', 'album', 'alcohol', 'alien', 'alive', 'alpha', 'already', 'alto', 'aluminum', 'always', 'amazing', 'ambition', 'amount', 'amuse', 'analysis', 'anatomy', 'ancestor', 'ancient', 'angel', 'angry', 'animal', 'answer', 'antenna', 'anxiety', 'apart', 'aquatic', 'arcade', 'arena', 'argue', 'armed', 'artist', 'artwork', 'aspect', 'auction', 'august', 'aunt', 'average', 'aviation', 'avoid', 'award', 'away', 'axis', 'axle', 'beam', 'beard', 'beaver', 'become', 'bedroom', 'behavior', 'being', 'believe', 'belong', 'benefit', 'best', 'beyond', 'bike', 'biology', 'birthday', 'bishop', 'black', 'blanket', 'blessing', 'blimp', 'blind', 'blue', 'body', 'bolt', 'boring', 'born', 'both', 'boundary', 'bracelet', 'branch', 'brave', 'breathe', 'briefing', 'broken', 'brother', 'browser', 'bucket', 'budget', 'building', 'bulb', 'bulge', 'bumpy', 'bundle', 'burden', 'burning', 'busy', 'buyer', 'cage', 'calcium', 'camera', 'campus', 'canyon', 'capacity', 'capital', 'capture', 'carbon', 'cards', 'careful', 'cargo', 'carpet', 'carve', 'category', 'cause', 'ceiling', 'center', 'ceramic', 'champion', 'change', 'charity', 'check', 'chemical', 'chest', 'chew', 'chubby', 'cinema', 'civil', 'class', 'clay', 'cleanup', 'client', 'climate', 'clinic', 'clock', 'clogs', 'closet', 'clothes', 'club', 'cluster', 'coal', 'coastal', 'coding', 'column', 'company', 'corner', 'costume', 'counter', 'course', 'cover', 'cowboy', 'cradle', 'craft', 'crazy', 'credit', 'cricket', 'criminal', 'crisis', 'critical', 'crowd', 'crucial', 'crunch', 'crush', 'crystal', 'cubic', 'cultural', 'curious', 'curly', 'custody', 'cylinder', 'daisy', 'damage', 'dance', 'darkness', 'database', 'daughter', 'deadline', 'deal', 'debris', 'debut', 'decent', 'decision', 'declare', 'decorate', 'decrease', 'deliver', 'demand', 'density', 'deny', 'depart', 'depend', 'depict', 'deploy', 'describe', 'desert', 'desire', 'desktop', 'destroy', 'detailed', 'detect', 'device', 'devote', 'diagnose', 'dictate', 'diet', 'dilemma', 'diminish', 'dining', 'diploma', 'disaster', 'discuss', 'disease', 'dish', 'dismiss', 'display', 'distance', 'dive', 'divorce', 'document', 'domain', 'domestic', 'dominant', 'dough', 'downtown', 'dragon', 'dramatic', 'dream', 'dress', 'drift', 'drink', 'drove', 'drug', 'dryer', 'duckling', 'duke', 'duration', 'dwarf', 'dynamic', 'early', 'earth', 'easel', 'easy', 'echo', 'eclipse', 'ecology', 'edge', 'editor', 'educate', 'either', 'elbow', 'elder', 'election', 'elegant', 'element', 'elephant', 'elevator', 'elite', 'else', 'email', 'emerald', 'emission', 'emperor', 'emphasis', 'employer', 'empty', 'ending', 'endless', 'endorse', 'enemy', 'energy', 'enforce', 'engage', 'enjoy', 'enlarge', 'entrance', 'envelope', 'envy', 'epidemic', 'episode', 'equation', 'equip', 'eraser', 'erode', 'escape', 'estate', 'estimate', 'evaluate', 'evening', 'evidence', 'evil', 'evoke', 'exact', 'example', 'exceed', 'exchange', 'exclude', 'excuse', 'execute', 'exercise', 'exhaust', 'exotic', 'expand', 'expect', 'explain', 'express', 'extend', 'extra', 'eyebrow', 'facility', 'fact', 'failure', 'faint', 'fake', 'false', 'family', 'famous', 'fancy', 'fangs', 'fantasy', 'fatal', 'fatigue', 'favorite', 'fawn', 'fiber', 'fiction', 'filter', 'finance', 'findings', 'finger', 'firefly', 'firm', 'fiscal', 'fishing', 'fitness', 'flame', 'flash', 'flavor', 'flea', 'flexible', 'flip', 'float', 'floral', 'fluff', 'focus', 'forbid', 'force', 'forecast', 'forget', 'formal', 'fortune', 'forward', 'founder', 'fraction', 'fragment', 'frequent', 'freshman', 'friar', 'fridge', 'friendly', 'frost', 'froth', 'frozen', 'fumes', 'funding', 'furl', 'fused', 'galaxy', 'game', 'garbage', 'garden', 'garlic', 'gasoline', 'gather', 'general', 'genius', 'genre', 'genuine', 'geology', 'gesture', 'glad', 'glance', 'glasses', 'glen', 'glimpse', 'goat', 'golden', 'graduate', 'grant', 'grasp', 'gravity', 'gray', 'greatest', 'grief', 'grill', 'grin', 'grocery', 'gross', 'group', 'grownup', 'grumpy', 'guard', 'guest', 'guilt', 'guitar', 'gums', 'hairy', 'hamster', 'hand', 'hanger', 'harvest', 'have', 'havoc', 'hawk', 'hazard', 'headset', 'health', 'hearing', 'heat', 'helpful', 'herald', 'herd', 'hesitate', 'hobo', 'holiday', 'holy', 'home', 'hormone', 'hospital', 'hour', 'huge', 'human', 'humidity', 'hunting', 'husband', 'hush', 'husky', 'hybrid', 'idea', 'identify', 'idle', 'image', 'impact', 'imply', 'improve', 'impulse', 'include', 'income', 'increase', 'index', 'indicate', 'industry', 'infant', 'inform', 'inherit', 'injury', 'inmate', 'insect', 'inside', 'install', 'intend', 'intimate', 'invasion', 'involve', 'iris', 'island', 'isolate', 'item', 'ivory', 'jacket', 'jerky', 'jewelry', 'join', 'judicial', 'juice', 'jump', 'junction', 'junior', 'junk', 'jury', 'justice', 'kernel', 'keyboard', 'kidney', 'kind', 'kitchen', 'knife', 'knit', 'laden', 'ladle', 'ladybug', 'lair', 'lamp', 'language', 'large', 'laser', 'laundry', 'lawsuit', 'leader', 'leaf', 'learn', 'leaves', 'lecture', 'legal', 'legend', 'legs', 'lend', 'length', 'level', 'liberty', 'library', 'license', 'lift', 'likely', 'lilac', 'lily', 'lips', 'liquid', 'listen', 'literary', 'living', 'lizard', 'loan', 'lobe', 'location', 'losing', 'loud', 'loyalty', 'luck', 'lunar', 'lunch', 'lungs', 'luxury', 'lying', 'lyrics', 'machine', 'magazine', 'maiden', 'mailman', 'main', 'makeup', 'making', 'mama', 'manager', 'mandate', 'mansion', 'manual', 'marathon', 'march', 'market', 'marvel', 'mason', 'material', 'math', 'maximum', 'mayor', 'meaning', 'medal', 'medical', 'member', 'memory', 'mental', 'merchant', 'merit', 'method', 'metric', 'midst', 'mild', 'military', 'mineral', 'minister', 'miracle', 'mixed', 'mixture', 'mobile', 'modern', 'modify', 'moisture', 'moment', 'morning', 'mortgage', 'mother', 'mountain', 'mouse', 'move', 'much', 'mule', 'multiple', 'muscle', 'museum', 'music', 'mustang', 'nail', 'national', 'necklace', 'negative', 'nervous', 'network', 'news', 'nuclear', 'numb', 'numerous', 'nylon', 'oasis', 'obesity', 'object', 'observe', 'obtain', 'ocean', 'often', 'olympic', 'omit', 'oral', 'orange', 'orbit', 'order', 'ordinary', 'organize', 'ounce', 'oven', 'overall', 'owner', 'paces', 'pacific', 'package', 'paid', 'painting', 'pajamas', 'pancake', 'pants', 'papa', 'paper', 'parcel', 'parking', 'party', 'patent', 'patrol', 'payment', 'payroll', 'peaceful', 'peanut', 'peasant', 'pecan', 'penalty', 'pencil', 'percent', 'perfect', 'permit', 'petition', 'phantom', 'pharmacy', 'photo', 'phrase', 'physics', 'pickup', 'picture', 'piece', 'pile', 'pink', 'pipeline', 'pistol', 'pitch', 'plains', 'plan', 'plastic', 'platform', 'playoff', 'pleasure', 'plot', 'plunge', 'practice', 'prayer', 'preach', 'predator', 'pregnant', 'premium', 'prepare', 'presence', 'prevent', 'priest', 'primary', 'priority', 'prisoner', 'privacy', 'prize', 'problem', 'process', 'profile', 'program', 'promise', 'prospect', 'provide', 'prune', 'public', 'pulse', 'pumps', 'punish', 'puny', 'pupal', 'purchase', 'purple', 'python', 'quantity', 'quarter', 'quick', 'quiet', 'race', 'racism', 'radar', 'railroad', 'rainbow', 'raisin', 'random', 'ranked', 'rapids', 'raspy', 'reaction', 'realize', 'rebound', 'rebuild', 'recall', 'receiver', 'recover', 'regret', 'regular', 'reject', 'relate', 'remember', 'remind', 'remove', 'render', 'repair', 'repeat', 'replace', 'require', 'rescue', 'research', 'resident', 'response', 'result', 'retailer', 'retreat', 'reunion', 'revenue', 'review', 'reward', 'rhyme', 'rhythm', 'rich', 'rival', 'river', 'robin', 'rocky', 'romantic', 'romp', 'roster', 'round', 'royal', 'ruin', 'ruler', 'rumor', 'sack', 'safari', 'salary', 'salon', 'salt', 'satisfy', 'satoshi', 'saver', 'says', 'scandal', 'scared', 'scatter', 'scene', 'scholar', 'science', 'scout', 'scramble', 'screw', 'script', 'scroll', 'seafood', 'season', 'secret', 'security', 'segment', 'senior', 'shadow', 'shaft', 'shame', 'shaped', 'sharp', 'shelter', 'sheriff', 'short', 'should', 'shrimp', 'sidewalk', 'silent', 'silver', 'similar', 'simple', 'single', 'sister', 'skin', 'skunk', 'slap', 'slavery', 'sled', 'slice', 'slim', 'slow', 'slush', 'smart', 'smear', 'smell', 'smirk', 'smith', 'smoking', 'smug', 'snake', 'snapshot', 'sniff', 'society', 'software', 'soldier', 'solution', 'soul', 'source', 'space', 'spark', 'speak', 'species', 'spelling', 'spend', 'spew', 'spider', 'spill', 'spine', 'spirit', 'spit', 'spray', 'sprinkle', 'square', 'squeeze', 'stadium', 'staff', 'standard', 'starting', 'station', 'stay', 'steady', 'step', 'stick', 'stilt', 'story', 'strategy', 'strike', 'style', 'subject', 'submit', 'sugar', 'suitable', 'sunlight', 'superior', 'surface', 'surprise', 'survive', 'sweater', 'swimming', 'swing', 'switch', 'symbolic', 'sympathy', 'syndrome', 'system', 'tackle', 'tactics', 'tadpole', 'talent', 'task', 'taste', 'taught', 'taxi', 'teacher', 'teammate', 'teaspoon', 'temple', 'tenant', 'tendency', 'tension', 'terminal', 'testify', 'texture', 'thank', 'that', 'theater', 'theory', 'therapy', 'thorn', 'threaten', 'thumb', 'thunder', 'ticket', 'tidy', 'timber', 'timely', 'ting', 'tofu', 'together', 'tolerate', 'total', 'toxic', 'tracks', 'traffic', 'training', 'transfer', 'trash', 'traveler', 'treat', 'trend', 'trial', 'tricycle', 'trip', 'triumph', 'trouble', 'true', 'trust', 'twice', 'twin', 'type', 'typical', 'ugly', 'ultimate', 'umbrella', 'uncover', 'undergo', 'unfair', 'unfold', 'unhappy', 'union', 'universe', 'unkind', 'unknown', 'unusual', 'unwrap', 'upgrade', 'upstairs', 'username', 'usher', 'usual', 'valid', 'valuable', 'vampire', 'vanish', 'various', 'vegan', 'velvet', 'venture', 'verdict', 'verify', 'very', 'veteran', 'vexed', 'victim', 'video', 'view', 'vintage', 'violence', 'viral', 'visitor', 'visual', 'vitamins', 'vocal', 'voice', 'volume', 'voter', 'voting', 'walnut', 'warmth', 'warn', 'watch', 'wavy', 'wealthy', 'weapon', 'webcam', 'welcome', 'welfare', 'western', 'width', 'wildlife', 'window', 'wine', 'wireless', 'wisdom', 'withdraw', 'wits', 'wolf', 'woman', 'work', 'worthy', 'wrap', 'wrist', 'writing', 'wrote', 'year', 'yelp', 'yield', 'yoga', 'zero']; -const WORD_LIST_MAP = WORD_LIST.reduce((obj, val, idx) => { - obj[val] = idx; - return obj; -}, {}); -exports = module.exports = { - MIN_ENTROPY_BITS, - generateIdentifier, - encodeMnemonic, - validateMnemonic, - splitSecret, - combineMnemonics, - crypt, - bitsToBytes -}; \ No newline at end of file diff --git a/blue_modules/slip39/example/main.js b/blue_modules/slip39/example/main.js deleted file mode 100644 index 19e1c0233..000000000 --- a/blue_modules/slip39/example/main.js +++ /dev/null @@ -1,122 +0,0 @@ -const slip39 = require('../src/slip39.js'); -const assert = require('assert'); -// threshold (N) number of group-shares required to reconstruct the master secret. -const groupThreshold = 2; -const masterSecret = 'ABCDEFGHIJKLMNOP'.slip39EncodeHex(); -const passphrase = 'TREZOR'; - -function recover(groupShares, pass) { - const recoveredSecret = slip39.recoverSecret(groupShares, pass); - console.log('\tMaster secret: ' + masterSecret.slip39DecodeHex()); - console.log('\tRecovered one: ' + recoveredSecret.slip39DecodeHex()); - assert(masterSecret.slip39DecodeHex() === recoveredSecret.slip39DecodeHex()); -} - -function printShares(shares) { - shares.forEach((s, i) => console.log(`\t${i + 1}) ${s}`)); -} - -/** - * 4 groups shares: - * = two for Alice - * = one for friends and - * = one for family members - * Any two (see threshold) of these four group-shares are required to reconstruct the master secret - * i.e. to recover the master secret the goal is to reconstruct any 2-of-4 group-shares. - * To reconstruct each group share, we need at least N of its M member-shares. - * Thus all possible master secret recovery combinations: - * Case 1) [requires 1 person: Alice] Alice alone with her 1-of-1 member-shares reconstructs both the 1st and 2nd group-shares - * Case 2) [requires 4 persons: Alice + any 3 of her 5 friends] Alice with her 1-of-1 member-shares reconstructs 1st (or 2nd) group-share + any 3-of-5 friend member-shares reconstruct the 3rd group-share - * Case 3) [requires 3 persons: Alice + any 2 of her 6 family relatives] Alice with her 1-of-1 member-shares reconstructs 1st (or 2nd) group-share + any 2-of-6 family member-shares reconstruct the 4th group-share - * Case 4) [requires 5 persons: any 3 of her 5 friends + any 2 of her 6 family relatives] any 3-of-5 friend member-shares reconstruct the 3rd group-share + any 2-of-6 family member-shares reconstruct the 4th group-share - */ -const groups = [ - // Alice group-shares. 1 is enough to reconstruct a group-share, - // therefore she needs at least two group-shares to reconstruct the master secret. - [1, 1, 'Alice personal group share 1'], - [1, 1, 'Alice personal group share 2'], - // 3 of 5 Friends' shares are required to reconstruct this group-share - [3, 5, 'Friends group share for Bob, Charlie, Dave, Frank and Grace'], - // 2 of 6 Family's shares are required to reconstruct this group-share - [2, 6, 'Family group share for mom, dad, brother, sister and wife'] -]; - -const slip = slip39.fromArray(masterSecret, { - passphrase: passphrase, - threshold: groupThreshold, - groups: groups, - title: 'Slip39 example for 2-level SSSS' -}); - -let requiredGroupShares; -let aliceBothGroupShares; -let aliceFirstGroupShare; -let aliceSecondGroupShare; -let friendGroupShares; -let familyGroupShares; - - -/* - * Example of Case 1 - */ -// The 1st, and only, member-share (member 0) of the 1st group-share (group 0) + the 1st, and only, member-share (member 0) of the 2nd group-share (group 1) -aliceBothGroupShares = slip.fromPath('r/0/0').mnemonics - .concat(slip.fromPath('r/1/0').mnemonics); - -requiredGroupShares = aliceBothGroupShares; - -console.log(`\n* Shares used by Alice alone for restoring the master secret (total of ${requiredGroupShares.length} member-shares):`); -printShares(requiredGroupShares); -recover(requiredGroupShares, passphrase); - -/* - * Example of Case 2 - */ -// The 1st, and only, member-share (member 0) of the 2nd group-share (group 1) -aliceSecondGroupShare = slip.fromPath('r/1/0').mnemonics; - -// ...plus the 3rd member-share (member 2) + the 4th member-share (member 3) + the 5th member-share (member 4) of the 3rd group-share (group 2) -friendGroupShares = slip.fromPath('r/2/2').mnemonics - .concat(slip.fromPath('r/2/3').mnemonics) - .concat(slip.fromPath('r/2/4').mnemonics); - -requiredGroupShares = aliceSecondGroupShare.concat(friendGroupShares); - -console.log(`\n* Shares used by Alice + 3 friends for restoring the master secret (total of ${requiredGroupShares.length} member-shares):`); -printShares(requiredGroupShares); -recover(requiredGroupShares, passphrase); - - -/* - * Example of Case 3 - */ -// The 1st, and only, member-share (member 0) of the 1st group-share (group 0) -aliceFirstGroupShare = slip.fromPath('r/0/0').mnemonics; - -// ...plus the 2nd member-share (member 1) + the 3rd member-share (member 2) of the 4th group-share (group 3) -familyGroupShares = slip.fromPath('r/3/1').mnemonics - .concat(slip.fromPath('r/3/2').mnemonics); - -requiredGroupShares = aliceFirstGroupShare.concat(familyGroupShares); - -console.log(`\n* Shares used by Alice + 2 family members for restoring the master secret (total of ${requiredGroupShares.length} member-shares):`); -printShares(requiredGroupShares); -recover(requiredGroupShares, passphrase); - -/* - * Example of Case 4 - */ -// The 3rd member-share (member 2) + the 4th member-share (member 3) + the 5th member-share (member 4) of the 3rd group-share (group 2) -friendGroupShares = slip.fromPath('r/2/2').mnemonics - .concat(slip.fromPath('r/2/3').mnemonics) - .concat(slip.fromPath('r/2/4').mnemonics); - -// ...plus the 2nd member-share (member 1) + the 3rd member-share (member 2) of the 4th group-share (group 3) -familyGroupShares = slip.fromPath('r/3/1').mnemonics - .concat(slip.fromPath('r/3/2').mnemonics); - -requiredGroupShares = friendGroupShares.concat(familyGroupShares); - -console.log(`\n* Shares used by 3 friends + 2 family members for restoring the master secret (total of ${requiredGroupShares.length} member-shares):`); -printShares(requiredGroupShares); -recover(requiredGroupShares, passphrase); diff --git a/blue_modules/slip39/jsbeautifyrc b/blue_modules/slip39/jsbeautifyrc deleted file mode 100644 index 7a3cb53df..000000000 --- a/blue_modules/slip39/jsbeautifyrc +++ /dev/null @@ -1,16 +0,0 @@ -{ - "indent_with_tabs": false, - "indent_size": 2, - "max_preserve_newlines": 2, - "preserve_newlines": true, - "keep_array_indentation": true, - "break_chained_methods": true, - "wrap_line_length": 120, - "end_with_newline": true, - "brace_style": "collapse,preserve-inline", - "unformatted": ["a", "abbr", "area", "audio", "b", "bdi", "bdo", "br", "button", "canvas", "cite", "code", "data", - "datalist", "del", "dfn", "em", "embed", "i", "iframe", "img", "input", "ins", "kbd", "keygen", "label", "map", - "mark", "math", "meter", "noscript", "object", "output", "progress", "q", "ruby", "s", "samp", "select", "small", - "span", "strong", "sub", "sup", "template", "textarea", "time", "u", "var", "video", "wbr", "text", "acronym", - "address", "big", "dt", "ins", "small", "strike", "tt", "pre", "h1", "h2", "h3", "h4", "h5", "h6"] -} diff --git a/blue_modules/slip39/jsconfig.json b/blue_modules/slip39/jsconfig.json deleted file mode 100644 index 98f25cc1e..000000000 --- a/blue_modules/slip39/jsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "exclude": [ - "node_modules", - "**/node_modules/*" - ], - "include": [ - "src/**/*" - ] -} diff --git a/blue_modules/slip39/package-lock.json b/blue_modules/slip39/package-lock.json deleted file mode 100644 index aab5f8bae..000000000 --- a/blue_modules/slip39/package-lock.json +++ /dev/null @@ -1,2999 +0,0 @@ -{ - "name": "slip39", - "version": "0.1.7", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/cli": { - "version": "7.13.14", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.13.14.tgz", - "integrity": "sha512-zmEFV8WBRsW+mPQumO1/4b34QNALBVReaiHJOkxhUsdo/AvYM62c+SKSuLi2aZ42t3ocK6OI0uwUXRvrIbREZw==", - "dev": true, - "requires": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", - "chokidar": "^3.4.0", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.0.0", - "lodash": "^4.17.19", - "make-dir": "^2.1.0", - "slash": "^2.0.0", - "source-map": "^0.5.0" - } - }, - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/compat-data": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.15.tgz", - "integrity": "sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==", - "dev": true - }, - "@babel/core": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.15.tgz", - "integrity": "sha512-6GXmNYeNjS2Uz+uls5jalOemgIhnTMeaXo+yBUA72kC2uX/8VW6XyhVIo2L8/q0goKQA3EVKx0KOQpVKSeWadQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.9", - "@babel/helper-compilation-targets": "^7.13.13", - "@babel/helper-module-transforms": "^7.13.14", - "@babel/helpers": "^7.13.10", - "@babel/parser": "^7.13.15", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.15", - "@babel/types": "^7.13.14", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.13.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz", - "integrity": "sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==", - "dev": true, - "requires": { - "@babel/types": "^7.13.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.13.13", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz", - "integrity": "sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.13.12", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", - "dev": true, - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", - "dev": true, - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-module-transforms": { - "version": "7.13.14", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz", - "integrity": "sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.12.11", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.13", - "@babel/types": "^7.13.14" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", - "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", - "dev": true, - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", - "dev": true - }, - "@babel/helpers": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.10.tgz", - "integrity": "sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==", - "dev": true, - "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.0" - } - }, - "@babel/highlight": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", - "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", - "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==", - "dev": true - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/traverse": { - "version": "7.13.15", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz", - "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.9", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.15", - "@babel/types": "^7.13.14", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.13.14", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", - "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz", - "integrity": "sha512-+nb9vWloHNNMFHjGofEam3wopE3m1yuambrrd/fnPc+lFOMB9ROTqQlche9ByFWNkdNqfSgR/kkQtQ8DzEWt2w==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "optional": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "optional": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "optional": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "optional": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "optional": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "optional": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "optional": true - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true, - "optional": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "optional": true - }, - "babel-plugin-transform-bigint": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-bigint/-/babel-plugin-transform-bigint-1.0.9.tgz", - "integrity": "sha512-Lf4X2LssZpl3XwvU9xr7HLMtZozGBzrh/0P1qEFktbiz24RI2qG6QF8VhGoWPynbYHKKGHYCjYwprrTS5Z/kkw==", - "dev": true, - "requires": { - "@babel/plugin-syntax-bigint": "^7.2.0", - "big-integer": "^1.6.41" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "optional": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "big-integer": { - "version": "1.6.48", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", - "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==", - "dev": true - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "optional": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "browserslist": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz", - "integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001208", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.712", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "optional": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001208", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz", - "integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "dependencies": { - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "optional": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "optional": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "optional": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "optional": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "optional": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "optional": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "optional": true - }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "optional": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "optional": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "optional": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "optional": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true, - "optional": true - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true, - "optional": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.713", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.713.tgz", - "integrity": "sha512-HWgkyX4xTHmxcWWlvv7a87RHSINEcpKYZmDMxkUlHcY+CJcfx7xEfBHuXVsO1rzyYs1WQJ7EgDp2CoErakBIow==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "es-abstract": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz", - "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.2", - "is-string": "^1.0.5", - "object-inspect": "^1.9.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.0" - }, - "dependencies": { - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - } - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "optional": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true, - "optional": true - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "optional": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "optional": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true, - "optional": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "optional": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "optional": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "optional": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true, - "optional": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "optional": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "optional": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "optional": true - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "optional": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-bigint": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", - "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "optional": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", - "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true - }, - "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "optional": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "optional": true - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "optional": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "optional": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "optional": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-number-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", - "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "optional": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", - "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.1" - } - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "optional": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "optional": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbi": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.4.tgz", - "integrity": "sha512-52QRRFSsi9impURE8ZUbzAMCLjPm4THO7H2fcuIvaaeFTbSysvkodbQQXIVsNgq/ypDbq6dJiuGKL0vZ/i9hUg==" - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "optional": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "optional": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "optional": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "optional": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", - "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "mocha": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", - "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", - "dev": true, - "requires": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "2.2.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.4", - "ms": "2.1.1", - "node-environment-flags": "1.0.5", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "node-environment-flags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", - "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "node-releases": { - "version": "1.1.71", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", - "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "optional": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "optional": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "optional": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", - "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "optional": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "optional": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "optional": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true, - "optional": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "picomatch": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", - "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", - "dev": true, - "optional": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "optional": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "optional": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "optional": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "optional": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true, - "optional": true - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "optional": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "optional": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true, - "optional": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "optional": true - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "optional": true, - "requires": { - "ret": "~0.1.10" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "optional": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true, - "optional": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "optional": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "optional": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "optional": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true, - "optional": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "optional": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "optional": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true, - "optional": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "optional": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "optional": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "optional": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "optional": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "optional": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "optional": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "optional": true - } - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "optional": true - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true, - "optional": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - } - } - } -} diff --git a/blue_modules/slip39/package.json b/blue_modules/slip39/package.json deleted file mode 100644 index cffe252a3..000000000 --- a/blue_modules/slip39/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "slip39", - "version": "0.1.7", - "description": "The javascript implementation of the SLIP39 for Shamir's Secret-Sharing for Mnemonic Codes.", - "main": "dist/slip39.js", - "scripts": { - "build": "npx babel src --out-dir dist && ./sed.sh", - "test": "mocha" - }, - "author": "Pal Dorogi \"iLap\" ", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/ilap/slip39-js.git" - }, - "keywords": [ - "SLIP39", - "crypto", - "Shamir", - "Shamir's Secret Sharing", - "Shamir's secret-sharing scheme", - "SSS" - ], - "devDependencies": { - "@babel/cli": "^7.13.14", - "@babel/core": "^7.13.15", - "babel-plugin-transform-bigint": "^1.0.9", - "mocha": "^6.2.0" - }, - "dependencies": { - "create-hmac": "^1.1.3", - "jsbi": "^3.1.4", - "pbkdf2": "^3.0.9", - "randombytes": "^2.0.1" - } -} diff --git a/blue_modules/slip39/scripts/test_publish.sh b/blue_modules/slip39/scripts/test_publish.sh deleted file mode 100755 index 4e1f96cac..000000000 --- a/blue_modules/slip39/scripts/test_publish.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -PROJ_DIR=`dirname $0`/.. - -cd "${PROJ_DIR}" - -sudo npm install . -g -sudo npm link - -mkdir ../pubtest -cd ../pubtest -cat > package.json << EOF -{ - "name": "test-slip39", - "version": "0.1.0", - "private": true -} -EOF - -npm install ../slip39-js - -cd - -sudo npm uninstall . -g -sudo npm unlink -rm -rf ../pubtest diff --git a/blue_modules/slip39/sed.sh b/blue_modules/slip39/sed.sh deleted file mode 100755 index c73523bdc..000000000 --- a/blue_modules/slip39/sed.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -sed -i '' 's/import JSBI from \"jsbi\"/const JSBI = require(\"jsbi\/dist\/jsbi-cjs.js\")/' dist/*.js diff --git a/blue_modules/slip39/src/slip39.js b/blue_modules/slip39/src/slip39.js deleted file mode 100644 index af284d265..000000000 --- a/blue_modules/slip39/src/slip39.js +++ /dev/null @@ -1,190 +0,0 @@ -/* eslint-disable radix */ -const slipHelper = require('./slip39_helper.js'); - -const MAX_DEPTH = 2; - -/** - * Slip39Node - * For root node, description refers to the whole set's title e.g. "Hardware wallet X SSSS shares" - * For children nodes, description refers to the group e.g. "Family group: mom, dad, sister, wife" - */ -class Slip39Node { - constructor(index = 0, description = '', mnemonic = '', children = []) { - this.index = index; - this.description = description; - this.mnemonic = mnemonic; - this.children = children; - } - - get mnemonics() { - if (this.children.length === 0) { - return [this.mnemonic]; - } - const result = this.children.reduce((prev, item) => { - return prev.concat(item.mnemonics); - }, []); - return result; - } -} - -// -// The javascript implementation of the SLIP-0039: Shamir's Secret-Sharing for Mnemonic Codes -// see: https://github.com/satoshilabs/slips/blob/master/slip-0039.md) -// -class Slip39 { - constructor({ - iterationExponent = 0, - identifier, - groupCount, - groupThreshold - } = {}) { - this.iterationExponent = iterationExponent; - this.identifier = identifier; - this.groupCount = groupCount; - this.groupThreshold = groupThreshold; - } - - static fromArray(masterSecret, { - passphrase = '', - threshold = 1, - groups = [ - [1, 1, 'Default 1-of-1 group share'] - ], - iterationExponent = 0, - title = 'My default slip39 shares' - } = {}) { - if (masterSecret.length * 8 < slipHelper.MIN_ENTROPY_BITS) { - throw Error(`The length of the master secret (${masterSecret.length} bytes) must be at least ${slipHelper.bitsToBytes(slipHelper.MIN_ENTROPY_BITS)} bytes.`); - } - - if (masterSecret.length % 2 !== 0) { - throw Error('The length of the master secret in bytes must be an even number.'); - } - - if (!/^[\x20-\x7E]*$/.test(passphrase)) { - throw Error('The passphrase must contain only printable ASCII characters (code points 32-126).'); - } - - if (threshold > groups.length) { - throw Error(`The requested group threshold (${threshold}) must not exceed the number of groups (${groups.length}).`); - } - - groups.forEach((item) => { - if (item[0] === 1 && item[1] > 1) { - throw Error(`Creating multiple member shares with member threshold 1 is not allowed. Use 1-of-1 member sharing instead. ${groups.join()}`); - } - }); - - const identifier = slipHelper.generateIdentifier(); - - const slip = new Slip39({ - iterationExponent: iterationExponent, - identifier: identifier, - groupCount: groups.length, - groupThreshold: threshold - }); - - const encryptedMasterSecret = slipHelper.crypt( - masterSecret, passphrase, iterationExponent, slip.identifier); - - const root = slip.buildRecursive( - new Slip39Node(0, title), - groups, - encryptedMasterSecret, - threshold - ); - - slip.root = root; - return slip; - } - - buildRecursive(currentNode, nodes, secret, threshold, index) { - // It means it's a leaf. - if (nodes.length === 0) { - const mnemonic = slipHelper.encodeMnemonic(this.identifier, this.iterationExponent, index, - this.groupThreshold, this.groupCount, currentNode.index, threshold, secret); - - currentNode.mnemonic = mnemonic; - return currentNode; - } - - const secretShares = slipHelper.splitSecret(threshold, nodes.length, secret); - let children = []; - let idx = 0; - - nodes.forEach((item) => { - // n=threshold - const n = item[0]; - // m=members - const m = item[1]; - // d=description - const d = item[2] || ''; - - // Generate leaf members, means their `m` is `0` - const members = Array().slip39Generate(m, () => [n, 0, d]); - - const node = new Slip39Node(idx, d); - const branch = this.buildRecursive( - node, - members, - secretShares[idx], - n, - currentNode.index); - - children = children.concat(branch); - idx = idx + 1; - }); - currentNode.children = children; - return currentNode; - } - - static recoverSecret(mnemonics, passphrase) { - return slipHelper.combineMnemonics(mnemonics, passphrase); - } - - static validateMnemonic(mnemonic) { - return slipHelper.validateMnemonic(mnemonic); - } - - fromPath(path) { - this.validatePath(path); - - const children = this.parseChildren(path); - - if (typeof children === 'undefined' || children.length === 0) { - return this.root; - } - - return children.reduce((prev, childNumber) => { - let childrenLen = prev.children.length; - if (childNumber >= childrenLen) { - throw new Error(`The path index (${childNumber}) exceeds the children index (${childrenLen - 1}).`); - } - - return prev.children[childNumber]; - }, this.root); - } - - validatePath(path) { - if (!path.match(/(^r)(\/\d{1,2}){0,2}$/)) { - throw new Error('Expected valid path e.g. "r/0/0".'); - } - - const depth = path.split('/'); - const pathLength = depth.length - 1; - if (pathLength > MAX_DEPTH) { - throw new Error(`Path\'s (${path}) max depth (${MAX_DEPTH}) is exceeded (${pathLength}).`); - } - } - - parseChildren(path) { - const splitted = path.split('/').slice(1); - - const result = splitted.map((pathFragment) => { - return parseInt(pathFragment); - }); - return result; - } -} - -exports = module.exports = Slip39; diff --git a/blue_modules/slip39/src/slip39_helper.js b/blue_modules/slip39/src/slip39_helper.js deleted file mode 100644 index ac3b2f286..000000000 --- a/blue_modules/slip39/src/slip39_helper.js +++ /dev/null @@ -1,2238 +0,0 @@ -/* eslint-disable no-array-constructor */ -const pbkdf2 = require('pbkdf2') -const createHmac = require('create-hmac') -const randombytes = require('randombytes'); - -// The length of the radix in bits. -const RADIX_BITS = 10; - -// The length of the random identifier in bits. -const ID_BITS_LENGTH = 15; - -// The length of the iteration exponent in bits. -const ITERATION_EXP_BITS_LENGTH = 5; - -// The length of the random identifier and iteration exponent in words. -const ITERATION_EXP_WORDS_LENGTH = - parseInt((ID_BITS_LENGTH + ITERATION_EXP_BITS_LENGTH + RADIX_BITS - 1) / RADIX_BITS, 10); - -// The maximum iteration exponent -const MAX_ITERATION_EXP = Math.pow(2, ITERATION_EXP_BITS_LENGTH); - -// The maximum number of shares that can be created. -const MAX_SHARE_COUNT = 16; - -// The length of the RS1024 checksum in words. -const CHECKSUM_WORDS_LENGTH = 3; - -// The length of the digest of the shared secret in bytes. -const DIGEST_LENGTH = 4; - -// The customization string used in the RS1024 checksum and in the PBKDF2 salt. -const SALT_STRING = 'shamir'; - -// The minimum allowed entropy of the master secret. -const MIN_ENTROPY_BITS = 128; - -// The minimum allowed length of the mnemonic in words. -const METADATA_WORDS_LENGTH = - ITERATION_EXP_WORDS_LENGTH + 2 + CHECKSUM_WORDS_LENGTH; - -// The length of the mnemonic in words without the share value. -const MNEMONICS_WORDS_LENGTH = parseInt( - METADATA_WORDS_LENGTH + (MIN_ENTROPY_BITS + RADIX_BITS - 1) / RADIX_BITS, 10); - -// The minimum number of iterations to use in PBKDF2. -const ITERATION_COUNT = 10000; - -// The number of rounds to use in the Feistel cipher. -const ROUND_COUNT = 4; - -// The index of the share containing the digest of the shared secret. -const DIGEST_INDEX = 254; - -// The index of the share containing the shared secret. -const SECRET_INDEX = 255; - -// -// Helper functions for SLIP39 implementation. -// -String.prototype.slip39EncodeHex = function () { - let bytes = []; - for (let i = 0; i < this.length; ++i) { - bytes.push(this.charCodeAt(i)); - } - return bytes; -}; - -Array.prototype.slip39DecodeHex = function () { - let str = []; - const hex = this.toString().split(','); - for (let i = 0; i < hex.length; i++) { - str.push(String.fromCharCode(hex[i])); - } - return str.toString().replace(/,/g, ''); -}; - -Array.prototype.slip39Generate = function (m, v = _ => _) { - let n = m || this.length; - for (let i = 0; i < n; i++) { - this[i] = v(i); - } - return this; -}; - -Array.prototype.toHexString = function () { - return Array.prototype.map.call(this, function (byte) { - return ('0' + (byte & 0xFF).toString(16)).slice(-2); - }).join(''); -}; - -Array.prototype.toByteArray = function (hexString) { - for (let i = 0; i < hexString.length; i = i + 2) { - this.push(parseInt(hexString.substr(i, 2), 16)); - } - return this; -}; - -const BIGINT_WORD_BITS = BigInt(8); - -function decodeBigInt(bytes) { - let result = BigInt(0); - for (let i = 0; i < bytes.length; i++) { - let b = BigInt(bytes[bytes.length - i - 1]); - result = result + (b << BIGINT_WORD_BITS * BigInt(i)); - } - return result; -} - -function encodeBigInt(number, paddedLength = 0) { - let num = number; - const BYTE_MASK = BigInt(0xff); - const BIGINT_ZERO = BigInt(0); - let result = new Array(0); - - while (num > BIGINT_ZERO) { - let i = parseInt(num & BYTE_MASK, 10); - result.unshift(i); - num = num >> BIGINT_WORD_BITS; - } - - // Zero padding to the length - for (let i = result.length; i < paddedLength; i++) { - result.unshift(0); - } - - if (paddedLength !== 0 && result.length > paddedLength) { - throw new Error(`Error in encoding BigInt value, expected less than ${paddedLength} length value, got ${result.length}`); - } - - return result; -} - -function bitsToBytes(n) { - const res = (n + 7) / 8; - const b = parseInt(res, RADIX_BITS); - return b; -} - -function bitsToWords(n) { - const res = (n + RADIX_BITS - 1) / RADIX_BITS; - const b = parseInt(res, RADIX_BITS); - return b; -} - -// -// Returns a randomly generated integer in the range 0, ... , 2**ID_LENGTH_BITS - 1. -// -function randomBytes(length = 32) { - let randoms = randombytes(length); - return Array.prototype.slice.call(randoms, 0); -} - -// -// The round function used internally by the Feistel cipher. -// -function roundFunction(round, passphrase, exp, salt, secret) { - const saltedSecret = salt.concat(secret); - const roundedPhrase = [round].concat(passphrase); - const count = (ITERATION_COUNT << exp) / ROUND_COUNT; - - const key = pbkdf2.pbkdf2Sync(Buffer.from(roundedPhrase), Buffer.from(saltedSecret), count, secret.length, 'sha256'); - return Array.prototype.slice.call(key, 0); -} - -function crypt(masterSecret, passphrase, iterationExponent, - identifier, - encrypt = true) { - // Iteration exponent validated here. - if (iterationExponent < 0 || iterationExponent > MAX_ITERATION_EXP) { - throw Error(`Invalid iteration exponent (${iterationExponent}). Expected between 0 and ${MAX_ITERATION_EXP}`); - } - - let IL = masterSecret.slice().slice(0, masterSecret.length / 2); - let IR = masterSecret.slice().slice(masterSecret.length / 2); - - const pwd = passphrase.slip39EncodeHex(); - - const salt = getSalt(identifier); - - let range = Array().slip39Generate(ROUND_COUNT); - range = encrypt ? range : range.reverse(); - - range.forEach((round) => { - const f = roundFunction(round, pwd, iterationExponent, salt, IR); - const t = xor(IL, f); - IL = IR; - IR = t; - }); - return IR.concat(IL); -} - -function createDigest(randomData, sharedSecret) { - const hmac = createHmac('sha256', Buffer.from(randomData)); - - hmac.update(Buffer.from(sharedSecret)); - - let result = hmac.digest(); - result = result.slice(0, 4); - return Array.prototype.slice.call(result, 0); -} - -function splitSecret(threshold, shareCount, sharedSecret) { - if (threshold <= 0) { - throw Error(`The requested threshold (${threshold}) must be a positive integer.`); - } - - if (threshold > shareCount) { - throw Error(`The requested threshold (${threshold}) must not exceed the number of shares (${shareCount}).`); - } - - if (shareCount > MAX_SHARE_COUNT) { - throw Error(`The requested number of shares (${shareCount}) must not exceed ${MAX_SHARE_COUNT}.`); - } - // If the threshold is 1, then the digest of the shared secret is not used. - if (threshold === 1) { - return Array().slip39Generate(shareCount, () => sharedSecret); - } - - const randomShareCount = threshold - 2; - - const randomPart = randomBytes(sharedSecret.length - DIGEST_LENGTH); - const digest = createDigest(randomPart, sharedSecret); - - let baseShares = new Map(); - let shares = []; - if (randomShareCount) { - shares = Array().slip39Generate( - randomShareCount, () => randomBytes(sharedSecret.length)); - shares.forEach((item, idx) => { - baseShares.set(idx, item); - }); - } - baseShares.set(DIGEST_INDEX, digest.concat(randomPart)); - baseShares.set(SECRET_INDEX, sharedSecret); - - for (let i = randomShareCount; i < shareCount; i++) { - const rr = interpolate(baseShares, i); - shares.push(rr); - } - - return shares; -} - -// -// Returns a randomly generated integer in the range 0, ... , 2**ID_BITS_LENGTH - 1. -// -function generateIdentifier() { - const byte = bitsToBytes(ID_BITS_LENGTH); - const bits = ID_BITS_LENGTH % 8; - const identifier = randomBytes(byte); - - identifier[0] = identifier[0] & (1 << bits) - 1; - - return identifier; -} - -function xor(a, b) { - if (a.length !== b.length) { - throw new Error(`Invalid padding in mnemonic or insufficient length of mnemonics (${a.length} or ${b.length})`); - } - return Array().slip39Generate(a.length, (i) => a[i] ^ b[i]); -} - -function getSalt(identifier) { - const salt = SALT_STRING.slip39EncodeHex(); - return salt.concat(identifier); -} - -function interpolate(shares, x) { - let xCoord = new Set(shares.keys()); - let arr = Array.from(shares.values(), (v) => v.length); - let sharesValueLengths = new Set(arr); - - if (sharesValueLengths.size !== 1) { - throw new Error('Invalid set of shares. All share values must have the same length.'); - } - - if (xCoord.has(x)) { - shares.forEach((v, k) => { - if (k === x) { - return v; - } - }); - } - - // Logarithm of the product of (x_i - x) for i = 1, ... , k. - let logProd = 0; - - shares.forEach((v, k) => { - logProd = logProd + LOG_TABLE[k ^ x]; - }); - - let results = Array().slip39Generate(sharesValueLengths.values().next().value, () => 0); - - shares.forEach((v, k) => { - // The logarithm of the Lagrange basis polynomial evaluated at x. - let sum = 0; - shares.forEach((vv, kk) => { - sum = sum + LOG_TABLE[k ^ kk]; - }); - - // FIXME: -18 % 255 = 237. IT shoulud be 237 and not -18 as it's - // implemented in javascript. - const basis = (logProd - LOG_TABLE[k ^ x] - sum) % 255; - - const logBasisEval = basis < 0 ? 255 + basis : basis; - - v.forEach((item, idx) => { - const shareVal = item; - const intermediateSum = results[idx]; - const r = shareVal !== 0 ? - EXP_TABLE[(LOG_TABLE[shareVal] + logBasisEval) % 255] : 0; - - const res = intermediateSum ^ r; - results[idx] = res; - }); - }); - return results; -} - -function rs1024Polymod(data) { - const GEN = [ - 0xE0E040, - 0x1C1C080, - 0x3838100, - 0x7070200, - 0xE0E0009, - 0x1C0C2412, - 0x38086C24, - 0x3090FC48, - 0x21B1F890, - 0x3F3F120 - ]; - let chk = 1; - - data.forEach((byte) => { - const b = chk >> 20; - chk = (chk & 0xFFFFF) << 10 ^ byte; - - for (let i = 0; i < 10; i++) { - let gen = (b >> i & 1) !== 0 ? GEN[i] : 0; - chk = chk ^ gen; - } - }); - - return chk; -} - -function rs1024CreateChecksum(data) { - const values = SALT_STRING.slip39EncodeHex() - .concat(data) - .concat(Array().slip39Generate(CHECKSUM_WORDS_LENGTH, () => 0)); - const polymod = rs1024Polymod(values) ^ 1; - const result = - Array().slip39Generate(CHECKSUM_WORDS_LENGTH, (i) => polymod >> 10 * i & 1023).reverse(); - - return result; -} - -function rs1024VerifyChecksum(data) { - return rs1024Polymod(SALT_STRING.slip39EncodeHex().concat(data)) === 1; -} - -// -// Converts a list of base 1024 indices in big endian order to an integer value. -// -function intFromIndices(indices) { - let value = BigInt(0); - const radix = BigInt(Math.pow(2, RADIX_BITS)); - indices.forEach((index) => { - value = value * radix + BigInt(index); - }); - - return value; -} - -// -// Converts a Big integer value to indices in big endian order. -// -function intToIndices(value, length, bits) { - const mask = BigInt((1 << bits) - 1); - const result = - Array().slip39Generate(length, (i) => parseInt(value >> BigInt(i) * BigInt(bits) & mask, 10)); - return result.reverse(); -} - -function mnemonicFromIndices(indices) { - const result = indices.map((index) => { - return WORD_LIST[index]; - }); - return result.toString().split(',').join(' '); -} - -function mnemonicToIndices(mnemonic) { - if (typeof mnemonic !== 'string') { - throw new Error(`Mnemonic expected to be typeof string with white space separated words. Instead found typeof ${typeof mnemonic}.`); - } - - const words = mnemonic.toLowerCase().split(' '); - const result = words.reduce((prev, item) => { - const index = WORD_LIST_MAP[item]; - if (typeof index === 'undefined') { - throw new Error(`Invalid mnemonic word ${item}.`); - } - return prev.concat(index); - }, []); - return result; -} - -function recoverSecret(threshold, shares) { - // If the threshold is 1, then the digest of the shared secret is not used. - if (threshold === 1) { - return shares.values().next().value; - } - - const sharedSecret = interpolate(shares, SECRET_INDEX); - const digestShare = interpolate(shares, DIGEST_INDEX); - const digest = digestShare.slice(0, DIGEST_LENGTH); - const randomPart = digestShare.slice(DIGEST_LENGTH); - - const recoveredDigest = createDigest( - randomPart, sharedSecret); - if (!listsAreEqual(digest, recoveredDigest)) { - throw new Error('Invalid digest of the shared secret.'); - } - return sharedSecret; -} - -// -// Combines mnemonic shares to obtain the master secret which was previously -// split using Shamir's secret sharing scheme. -// -function combineMnemonics(mnemonics, passphrase = '') { - if (mnemonics === null || mnemonics.length === 0) { - throw new Error('The list of mnemonics is empty.'); - } - - const decoded = decodeMnemonics(mnemonics); - const identifier = decoded.identifier; - const iterationExponent = decoded.iterationExponent; - const groupThreshold = decoded.groupThreshold; - const groupCount = decoded.groupCount; - const groups = decoded.groups; - - if (groups.size < groupThreshold) { - throw new Error(`Insufficient number of mnemonic groups (${groups.size}). The required number of groups is ${groupThreshold}.`); - } - - if (groups.size !== groupThreshold) { - throw new Error(`Wrong number of mnemonic groups. Expected ${groupThreshold} groups, but ${groups.size} were provided.`); - } - - let allShares = new Map(); - groups.forEach((members, groupIndex) => { - const threshold = members.keys().next().value; - const shares = members.values().next().value; - if (shares.size !== threshold) { - const prefix = groupPrefix( - identifier, - iterationExponent, - groupIndex, - groupThreshold, - groupCount - ); - throw new Error(`Wrong number of mnemonics. Expected ${threshold} mnemonics starting with "${mnemonicFromIndices(prefix)}", \n but ${shares.size} were provided.`); - } - - const recovered = recoverSecret(threshold, shares); - allShares.set(groupIndex, recovered); - }); - - const ems = recoverSecret(groupThreshold, allShares); - const id = intToIndices(BigInt(identifier), ITERATION_EXP_WORDS_LENGTH, 8); - const ms = crypt(ems, passphrase, iterationExponent, id, false); - - return ms; -} - -function decodeMnemonics(mnemonics) { - if (!(mnemonics instanceof Array)) { - throw new Error('Mnemonics should be an array of strings'); - } - const identifiers = new Set(); - const iterationExponents = new Set(); - const groupThresholds = new Set(); - const groupCounts = new Set(); - const groups = new Map(); - - mnemonics.forEach((mnemonic) => { - const decoded = decodeMnemonic(mnemonic); - - identifiers.add(decoded.identifier); - iterationExponents.add(decoded.iterationExponent); - const groupIndex = decoded.groupIndex; - groupThresholds.add(decoded.groupThreshold); - groupCounts.add(decoded.groupCount); - const memberIndex = decoded.memberIndex; - const memberThreshold = decoded.memberThreshold; - const share = decoded.share; - - const group = !groups.has(groupIndex) ? new Map() : groups.get(groupIndex); - const member = !group.has(memberThreshold) ? new Map() : group.get(memberThreshold); - member.set(memberIndex, share); - group.set(memberThreshold, member); - if (group.size !== 1) { - throw new Error('Invalid set of mnemonics. All mnemonics in a group must have the same member threshold.'); - } - groups.set(groupIndex, group); - }); - - if (identifiers.size !== 1 || iterationExponents.size !== 1) { - throw new Error(`Invalid set of mnemonics. All mnemonics must begin with the same ${ITERATION_EXP_WORDS_LENGTH} words.`); - } - - if (groupThresholds.size !== 1) { - throw new Error('Invalid set of mnemonics. All mnemonics must have the same group threshold.'); - } - - if (groupCounts.size !== 1) { - throw new Error('Invalid set of mnemonics. All mnemonics must have the same group count.'); - } - - return { - identifier: identifiers.values().next().value, - iterationExponent: iterationExponents.values().next().value, - groupThreshold: groupThresholds.values().next().value, - groupCount: groupCounts.values().next().value, - groups: groups - }; -} - -// -// Converts a share mnemonic to share data. -// -function decodeMnemonic(mnemonic) { - const data = mnemonicToIndices(mnemonic); - - if (data.length < MNEMONICS_WORDS_LENGTH) { - throw new Error(`Invalid mnemonic length. The length of each mnemonic must be at least ${MNEMONICS_WORDS_LENGTH} words.`); - } - - const paddingLen = RADIX_BITS * (data.length - METADATA_WORDS_LENGTH) % 16; - if (paddingLen > 8) { - throw new Error('Invalid mnemonic length.'); - } - - if (!rs1024VerifyChecksum(data)) { - throw new Error('Invalid mnemonic checksum'); - } - - const idExpInt = - parseInt(intFromIndices(data.slice(0, ITERATION_EXP_WORDS_LENGTH)), 10); - const identifier = idExpInt >> ITERATION_EXP_BITS_LENGTH; - const iterationExponent = idExpInt & (1 << ITERATION_EXP_BITS_LENGTH) - 1; - const tmp = intFromIndices( - data.slice(ITERATION_EXP_WORDS_LENGTH, ITERATION_EXP_WORDS_LENGTH + 2)); - - const indices = intToIndices(tmp, 5, 4); - - const groupIndex = indices[0]; - const groupThreshold = indices[1]; - const groupCount = indices[2]; - const memberIndex = indices[3]; - const memberThreshold = indices[4]; - - const valueData = data.slice( - ITERATION_EXP_WORDS_LENGTH + 2, data.length - CHECKSUM_WORDS_LENGTH); - - if (groupCount < groupThreshold) { - throw new Error(`Invalid mnemonic: ${mnemonic}.\n Group threshold (${groupThreshold}) cannot be greater than group count (${groupCount}).`); - } - - const valueInt = intFromIndices(valueData); - - try { - const valueByteCount = bitsToBytes(RADIX_BITS * valueData.length - paddingLen); - const share = encodeBigInt(valueInt, valueByteCount); - - return { - identifier: identifier, - iterationExponent: iterationExponent, - groupIndex: groupIndex, - groupThreshold: groupThreshold + 1, - groupCount: groupCount + 1, - memberIndex: memberIndex, - memberThreshold: memberThreshold + 1, - share: share - }; - } catch (e) { - throw new Error(`Invalid mnemonic padding (${e})`); - } -} - -function validateMnemonic(mnemonic) { - try { - decodeMnemonic(mnemonic); - return true; - } catch (error) { - return false; - } -} - -function groupPrefix( - identifier, iterationExponent, groupIndex, groupThreshold, groupCount) { - const idExpInt = BigInt( - (identifier << ITERATION_EXP_BITS_LENGTH) + iterationExponent); - - const indc = intToIndices(idExpInt, ITERATION_EXP_WORDS_LENGTH, RADIX_BITS); - - const indc2 = - (groupIndex << 6) + (groupThreshold - 1 << 2) + (groupCount - 1 >> 2); - - indc.push(indc2); - return indc; -} - -function listsAreEqual(a, b) { - if (a === null || b === null || a.length !== b.length) { - return false; - } - - let i = 0; - return a.every((item) => { - return b[i++] === item; - }); -} - -// -// Converts share data to a share mnemonic. -// -function encodeMnemonic( - identifier, - iterationExponent, - groupIndex, - groupThreshold, - groupCount, - memberIndex, - memberThreshold, - value -) { - // Convert the share value from bytes to wordlist indices. - const valueWordCount = bitsToWords(value.length * 8); - - const valueInt = decodeBigInt(value); - let newIdentifier = parseInt(decodeBigInt(identifier), 10); - - const gp = groupPrefix( - newIdentifier, iterationExponent, groupIndex, groupThreshold, groupCount); - const tp = intToIndices(valueInt, valueWordCount, RADIX_BITS); - - const calc = ((groupCount - 1 & 3) << 8) + - (memberIndex << 4) + - (memberThreshold - 1); - - gp.push(calc); - const shareData = gp.concat(tp); - - const checksum = rs1024CreateChecksum(shareData); - - return mnemonicFromIndices(shareData.concat(checksum)); -} - -// The precomputed exponent and log tables. -// ``` -// const exp = List.filled(255, 0) -// const log = List.filled(256, 0) -// const poly = 1 -// -// for (let i = 0; i < exp.length; i++) { -// exp[i] = poly -// log[poly] = i -// // Multiply poly by the polynomial x + 1. -// poly = (poly << 1) ^ poly -// // Reduce poly by x^8 + x^4 + x^3 + x + 1. -// if (poly & 0x100 === 0x100) poly ^= 0x11B -// } -// ``` -const EXP_TABLE = [ - 1, - 3, - 5, - 15, - 17, - 51, - 85, - 255, - 26, - 46, - 114, - 150, - 161, - 248, - 19, - 53, - 95, - 225, - 56, - 72, - 216, - 115, - 149, - 164, - 247, - 2, - 6, - 10, - 30, - 34, - 102, - 170, - 229, - 52, - 92, - 228, - 55, - 89, - 235, - 38, - 106, - 190, - 217, - 112, - 144, - 171, - 230, - 49, - 83, - 245, - 4, - 12, - 20, - 60, - 68, - 204, - 79, - 209, - 104, - 184, - 211, - 110, - 178, - 205, - 76, - 212, - 103, - 169, - 224, - 59, - 77, - 215, - 98, - 166, - 241, - 8, - 24, - 40, - 120, - 136, - 131, - 158, - 185, - 208, - 107, - 189, - 220, - 127, - 129, - 152, - 179, - 206, - 73, - 219, - 118, - 154, - 181, - 196, - 87, - 249, - 16, - 48, - 80, - 240, - 11, - 29, - 39, - 105, - 187, - 214, - 97, - 163, - 254, - 25, - 43, - 125, - 135, - 146, - 173, - 236, - 47, - 113, - 147, - 174, - 233, - 32, - 96, - 160, - 251, - 22, - 58, - 78, - 210, - 109, - 183, - 194, - 93, - 231, - 50, - 86, - 250, - 21, - 63, - 65, - 195, - 94, - 226, - 61, - 71, - 201, - 64, - 192, - 91, - 237, - 44, - 116, - 156, - 191, - 218, - 117, - 159, - 186, - 213, - 100, - 172, - 239, - 42, - 126, - 130, - 157, - 188, - 223, - 122, - 142, - 137, - 128, - 155, - 182, - 193, - 88, - 232, - 35, - 101, - 175, - 234, - 37, - 111, - 177, - 200, - 67, - 197, - 84, - 252, - 31, - 33, - 99, - 165, - 244, - 7, - 9, - 27, - 45, - 119, - 153, - 176, - 203, - 70, - 202, - 69, - 207, - 74, - 222, - 121, - 139, - 134, - 145, - 168, - 227, - 62, - 66, - 198, - 81, - 243, - 14, - 18, - 54, - 90, - 238, - 41, - 123, - 141, - 140, - 143, - 138, - 133, - 148, - 167, - 242, - 13, - 23, - 57, - 75, - 221, - 124, - 132, - 151, - 162, - 253, - 28, - 36, - 108, - 180, - 199, - 82, - 246 -]; -const LOG_TABLE = [ - 0, - 0, - 25, - 1, - 50, - 2, - 26, - 198, - 75, - 199, - 27, - 104, - 51, - 238, - 223, - 3, - 100, - 4, - 224, - 14, - 52, - 141, - 129, - 239, - 76, - 113, - 8, - 200, - 248, - 105, - 28, - 193, - 125, - 194, - 29, - 181, - 249, - 185, - 39, - 106, - 77, - 228, - 166, - 114, - 154, - 201, - 9, - 120, - 101, - 47, - 138, - 5, - 33, - 15, - 225, - 36, - 18, - 240, - 130, - 69, - 53, - 147, - 218, - 142, - 150, - 143, - 219, - 189, - 54, - 208, - 206, - 148, - 19, - 92, - 210, - 241, - 64, - 70, - 131, - 56, - 102, - 221, - 253, - 48, - 191, - 6, - 139, - 98, - 179, - 37, - 226, - 152, - 34, - 136, - 145, - 16, - 126, - 110, - 72, - 195, - 163, - 182, - 30, - 66, - 58, - 107, - 40, - 84, - 250, - 133, - 61, - 186, - 43, - 121, - 10, - 21, - 155, - 159, - 94, - 202, - 78, - 212, - 172, - 229, - 243, - 115, - 167, - 87, - 175, - 88, - 168, - 80, - 244, - 234, - 214, - 116, - 79, - 174, - 233, - 213, - 231, - 230, - 173, - 232, - 44, - 215, - 117, - 122, - 235, - 22, - 11, - 245, - 89, - 203, - 95, - 176, - 156, - 169, - 81, - 160, - 127, - 12, - 246, - 111, - 23, - 196, - 73, - 236, - 216, - 67, - 31, - 45, - 164, - 118, - 123, - 183, - 204, - 187, - 62, - 90, - 251, - 96, - 177, - 134, - 59, - 82, - 161, - 108, - 170, - 85, - 41, - 157, - 151, - 178, - 135, - 144, - 97, - 190, - 220, - 252, - 188, - 149, - 207, - 205, - 55, - 63, - 91, - 209, - 83, - 57, - 132, - 60, - 65, - 162, - 109, - 71, - 20, - 42, - 158, - 93, - 86, - 242, - 211, - 171, - 68, - 17, - 146, - 217, - 35, - 32, - 46, - 137, - 180, - 124, - 184, - 38, - 119, - 153, - 227, - 165, - 103, - 74, - 237, - 222, - 197, - 49, - 254, - 24, - 13, - 99, - 140, - 128, - 192, - 247, - 112, - 7 -]; - -// -// SLIP39 wordlist -// -const WORD_LIST = [ - 'academic', - 'acid', - 'acne', - 'acquire', - 'acrobat', - 'activity', - 'actress', - 'adapt', - 'adequate', - 'adjust', - 'admit', - 'adorn', - 'adult', - 'advance', - 'advocate', - 'afraid', - 'again', - 'agency', - 'agree', - 'aide', - 'aircraft', - 'airline', - 'airport', - 'ajar', - 'alarm', - 'album', - 'alcohol', - 'alien', - 'alive', - 'alpha', - 'already', - 'alto', - 'aluminum', - 'always', - 'amazing', - 'ambition', - 'amount', - 'amuse', - 'analysis', - 'anatomy', - 'ancestor', - 'ancient', - 'angel', - 'angry', - 'animal', - 'answer', - 'antenna', - 'anxiety', - 'apart', - 'aquatic', - 'arcade', - 'arena', - 'argue', - 'armed', - 'artist', - 'artwork', - 'aspect', - 'auction', - 'august', - 'aunt', - 'average', - 'aviation', - 'avoid', - 'award', - 'away', - 'axis', - 'axle', - 'beam', - 'beard', - 'beaver', - 'become', - 'bedroom', - 'behavior', - 'being', - 'believe', - 'belong', - 'benefit', - 'best', - 'beyond', - 'bike', - 'biology', - 'birthday', - 'bishop', - 'black', - 'blanket', - 'blessing', - 'blimp', - 'blind', - 'blue', - 'body', - 'bolt', - 'boring', - 'born', - 'both', - 'boundary', - 'bracelet', - 'branch', - 'brave', - 'breathe', - 'briefing', - 'broken', - 'brother', - 'browser', - 'bucket', - 'budget', - 'building', - 'bulb', - 'bulge', - 'bumpy', - 'bundle', - 'burden', - 'burning', - 'busy', - 'buyer', - 'cage', - 'calcium', - 'camera', - 'campus', - 'canyon', - 'capacity', - 'capital', - 'capture', - 'carbon', - 'cards', - 'careful', - 'cargo', - 'carpet', - 'carve', - 'category', - 'cause', - 'ceiling', - 'center', - 'ceramic', - 'champion', - 'change', - 'charity', - 'check', - 'chemical', - 'chest', - 'chew', - 'chubby', - 'cinema', - 'civil', - 'class', - 'clay', - 'cleanup', - 'client', - 'climate', - 'clinic', - 'clock', - 'clogs', - 'closet', - 'clothes', - 'club', - 'cluster', - 'coal', - 'coastal', - 'coding', - 'column', - 'company', - 'corner', - 'costume', - 'counter', - 'course', - 'cover', - 'cowboy', - 'cradle', - 'craft', - 'crazy', - 'credit', - 'cricket', - 'criminal', - 'crisis', - 'critical', - 'crowd', - 'crucial', - 'crunch', - 'crush', - 'crystal', - 'cubic', - 'cultural', - 'curious', - 'curly', - 'custody', - 'cylinder', - 'daisy', - 'damage', - 'dance', - 'darkness', - 'database', - 'daughter', - 'deadline', - 'deal', - 'debris', - 'debut', - 'decent', - 'decision', - 'declare', - 'decorate', - 'decrease', - 'deliver', - 'demand', - 'density', - 'deny', - 'depart', - 'depend', - 'depict', - 'deploy', - 'describe', - 'desert', - 'desire', - 'desktop', - 'destroy', - 'detailed', - 'detect', - 'device', - 'devote', - 'diagnose', - 'dictate', - 'diet', - 'dilemma', - 'diminish', - 'dining', - 'diploma', - 'disaster', - 'discuss', - 'disease', - 'dish', - 'dismiss', - 'display', - 'distance', - 'dive', - 'divorce', - 'document', - 'domain', - 'domestic', - 'dominant', - 'dough', - 'downtown', - 'dragon', - 'dramatic', - 'dream', - 'dress', - 'drift', - 'drink', - 'drove', - 'drug', - 'dryer', - 'duckling', - 'duke', - 'duration', - 'dwarf', - 'dynamic', - 'early', - 'earth', - 'easel', - 'easy', - 'echo', - 'eclipse', - 'ecology', - 'edge', - 'editor', - 'educate', - 'either', - 'elbow', - 'elder', - 'election', - 'elegant', - 'element', - 'elephant', - 'elevator', - 'elite', - 'else', - 'email', - 'emerald', - 'emission', - 'emperor', - 'emphasis', - 'employer', - 'empty', - 'ending', - 'endless', - 'endorse', - 'enemy', - 'energy', - 'enforce', - 'engage', - 'enjoy', - 'enlarge', - 'entrance', - 'envelope', - 'envy', - 'epidemic', - 'episode', - 'equation', - 'equip', - 'eraser', - 'erode', - 'escape', - 'estate', - 'estimate', - 'evaluate', - 'evening', - 'evidence', - 'evil', - 'evoke', - 'exact', - 'example', - 'exceed', - 'exchange', - 'exclude', - 'excuse', - 'execute', - 'exercise', - 'exhaust', - 'exotic', - 'expand', - 'expect', - 'explain', - 'express', - 'extend', - 'extra', - 'eyebrow', - 'facility', - 'fact', - 'failure', - 'faint', - 'fake', - 'false', - 'family', - 'famous', - 'fancy', - 'fangs', - 'fantasy', - 'fatal', - 'fatigue', - 'favorite', - 'fawn', - 'fiber', - 'fiction', - 'filter', - 'finance', - 'findings', - 'finger', - 'firefly', - 'firm', - 'fiscal', - 'fishing', - 'fitness', - 'flame', - 'flash', - 'flavor', - 'flea', - 'flexible', - 'flip', - 'float', - 'floral', - 'fluff', - 'focus', - 'forbid', - 'force', - 'forecast', - 'forget', - 'formal', - 'fortune', - 'forward', - 'founder', - 'fraction', - 'fragment', - 'frequent', - 'freshman', - 'friar', - 'fridge', - 'friendly', - 'frost', - 'froth', - 'frozen', - 'fumes', - 'funding', - 'furl', - 'fused', - 'galaxy', - 'game', - 'garbage', - 'garden', - 'garlic', - 'gasoline', - 'gather', - 'general', - 'genius', - 'genre', - 'genuine', - 'geology', - 'gesture', - 'glad', - 'glance', - 'glasses', - 'glen', - 'glimpse', - 'goat', - 'golden', - 'graduate', - 'grant', - 'grasp', - 'gravity', - 'gray', - 'greatest', - 'grief', - 'grill', - 'grin', - 'grocery', - 'gross', - 'group', - 'grownup', - 'grumpy', - 'guard', - 'guest', - 'guilt', - 'guitar', - 'gums', - 'hairy', - 'hamster', - 'hand', - 'hanger', - 'harvest', - 'have', - 'havoc', - 'hawk', - 'hazard', - 'headset', - 'health', - 'hearing', - 'heat', - 'helpful', - 'herald', - 'herd', - 'hesitate', - 'hobo', - 'holiday', - 'holy', - 'home', - 'hormone', - 'hospital', - 'hour', - 'huge', - 'human', - 'humidity', - 'hunting', - 'husband', - 'hush', - 'husky', - 'hybrid', - 'idea', - 'identify', - 'idle', - 'image', - 'impact', - 'imply', - 'improve', - 'impulse', - 'include', - 'income', - 'increase', - 'index', - 'indicate', - 'industry', - 'infant', - 'inform', - 'inherit', - 'injury', - 'inmate', - 'insect', - 'inside', - 'install', - 'intend', - 'intimate', - 'invasion', - 'involve', - 'iris', - 'island', - 'isolate', - 'item', - 'ivory', - 'jacket', - 'jerky', - 'jewelry', - 'join', - 'judicial', - 'juice', - 'jump', - 'junction', - 'junior', - 'junk', - 'jury', - 'justice', - 'kernel', - 'keyboard', - 'kidney', - 'kind', - 'kitchen', - 'knife', - 'knit', - 'laden', - 'ladle', - 'ladybug', - 'lair', - 'lamp', - 'language', - 'large', - 'laser', - 'laundry', - 'lawsuit', - 'leader', - 'leaf', - 'learn', - 'leaves', - 'lecture', - 'legal', - 'legend', - 'legs', - 'lend', - 'length', - 'level', - 'liberty', - 'library', - 'license', - 'lift', - 'likely', - 'lilac', - 'lily', - 'lips', - 'liquid', - 'listen', - 'literary', - 'living', - 'lizard', - 'loan', - 'lobe', - 'location', - 'losing', - 'loud', - 'loyalty', - 'luck', - 'lunar', - 'lunch', - 'lungs', - 'luxury', - 'lying', - 'lyrics', - 'machine', - 'magazine', - 'maiden', - 'mailman', - 'main', - 'makeup', - 'making', - 'mama', - 'manager', - 'mandate', - 'mansion', - 'manual', - 'marathon', - 'march', - 'market', - 'marvel', - 'mason', - 'material', - 'math', - 'maximum', - 'mayor', - 'meaning', - 'medal', - 'medical', - 'member', - 'memory', - 'mental', - 'merchant', - 'merit', - 'method', - 'metric', - 'midst', - 'mild', - 'military', - 'mineral', - 'minister', - 'miracle', - 'mixed', - 'mixture', - 'mobile', - 'modern', - 'modify', - 'moisture', - 'moment', - 'morning', - 'mortgage', - 'mother', - 'mountain', - 'mouse', - 'move', - 'much', - 'mule', - 'multiple', - 'muscle', - 'museum', - 'music', - 'mustang', - 'nail', - 'national', - 'necklace', - 'negative', - 'nervous', - 'network', - 'news', - 'nuclear', - 'numb', - 'numerous', - 'nylon', - 'oasis', - 'obesity', - 'object', - 'observe', - 'obtain', - 'ocean', - 'often', - 'olympic', - 'omit', - 'oral', - 'orange', - 'orbit', - 'order', - 'ordinary', - 'organize', - 'ounce', - 'oven', - 'overall', - 'owner', - 'paces', - 'pacific', - 'package', - 'paid', - 'painting', - 'pajamas', - 'pancake', - 'pants', - 'papa', - 'paper', - 'parcel', - 'parking', - 'party', - 'patent', - 'patrol', - 'payment', - 'payroll', - 'peaceful', - 'peanut', - 'peasant', - 'pecan', - 'penalty', - 'pencil', - 'percent', - 'perfect', - 'permit', - 'petition', - 'phantom', - 'pharmacy', - 'photo', - 'phrase', - 'physics', - 'pickup', - 'picture', - 'piece', - 'pile', - 'pink', - 'pipeline', - 'pistol', - 'pitch', - 'plains', - 'plan', - 'plastic', - 'platform', - 'playoff', - 'pleasure', - 'plot', - 'plunge', - 'practice', - 'prayer', - 'preach', - 'predator', - 'pregnant', - 'premium', - 'prepare', - 'presence', - 'prevent', - 'priest', - 'primary', - 'priority', - 'prisoner', - 'privacy', - 'prize', - 'problem', - 'process', - 'profile', - 'program', - 'promise', - 'prospect', - 'provide', - 'prune', - 'public', - 'pulse', - 'pumps', - 'punish', - 'puny', - 'pupal', - 'purchase', - 'purple', - 'python', - 'quantity', - 'quarter', - 'quick', - 'quiet', - 'race', - 'racism', - 'radar', - 'railroad', - 'rainbow', - 'raisin', - 'random', - 'ranked', - 'rapids', - 'raspy', - 'reaction', - 'realize', - 'rebound', - 'rebuild', - 'recall', - 'receiver', - 'recover', - 'regret', - 'regular', - 'reject', - 'relate', - 'remember', - 'remind', - 'remove', - 'render', - 'repair', - 'repeat', - 'replace', - 'require', - 'rescue', - 'research', - 'resident', - 'response', - 'result', - 'retailer', - 'retreat', - 'reunion', - 'revenue', - 'review', - 'reward', - 'rhyme', - 'rhythm', - 'rich', - 'rival', - 'river', - 'robin', - 'rocky', - 'romantic', - 'romp', - 'roster', - 'round', - 'royal', - 'ruin', - 'ruler', - 'rumor', - 'sack', - 'safari', - 'salary', - 'salon', - 'salt', - 'satisfy', - 'satoshi', - 'saver', - 'says', - 'scandal', - 'scared', - 'scatter', - 'scene', - 'scholar', - 'science', - 'scout', - 'scramble', - 'screw', - 'script', - 'scroll', - 'seafood', - 'season', - 'secret', - 'security', - 'segment', - 'senior', - 'shadow', - 'shaft', - 'shame', - 'shaped', - 'sharp', - 'shelter', - 'sheriff', - 'short', - 'should', - 'shrimp', - 'sidewalk', - 'silent', - 'silver', - 'similar', - 'simple', - 'single', - 'sister', - 'skin', - 'skunk', - 'slap', - 'slavery', - 'sled', - 'slice', - 'slim', - 'slow', - 'slush', - 'smart', - 'smear', - 'smell', - 'smirk', - 'smith', - 'smoking', - 'smug', - 'snake', - 'snapshot', - 'sniff', - 'society', - 'software', - 'soldier', - 'solution', - 'soul', - 'source', - 'space', - 'spark', - 'speak', - 'species', - 'spelling', - 'spend', - 'spew', - 'spider', - 'spill', - 'spine', - 'spirit', - 'spit', - 'spray', - 'sprinkle', - 'square', - 'squeeze', - 'stadium', - 'staff', - 'standard', - 'starting', - 'station', - 'stay', - 'steady', - 'step', - 'stick', - 'stilt', - 'story', - 'strategy', - 'strike', - 'style', - 'subject', - 'submit', - 'sugar', - 'suitable', - 'sunlight', - 'superior', - 'surface', - 'surprise', - 'survive', - 'sweater', - 'swimming', - 'swing', - 'switch', - 'symbolic', - 'sympathy', - 'syndrome', - 'system', - 'tackle', - 'tactics', - 'tadpole', - 'talent', - 'task', - 'taste', - 'taught', - 'taxi', - 'teacher', - 'teammate', - 'teaspoon', - 'temple', - 'tenant', - 'tendency', - 'tension', - 'terminal', - 'testify', - 'texture', - 'thank', - 'that', - 'theater', - 'theory', - 'therapy', - 'thorn', - 'threaten', - 'thumb', - 'thunder', - 'ticket', - 'tidy', - 'timber', - 'timely', - 'ting', - 'tofu', - 'together', - 'tolerate', - 'total', - 'toxic', - 'tracks', - 'traffic', - 'training', - 'transfer', - 'trash', - 'traveler', - 'treat', - 'trend', - 'trial', - 'tricycle', - 'trip', - 'triumph', - 'trouble', - 'true', - 'trust', - 'twice', - 'twin', - 'type', - 'typical', - 'ugly', - 'ultimate', - 'umbrella', - 'uncover', - 'undergo', - 'unfair', - 'unfold', - 'unhappy', - 'union', - 'universe', - 'unkind', - 'unknown', - 'unusual', - 'unwrap', - 'upgrade', - 'upstairs', - 'username', - 'usher', - 'usual', - 'valid', - 'valuable', - 'vampire', - 'vanish', - 'various', - 'vegan', - 'velvet', - 'venture', - 'verdict', - 'verify', - 'very', - 'veteran', - 'vexed', - 'victim', - 'video', - 'view', - 'vintage', - 'violence', - 'viral', - 'visitor', - 'visual', - 'vitamins', - 'vocal', - 'voice', - 'volume', - 'voter', - 'voting', - 'walnut', - 'warmth', - 'warn', - 'watch', - 'wavy', - 'wealthy', - 'weapon', - 'webcam', - 'welcome', - 'welfare', - 'western', - 'width', - 'wildlife', - 'window', - 'wine', - 'wireless', - 'wisdom', - 'withdraw', - 'wits', - 'wolf', - 'woman', - 'work', - 'worthy', - 'wrap', - 'wrist', - 'writing', - 'wrote', - 'year', - 'yelp', - 'yield', - 'yoga', - 'zero' -]; - -const WORD_LIST_MAP = WORD_LIST.reduce((obj, val, idx) => { - obj[val] = idx; - return obj; -}, {}); - -exports = module.exports = { - MIN_ENTROPY_BITS, - generateIdentifier, - encodeMnemonic, - validateMnemonic, - splitSecret, - combineMnemonics, - crypt, - bitsToBytes -}; diff --git a/blue_modules/slip39/test/test.js b/blue_modules/slip39/test/test.js deleted file mode 100644 index 341a3c2ce..000000000 --- a/blue_modules/slip39/test/test.js +++ /dev/null @@ -1,353 +0,0 @@ -const assert = require('assert'); -const slip39 = require('../dist/slip39'); - -const MASTERSECRET = 'ABCDEFGHIJKLMNOP'; -const MS = MASTERSECRET.slip39EncodeHex(); -const PASSPHRASE = 'TREZOR'; -const ONE_GROUP = [ - [5, 7] -]; - -const slip15 = slip39.fromArray(MS, { - passphrase: PASSPHRASE, - threshold: 1, - groups: ONE_GROUP -}); - -const slip15NoPW = slip39.fromArray(MS, { - threshold: 1, - groups: ONE_GROUP -}); - -// -// Shuffle -// -function shuffle(array) { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } -} - -// -// Combination C(n, k) of the grooups -// -function getCombinations(array, k) { - let result = []; - let combinations = []; - - function helper(level, start) { - for (let i = start; i < array.length - k + level + 1; i++) { - combinations[level] = array[i]; - - if (level < k - 1) { - helper(level + 1, i + 1); - } else { - result.push(combinations.slice(0)); - } - } - } - - helper(0, 0); - return result; -} - -describe('Basic Tests', () => { - describe('Test threshold 1 with 5 of 7 shares of a group combinations', () => { - let mnemonics = slip15.fromPath('r/0').mnemonics; - - let combinations = getCombinations([0, 1, 2, 3, 4, 5, 6], 5); - combinations.forEach((item) => { - shuffle(item); - let description = `Test shuffled combination ${item.join(' ')}.`; - it(description, () => { - let shares = item.map((idx) => mnemonics[idx]); - assert(MS.slip39DecodeHex() === slip39.recoverSecret(shares, PASSPHRASE) - .slip39DecodeHex()); - }); - }); - }); - - describe('Test passhrase', () => { - let mnemonics = slip15.fromPath('r/0').mnemonics; - let nopwMnemonics = slip15NoPW.fromPath('r/0').mnemonics; - - it('should return valid mastersecret when user submits valid passphrase', () => { - assert(MS.slip39DecodeHex() === slip39.recoverSecret(mnemonics.slice(0, 5), PASSPHRASE) - .slip39DecodeHex()); - }); - it('should NOT return valid mastersecret when user submits invalid passphrse', () => { - assert(MS.slip39DecodeHex() !== slip39.recoverSecret(mnemonics.slice(0, 5)) - .slip39DecodeHex()); - }); - it('should return valid mastersecret when user does not submit passphrase', () => { - assert(MS.slip39DecodeHex() === slip39.recoverSecret(nopwMnemonics.slice(0, 5)) - .slip39DecodeHex()); - }); - }); - - describe('Test iteration exponent', () => { - const slip1 = slip39.fromArray(MS, { - iterationExponent: 1 - }); - - const slip2 = slip39.fromArray(MS, { - iterationExponent: 2 - }); - - it('should return valid mastersecret when user apply valid iteration exponent', () => { - assert(MS.slip39DecodeHex() === slip39.recoverSecret(slip1.fromPath('r/0').mnemonics) - .slip39DecodeHex()); - - assert(MS.slip39DecodeHex() === slip39.recoverSecret(slip2.fromPath('r/0').mnemonics) - .slip39DecodeHex()); - }); - /** - * assert.throws(() => x.y.z); - * assert.throws(() => x.y.z, ReferenceError); - * assert.throws(() => x.y.z, ReferenceError, /is not defined/); - * assert.throws(() => x.y.z, /is not defined/); - * assert.doesNotThrow(() => 42); - * assert.throws(() => x.y.z, Error); - * assert.throws(() => model.get.z, /Property does not exist in model schema./) - * Ref: https://stackoverflow.com/questions/21587122/mocha-chai-expect-to-throw-not-catching-thrown-errors - */ - it('should throw an Error when user submits invalid iteration exponent', () => { - assert.throws(() => slip39.fromArray(MS, { - iterationExponent: -1 - }), Error); - assert.throws(() => slip39.fromArray(MS, { - iterationExponent: 33 - }), Error); - }); - }); -}); - -// FIXME: finish it. -describe('Group Sharing Tests', () => { - describe('Test all valid combinations of mnemonics', () => { - const groups = [ - [3, 5, 'Group 0'], - [3, 3, 'Group 1'], - [2, 5, 'Group 2'], - [1, 1, 'Group 3'] - ]; - const slip = slip39.fromArray(MS, { - threshold: 2, - groups: groups, - title: 'Trezor one SSSS' - }); - - const group2Mnemonics = slip.fromPath('r/2').mnemonics; - const group3Mnemonic = slip.fromPath('r/3').mnemonics[0]; - - it('Should include overall split title', () => { - assert.equal(slip.fromPath('r').description, 'Trezor one SSSS'); - }); - it('Should include group descriptions', () => { - assert.equal(slip.fromPath('r/0').description, 'Group 0'); - assert.equal(slip.fromPath('r/1').description, 'Group 1'); - assert.equal(slip.fromPath('r/2').description, 'Group 2'); - assert.equal(slip.fromPath('r/3').description, 'Group 3'); - }); - it('Should return the valid master secret when it tested with minimal sets of mnemonics.', () => { - const mnemonics = group2Mnemonics.filter((_, index) => { - return index === 0 || index === 2; - }).concat(group3Mnemonic); - - assert(MS.slip39DecodeHex() === slip39.recoverSecret(mnemonics).slip39DecodeHex()); - }); - it('TODO: Should NOT return the valid master secret when one complete group and one incomplete group out of two groups required', () => { - assert(true); - }); - it('TODO: Should return the valid master secret when one group of two required but only one applied.', () => { - assert(true); - }); - }); -}); - -describe('Original test vectors Tests', () => { - let fs = require('fs'); - let path = require('path'); - let filePath = path.join(__dirname, 'vectors.json'); - - let content = fs.readFileSync(filePath, 'utf-8'); - - const tests = JSON.parse(content); - tests.forEach((item) => { - let description = item[0]; - let mnemonics = item[1]; - let masterSecret = Buffer.from(item[2], 'hex'); - - it(description, () => { - if (masterSecret.length !== 0) { - let ms = slip39.recoverSecret(mnemonics, PASSPHRASE); - assert(masterSecret.every((v, i) => v === ms[i])); - } else { - assert.throws(() => slip39.recoverSecret(mnemonics, PASSPHRASE), Error); - } - }); - }); -}); - -describe('Invalid Shares', () => { - const tests = [ - ['Short master secret', 1, [ - [2, 3] - ], MS.slice(0, 14)], - ['Odd length master secret', 1, [ - [2, 3] - ], MS.concat([55])], - ['Group threshold exceeds number of groups', 3, [ - [3, 5], - [2, 5] - ], MS], - ['Invalid group threshold.', 0, [ - [3, 5], - [2, 5] - ], MS], - ['Member threshold exceeds number of members', 2, [ - [3, 2], - [2, 5] - ], MS], - ['Invalid member threshold', 2, [ - [0, 2], - [2, 5] - ], MS], - ['Group with multiple members and threshold 1', 2, [ - [3, 5], - [1, 3], - [2, 5] - ], MS] - ]; - - tests.forEach((item) => { - let description = item[0]; - let threshold = item[1]; - - let groups = item[2]; - let secret = item[3]; - - it(description, () => { - assert.throws(() => - slip39.fromArray(secret, { - threshold: threshold, - groups: groups - }), Error); - }); - }); -}); - -describe('Mnemonic Validation', () => { - describe('Valid Mnemonics', () => { - let mnemonics = slip15.fromPath('r/0').mnemonics; - - mnemonics.forEach((mnemonic, index) => { - it(`Mnemonic at index ${index} should be valid`, () => { - const isValid = slip39.validateMnemonic(mnemonic); - - assert(isValid); - }); - }); - }); - - const vectors = [ - [ - '2. Mnemonic with invalid checksum (128 bits)', - [ - 'duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision kidney' - ] - ], - [ - '21. Mnemonic with invalid checksum (256 bits)', - [ - 'theory painting academic academic armed sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips brave detect lunar' - ] - ], - [ - '3. Mnemonic with invalid padding (128 bits)', - [ - 'duckling enlarge academic academic email result length solution fridge kidney coal piece deal husband erode duke ajar music cargo fitness' - ] - ], - [ - '22. Mnemonic with invalid padding (256 bits)', - [ - 'theory painting academic academic campus sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips facility obtain sister' - ] - ], - [ - '10. Mnemonics with greater group threshold than group counts (128 bits)', - [ - 'music husband acrobat acid artist finance center either graduate swimming object bike medical clothes station aspect spider maiden bulb welcome', - 'music husband acrobat agency advance hunting bike corner density careful material civil evil tactics remind hawk discuss hobo voice rainbow', - 'music husband beard academic black tricycle clock mayor estimate level photo episode exclude ecology papa source amazing salt verify divorce' - ] - ], - [ - '29. Mnemonics with greater group threshold than group counts (256 bits)', - [ - 'smirk pink acrobat acid auction wireless impulse spine sprinkle fortune clogs elbow guest hush loyalty crush dictate tracks airport talent', - 'smirk pink acrobat agency dwarf emperor ajar organize legs slice harvest plastic dynamic style mobile float bulb health coding credit', - 'smirk pink beard academic alto strategy carve shame language rapids ruin smart location spray training acquire eraser endorse submit peaceful' - ] - ], - [ - '39. Mnemonic with insufficient length', - [ - 'junk necklace academic academic acne isolate join hesitate lunar roster dough calcium chemical ladybug amount mobile glasses verify cylinder' - ] - ], - [ - '40. Mnemonic with invalid master secret length', - [ - 'fraction necklace academic academic award teammate mouse regular testify coding building member verdict purchase blind camera duration email prepare spirit quarter' - ] - ] - ]; - - vectors.forEach((item) => { - const description = item[0]; - const mnemonics = item[1]; - - describe(description, () => { - mnemonics.forEach((mnemonic, index) => { - it(`Mnemonic at index ${index} should be invalid`, () => { - const isValid = slip39.validateMnemonic(mnemonic); - - assert(isValid === false); - }); - }); - }); - }); -}); - -function itTestArray(t, g, gs) { - it( - `recover master secret for ${t} shares (threshold=${t}) of ${g} '[1, 1,]' groups",`, - () => { - let slip = slip39.fromArray(MS, { - groups: gs.slice(0, g), - passphrase: PASSPHRASE, - threshold: t - }); - - let mnemonics = slip.fromPath('r').mnemonics.slice(0, t); - - let recoveredSecret = - slip39.recoverSecret(mnemonics, PASSPHRASE); - - assert(MASTERSECRET === String.fromCharCode(...recoveredSecret)); - }); -} - -describe('Groups test (T=1, N=1 e.g. [1,1]) - ', () => { - let totalGroups = 16; - let groups = Array.from(Array(totalGroups), () => [1, 1]); - - for (group = 1; group <= totalGroups; group++) { - for (threshold = 1; threshold <= group; threshold++) { - itTestArray(threshold, group, groups); - } - } -}); diff --git a/blue_modules/slip39/test/vectors.json b/blue_modules/slip39/test/vectors.json deleted file mode 100644 index ca83a10e9..000000000 --- a/blue_modules/slip39/test/vectors.json +++ /dev/null @@ -1,322 +0,0 @@ -[ - [ - "1. Valid mnemonic without sharing (128 bits)", - [ - "duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision keyboard" - ], - "bb54aac4b89dc868ba37d9cc21b2cece" - ], - [ - "2. Mnemonic with invalid checksum (128 bits)", - [ - "duckling enlarge academic academic agency result length solution fridge kidney coal piece deal husband erode duke ajar critical decision kidney" - ], - "" - ], - [ - "3. Mnemonic with invalid padding (128 bits)", - [ - "duckling enlarge academic academic email result length solution fridge kidney coal piece deal husband erode duke ajar music cargo fitness" - ], - "" - ], - [ - "4. Basic sharing 2-of-3 (128 bits)", - [ - "shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed", - "shadow pistol academic acid actress prayer class unknown daughter sweater depict flip twice unkind craft early superior advocate guest smoking" - ], - "b43ceb7e57a0ea8766221624d01b0864" - ], - [ - "5. Basic sharing 2-of-3 (128 bits)", - [ - "shadow pistol academic always adequate wildlife fancy gross oasis cylinder mustang wrist rescue view short owner flip making coding armed" - ], - "" - ], - [ - "6. Mnemonics with different identifiers (128 bits)", - [ - "adequate smoking academic acid debut wine petition glen cluster slow rhyme slow simple epidemic rumor junk tracks treat olympic tolerate", - "adequate stay academic agency agency formal party ting frequent learn upstairs remember smear leaf damage anatomy ladle market hush corner" - ], - "" - ], - [ - "7. Mnemonics with different iteration exponents (128 bits)", - [ - "peasant leaves academic acid desert exact olympic math alive axle trial tackle drug deny decent smear dominant desert bucket remind", - "peasant leader academic agency cultural blessing percent network envelope medal junk primary human pumps jacket fragment payroll ticket evoke voice" - ], - "" - ], - [ - "8. Mnemonics with mismatching group thresholds (128 bits)", - [ - "liberty category beard echo animal fawn temple briefing math username various wolf aviation fancy visual holy thunder yelp helpful payment", - "liberty category beard email beyond should fancy romp founder easel pink holy hairy romp loyalty material victim owner toxic custody", - "liberty category academic easy being hazard crush diminish oral lizard reaction cluster force dilemma deploy force club veteran expect photo" - ], - "" - ], - [ - "9. Mnemonics with mismatching group counts (128 bits)", - [ - "average senior academic leaf broken teacher expect surface hour capture obesity desire negative dynamic dominant pistol mineral mailman iris aide", - "average senior academic agency curious pants blimp spew clothes slice script dress wrap firm shaft regular slavery negative theater roster" - ], - "" - ], - [ - "10. Mnemonics with greater group threshold than group counts (128 bits)", - [ - "music husband acrobat acid artist finance center either graduate swimming object bike medical clothes station aspect spider maiden bulb welcome", - "music husband acrobat agency advance hunting bike corner density careful material civil evil tactics remind hawk discuss hobo voice rainbow", - "music husband beard academic black tricycle clock mayor estimate level photo episode exclude ecology papa source amazing salt verify divorce" - ], - "" - ], - [ - "11. Mnemonics with duplicate member indices (128 bits)", - [ - "device stay academic always dive coal antenna adult black exceed stadium herald advance soldier busy dryer daughter evaluate minister laser", - "device stay academic always dwarf afraid robin gravity crunch adjust soul branch walnut coastal dream costume scholar mortgage mountain pumps" - ], - "" - ], - [ - "12. Mnemonics with mismatching member thresholds (128 bits)", - [ - "hour painting academic academic device formal evoke guitar random modern justice filter withdraw trouble identify mailman insect general cover oven", - "hour painting academic agency artist again daisy capital beaver fiber much enjoy suitable symbolic identify photo editor romp float echo" - ], - "" - ], - [ - "13. Mnemonics giving an invalid digest (128 bits)", - [ - "guilt walnut academic acid deliver remove equip listen vampire tactics nylon rhythm failure husband fatigue alive blind enemy teaspoon rebound", - "guilt walnut academic agency brave hamster hobo declare herd taste alpha slim criminal mild arcade formal romp branch pink ambition" - ], - "" - ], - [ - "14. Insufficient number of groups (128 bits, case 1)", - [ - "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice" - ], - "" - ], - [ - "15. Insufficient number of groups (128 bits, case 2)", - [ - "eraser senior decision scared cargo theory device idea deliver modify curly include pancake both news skin realize vitamins away join", - "eraser senior decision roster beard treat identify grumpy salt index fake aviation theater cubic bike cause research dragon emphasis counter" - ], - "" - ], - [ - "16. Threshold number of groups, but insufficient number of members in one group (128 bits)", - [ - "eraser senior decision shadow artist work morning estate greatest pipeline plan ting petition forget hormone flexible general goat admit surface", - "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice" - ], - "" - ], - [ - "17. Threshold number of groups and members in each group (128 bits, case 1)", - [ - "eraser senior decision roster beard treat identify grumpy salt index fake aviation theater cubic bike cause research dragon emphasis counter", - "eraser senior ceramic snake clay various huge numb argue hesitate auction category timber browser greatest hanger petition script leaf pickup", - "eraser senior ceramic shaft dynamic become junior wrist silver peasant force math alto coal amazing segment yelp velvet image paces", - "eraser senior ceramic round column hawk trust auction smug shame alive greatest sheriff living perfect corner chest sled fumes adequate", - "eraser senior decision smug corner ruin rescue cubic angel tackle skin skunk program roster trash rumor slush angel flea amazing" - ], - "7c3397a292a5941682d7a4ae2d898d11" - ], - [ - "18. Threshold number of groups and members in each group (128 bits, case 2)", - [ - "eraser senior decision smug corner ruin rescue cubic angel tackle skin skunk program roster trash rumor slush angel flea amazing", - "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice", - "eraser senior decision scared cargo theory device idea deliver modify curly include pancake both news skin realize vitamins away join" - ], - "7c3397a292a5941682d7a4ae2d898d11" - ], - [ - "19. Threshold number of groups and members in each group (128 bits, case 3)", - [ - "eraser senior beard romp adorn nuclear spill corner cradle style ancient family general leader ambition exchange unusual garlic promise voice", - "eraser senior acrobat romp bishop medical gesture pumps secret alive ultimate quarter priest subject class dictate spew material endless market" - ], - "7c3397a292a5941682d7a4ae2d898d11" - ], - [ - "20. Valid mnemonic without sharing (256 bits)", - [ - "theory painting academic academic armed sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips brave detect luck" - ], - "989baf9dcaad5b10ca33dfd8cc75e42477025dce88ae83e75a230086a0e00e92" - ], - [ - "21. Mnemonic with invalid checksum (256 bits)", - [ - "theory painting academic academic armed sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips brave detect lunar" - ], - "" - ], - [ - "22. Mnemonic with invalid padding (256 bits)", - [ - "theory painting academic academic campus sweater year military elder discuss acne wildlife boring employer fused large satoshi bundle carbon diagnose anatomy hamster leaves tracks paces beyond phantom capital marvel lips facility obtain sister" - ], - "" - ], - [ - "23. Basic sharing 2-of-3 (256 bits)", - [ - "humidity disease academic always aluminum jewelry energy woman receiver strategy amuse duckling lying evidence network walnut tactics forget hairy rebound impulse brother survive clothes stadium mailman rival ocean reward venture always armed unwrap", - "humidity disease academic agency actress jacket gross physics cylinder solution fake mortgage benefit public busy prepare sharp friar change work slow purchase ruler again tricycle involve viral wireless mixture anatomy desert cargo upgrade" - ], - "c938b319067687e990e05e0da0ecce1278f75ff58d9853f19dcaeed5de104aae" - ], - [ - "24. Basic sharing 2-of-3 (256 bits)", - [ - "humidity disease academic always aluminum jewelry energy woman receiver strategy amuse duckling lying evidence network walnut tactics forget hairy rebound impulse brother survive clothes stadium mailman rival ocean reward venture always armed unwrap" - ], - "" - ], - [ - "25. Mnemonics with different identifiers (256 bits)", - [ - "smear husband academic acid deadline scene venture distance dive overall parking bracelet elevator justice echo burning oven chest duke nylon", - "smear isolate academic agency alpha mandate decorate burden recover guard exercise fatal force syndrome fumes thank guest drift dramatic mule" - ], - "" - ], - [ - "26. Mnemonics with different iteration exponents (256 bits)", - [ - "finger trash academic acid average priority dish revenue academic hospital spirit western ocean fact calcium syndrome greatest plan losing dictate", - "finger traffic academic agency building lilac deny paces subject threaten diploma eclipse window unknown health slim piece dragon focus smirk" - ], - "" - ], - [ - "27. Mnemonics with mismatching group thresholds (256 bits)", - [ - "flavor pink beard echo depart forbid retreat become frost helpful juice unwrap reunion credit math burning spine black capital lair", - "flavor pink beard email diet teaspoon freshman identify document rebound cricket prune headset loyalty smell emission skin often square rebound", - "flavor pink academic easy credit cage raisin crazy closet lobe mobile become drink human tactics valuable hand capture sympathy finger" - ], - "" - ], - [ - "28. Mnemonics with mismatching group counts (256 bits)", - [ - "column flea academic leaf debut extra surface slow timber husky lawsuit game behavior husky swimming already paper episode tricycle scroll", - "column flea academic agency blessing garbage party software stadium verify silent umbrella therapy decorate chemical erode dramatic eclipse replace apart" - ], - "" - ], - [ - "29. Mnemonics with greater group threshold than group counts (256 bits)", - [ - "smirk pink acrobat acid auction wireless impulse spine sprinkle fortune clogs elbow guest hush loyalty crush dictate tracks airport talent", - "smirk pink acrobat agency dwarf emperor ajar organize legs slice harvest plastic dynamic style mobile float bulb health coding credit", - "smirk pink beard academic alto strategy carve shame language rapids ruin smart location spray training acquire eraser endorse submit peaceful" - ], - "" - ], - [ - "30. Mnemonics with duplicate member indices (256 bits)", - [ - "fishing recover academic always device craft trend snapshot gums skin downtown watch device sniff hour clock public maximum garlic born", - "fishing recover academic always aircraft view software cradle fangs amazing package plastic evaluate intend penalty epidemic anatomy quarter cage apart" - ], - "" - ], - [ - "31. Mnemonics with mismatching member thresholds (256 bits)", - [ - "evoke garden academic academic answer wolf scandal modern warmth station devote emerald market physics surface formal amazing aquatic gesture medical", - "evoke garden academic agency deal revenue knit reunion decrease magazine flexible company goat repair alarm military facility clogs aide mandate" - ], - "" - ], - [ - "32. Mnemonics giving an invalid digest (256 bits)", - [ - "river deal academic acid average forbid pistol peanut custody bike class aunt hairy merit valid flexible learn ajar very easel", - "river deal academic agency camera amuse lungs numb isolate display smear piece traffic worthy year patrol crush fact fancy emission" - ], - "" - ], - [ - "33. Insufficient number of groups (256 bits, case 1)", - [ - "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium" - ], - "" - ], - [ - "34. Insufficient number of groups (256 bits, case 2)", - [ - "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen", - "wildlife deal decision smug ancestor genuine move huge cubic strategy smell game costume extend swimming false desire fake traffic vegan senior twice timber submit leader payroll fraction apart exact forward pulse tidy install" - ], - "" - ], - [ - "35. Threshold number of groups, but insufficient number of members in one group (256 bits)", - [ - "wildlife deal decision shadow analysis adjust bulb skunk muscle mandate obesity total guitar coal gravity carve slim jacket ruin rebuild ancestor numerous hour mortgage require herd maiden public ceiling pecan pickup shadow club", - "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium" - ], - "" - ], - [ - "36. Threshold number of groups and members in each group (256 bits, case 1)", - [ - "wildlife deal ceramic round aluminum pitch goat racism employer miracle percent math decision episode dramatic editor lily prospect program scene rebuild display sympathy have single mustang junction relate often chemical society wits estate", - "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen", - "wildlife deal ceramic scatter argue equip vampire together ruin reject literary rival distance aquatic agency teammate rebound false argue miracle stay again blessing peaceful unknown cover beard acid island language debris industry idle", - "wildlife deal ceramic snake agree voter main lecture axis kitchen physics arcade velvet spine idea scroll promise platform firm sharp patrol divorce ancestor fantasy forbid goat ajar believe swimming cowboy symbolic plastic spelling", - "wildlife deal decision shadow analysis adjust bulb skunk muscle mandate obesity total guitar coal gravity carve slim jacket ruin rebuild ancestor numerous hour mortgage require herd maiden public ceiling pecan pickup shadow club" - ], - "5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b" - ], - [ - "37. Threshold number of groups and members in each group (256 bits, case 2)", - [ - "wildlife deal decision scared acne fatal snake paces obtain election dryer dominant romp tactics railroad marvel trust helpful flip peanut theory theater photo luck install entrance taxi step oven network dictate intimate listen", - "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium", - "wildlife deal decision smug ancestor genuine move huge cubic strategy smell game costume extend swimming false desire fake traffic vegan senior twice timber submit leader payroll fraction apart exact forward pulse tidy install" - ], - "5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b" - ], - [ - "38. Threshold number of groups and members in each group (256 bits, case 3)", - [ - "wildlife deal beard romp alcohol space mild usual clothes union nuclear testify course research heat listen task location thank hospital slice smell failure fawn helpful priest ambition average recover lecture process dough stadium", - "wildlife deal acrobat romp anxiety axis starting require metric flexible geology game drove editor edge screw helpful have huge holy making pitch unknown carve holiday numb glasses survive already tenant adapt goat fangs" - ], - "5385577c8cfc6c1a8aa0f7f10ecde0a3318493262591e78b8c14c6686167123b" - ], - [ - "39. Mnemonic with insufficient length", - [ - "junk necklace academic academic acne isolate join hesitate lunar roster dough calcium chemical ladybug amount mobile glasses verify cylinder" - ], - "" - ], - [ - "40. Mnemonic with invalid master secret length", - [ - "fraction necklace academic academic award teammate mouse regular testify coding building member verdict purchase blind camera duration email prepare spirit quarter" - ], - "" - ] -] diff --git a/blue_modules/storage-context.js b/blue_modules/storage-context.js index 30c80de12..8c3be1f23 100644 --- a/blue_modules/storage-context.js +++ b/blue_modules/storage-context.js @@ -2,10 +2,11 @@ import { useAsyncStorage } from '@react-native-async-storage/async-storage'; import React, { createContext, useEffect, useState } from 'react'; import { LayoutAnimation } from 'react-native'; -import { AppStorage } from '../class'; import { FiatUnit } from '../models/fiatUnit'; +import loc from '../loc'; const BlueApp = require('../BlueApp'); const BlueElectrum = require('./BlueElectrum'); +const currency = require('../blue_modules/currency'); const _lastTimeTriedToRefetchWallet = {}; // hashmap of timestamps we _started_ refetching some wallet @@ -13,20 +14,20 @@ export const WalletTransactionsStatus = { NONE: false, ALL: true }; export const BlueStorageContext = createContext(); export const BlueStorageProvider = ({ children }) => { const [wallets, setWallets] = useState([]); - const [pendingWallets, setPendingWallets] = useState([]); + const [isImportingWallet, setIsImportingWallet] = useState(false); const [selectedWallet, setSelectedWallet] = useState(''); const [walletTransactionUpdateStatus, setWalletTransactionUpdateStatus] = useState(WalletTransactionsStatus.NONE); const [walletsInitialized, setWalletsInitialized] = useState(false); const [preferredFiatCurrency, _setPreferredFiatCurrency] = useState(FiatUnit.USD); const [language, _setLanguage] = useState(); - const getPreferredCurrencyAsyncStorage = useAsyncStorage(AppStorage.PREFERRED_CURRENCY).getItem; - const getLanguageAsyncStorage = useAsyncStorage(AppStorage.LANG).getItem; + const getPreferredCurrencyAsyncStorage = useAsyncStorage(currency.PREFERRED_CURRENCY).getItem; + const getLanguageAsyncStorage = useAsyncStorage(loc.LANG).getItem; const [isHandOffUseEnabled, setIsHandOffUseEnabled] = useState(false); const [isDrawerListBlurred, _setIsDrawerListBlurred] = useState(false); const setIsHandOffUseEnabledAsyncStorage = value => { setIsHandOffUseEnabled(value); - return BlueApp.setItem(AppStorage.HANDOFF_STORAGE_KEY, value === true ? '1' : ''); + return BlueApp.setIsHandoffEnabled(value); }; const saveToDisk = async () => { @@ -43,7 +44,7 @@ export const BlueStorageProvider = ({ children }) => { useEffect(() => { (async () => { try { - const enabledHandoff = await BlueApp.getItem(AppStorage.HANDOFF_STORAGE_KEY); + const enabledHandoff = await BlueApp.isHandoffEnabled(); setIsHandOffUseEnabled(!!enabledHandoff); } catch (_e) { setIsHandOffUseEnabledAsyncStorage(false); @@ -175,6 +176,8 @@ export const BlueStorageProvider = ({ children }) => { const getHodlHodlSignatureKey = BlueApp.getHodlHodlSignatureKey; const addHodlHodlContract = BlueApp.addHodlHodlContract; const getHodlHodlContracts = BlueApp.getHodlHodlContracts; + const setDoNotTrack = BlueApp.setDoNotTrack; + const isDoNotTrackEnabled = BlueApp.isDoNotTrackEnabled; const getItem = BlueApp.getItem; const setItem = BlueApp.setItem; @@ -183,8 +186,8 @@ export const BlueStorageProvider = ({ children }) => { value={{ wallets, setWalletsWithNewOrder, - pendingWallets, - setPendingWallets, + isImportingWallet, + setIsImportingWallet, txMetadata, saveToDisk, getTransactions, @@ -227,6 +230,8 @@ export const BlueStorageProvider = ({ children }) => { setWalletTransactionUpdateStatus, isDrawerListBlurred, setIsDrawerListBlurred, + setDoNotTrack, + isDoNotTrackEnabled, }} > {children} diff --git a/blue_modules/ur/index.js b/blue_modules/ur/index.js new file mode 100644 index 000000000..4bf6aff12 --- /dev/null +++ b/blue_modules/ur/index.js @@ -0,0 +1,228 @@ +import { URDecoder } from '@ngraveio/bc-ur'; +import b58 from 'bs58check'; +import { + CryptoHDKey, + CryptoKeypath, + CryptoOutput, + PathComponent, + ScriptExpressions, + CryptoPSBT, + CryptoAccount, + Bytes, +} from '@keystonehq/bc-ur-registry/dist'; +import { decodeUR as origDecodeUr, encodeUR as origEncodeUR, extractSingleWorkload as origExtractSingleWorkload } from '../bc-ur/dist'; +import { MultisigCosigner, MultisigHDWallet } from '../../class'; +import { Psbt } from 'bitcoinjs-lib'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const USE_UR_V1 = 'USE_UR_V1'; + +let useURv1 = false; + +(async () => { + try { + useURv1 = !!(await AsyncStorage.getItem(USE_UR_V1)); + } catch (_) {} +})(); + +async function isURv1Enabled() { + try { + return !!(await AsyncStorage.getItem(USE_UR_V1)); + } catch (_) {} + + return false; +} + +async function setUseURv1() { + useURv1 = true; + return AsyncStorage.setItem(USE_UR_V1, '1'); +} + +async function clearUseURv1() { + useURv1 = false; + return AsyncStorage.removeItem(USE_UR_V1); +} + +function encodeUR(arg1, arg2) { + return useURv1 ? encodeURv1(arg1, arg2) : encodeURv2(arg1, arg2); +} + +function encodeURv1(arg1, arg2) { + // first, lets check that its not a cosigner's json, which we do NOT encode at all: + try { + const json = JSON.parse(arg1); + if (json && json.xpub && json.path && json.xfp) return [arg1]; + } catch (_) {} + + return origEncodeUR(arg1, arg2); +} + +/** + * + * @param str {string} For PSBT, or coordination setup (translates to `bytes`) it expects hex string. For ms cosigner it expects plain json string + * @param len {number} lenght of each fragment + * @return {string[]} txt fragments ready to be displayed in dynamic QR + */ +function encodeURv2(str, len) { + // now, lets do some intelligent guessing what we've got here, psbt hex, or json with a multisig cosigner..? + + try { + const cosigner = new MultisigCosigner(str); + + if (cosigner.isValid()) { + let scriptExpressions = false; + + if (cosigner.isNativeSegwit()) { + scriptExpressions = [ScriptExpressions.WITNESS_SCRIPT_HASH]; + } else if (cosigner.isWrappedSegwit()) { + scriptExpressions = [ScriptExpressions.SCRIPT_HASH, ScriptExpressions.WITNESS_SCRIPT_HASH]; + } else if (cosigner.isLegacy()) { + scriptExpressions = [ScriptExpressions.SCRIPT_HASH]; + } else { + return ['unsupported multisig type']; + } + + const cryptoKeyPathComponents = []; + for (const component of cosigner.getPath().split('/')) { + if (component === 'm') continue; + const index = parseInt(component); + const hardened = component.endsWith('h') || component.endsWith("'"); + cryptoKeyPathComponents.push(new PathComponent({ index, hardened })); + } + + const cryptoAccount = new CryptoAccount(Buffer.from(cosigner.getFp(), 'hex'), [ + new CryptoOutput( + scriptExpressions, + new CryptoHDKey({ + isMaster: false, + key: Buffer.from(cosigner.getKeyHex(), 'hex'), + chainCode: Buffer.from(cosigner.getChainCodeHex(), 'hex'), + origin: new CryptoKeypath( + cryptoKeyPathComponents, + Buffer.from(cosigner.getFp(), 'hex'), + cosigner.getDepthNumber(), + ), + parentFingerprint: Buffer.from(cosigner.getParentFingerprintHex(), 'hex'), + }), + ), + ]); + const ur = cryptoAccount.toUREncoder(2000).nextPart(); + return [ur]; + } + } catch (_) {} + + // not account. lets try psbt + + try { + Psbt.fromHex(str); // will throw if not PSBT hex + const data = Buffer.from(str, 'hex'); + const cryptoPSBT = new CryptoPSBT(data); + const encoder = cryptoPSBT.toUREncoder(len); + + const ret = []; + for (let c = 1; c <= encoder.fragmentsLength; c++) { + const ur = encoder.nextPart(); + ret.push(ur); + } + + return ret; + } catch (_) {} + + // fail. fallback to bytes + + const bytes = new Bytes(Buffer.from(str, 'hex')); + const encoder = bytes.toUREncoder(len); + + const ret = []; + for (let c = 1; c <= encoder.fragmentsLength; c++) { + const ur = encoder.nextPart(); + ret.push(ur); + } + + return ret; +} + +function extractSingleWorkload(arg) { + return origExtractSingleWorkload(arg); +} + +function decodeUR(arg) { + try { + return origDecodeUr(arg); + } catch (_) {} + + const decoder = new URDecoder(); + + for (const part of arg) { + decoder.receivePart(part); + } + + if (!decoder.isSuccess()) { + throw new Error(decoder.resultError()); + } + + const decoded = decoder.resultUR(); + + if (decoded.type === 'crypto-psbt') { + const cryptoPsbt = CryptoPSBT.fromCBOR(decoded.cbor); + return cryptoPsbt.getPSBT().toString('hex'); + } + + if (decoded.type === 'bytes') { + const b = Bytes.fromCBOR(decoded.cbor); + return b.getData(); + } + + const cryptoAccount = CryptoAccount.fromCBOR(decoded.cbor); + + // now, crafting zpub out of data we have + const hdKey = cryptoAccount.outputDescriptors[0].getCryptoKey(); + const derivationPath = 'm/' + hdKey.getOrigin().getPath(); + const script = cryptoAccount.outputDescriptors[0].getScriptExpressions()[0].getExpression(); + const isMultisig = + script === ScriptExpressions.WITNESS_SCRIPT_HASH.getExpression() || + // fallback to paths (unreliable). + // dont know how to add ms p2sh (legacy) or p2sh-p2wsh (wrapped segwit) atm + derivationPath === MultisigHDWallet.PATH_LEGACY || + derivationPath === MultisigHDWallet.PATH_WRAPPED_SEGWIT || + derivationPath === MultisigHDWallet.PATH_NATIVE_SEGWIT; + const version = Buffer.from(isMultisig ? '02aa7ed3' : '04b24746', 'hex'); + const parentFingerprint = hdKey.getParentFingerprint(); + const depth = hdKey.getOrigin().getDepth(); + const depthBuf = Buffer.alloc(1); + depthBuf.writeUInt8(depth); + const components = hdKey.getOrigin().getComponents(); + const lastComponents = components[components.length - 1]; + const index = lastComponents.isHardened() ? lastComponents.getIndex() + 0x80000000 : lastComponents.getIndex(); + const indexBuf = Buffer.alloc(4); + indexBuf.writeUInt32BE(index); + const chainCode = hdKey.getChainCode(); + const key = hdKey.getKey(); + const data = Buffer.concat([version, depthBuf, parentFingerprint, indexBuf, chainCode, key]); + + const zpub = b58.encode(data); + + const result = {}; + result.ExtPubKey = zpub; + result.MasterFingerprint = cryptoAccount.getMasterFingerprint().toString('hex').toUpperCase(); + result.AccountKeyPath = derivationPath; + + const str = JSON.stringify(result); + return Buffer.from(str, 'ascii').toString('hex'); // we are expected to return hex-encoded string +} + +class BlueURDecoder extends URDecoder { + toString() { + const decoded = this.resultUR(); + + if (decoded.type === 'crypto-psbt') { + const cryptoPsbt = CryptoPSBT.fromCBOR(decoded.cbor); + return cryptoPsbt.getPSBT().toString('base64'); + } else if (decoded.type === 'bytes') { + const bytes = Bytes.fromCBOR(decoded.cbor); + return Buffer.from(bytes.getData(), 'hex').toString('ascii'); + } + } +} + +export { decodeUR, encodeUR, extractSingleWorkload, BlueURDecoder, isURv1Enabled, setUseURv1, clearUseURv1 }; diff --git a/class/app-storage.js b/class/app-storage.js index 1c2f482d5..c43e5d2d6 100644 --- a/class/app-storage.js +++ b/class/app-storage.js @@ -1,6 +1,7 @@ /* global alert */ import AsyncStorage from '@react-native-async-storage/async-storage'; import RNSecureKeyStore, { ACCESSIBLE } from 'react-native-secure-key-store'; +import * as Keychain from 'react-native-keychain'; import { HDLegacyBreadwalletWallet, HDSegwitP2SHWallet, @@ -20,27 +21,25 @@ import { SLIP39LegacyP2PKHWallet, SLIP39SegwitBech32Wallet, } from './'; +import { randomBytes } from './rng'; const encryption = require('../blue_modules/encryption'); const Realm = require('realm'); const createHash = require('create-hash'); let usedBucketNum = false; +let savingInProgress = 0; // its both a flag and a counter of attempts to write to disk export class AppStorage { static FLAG_ENCRYPTED = 'data_encrypted'; - static LANG = 'lang'; - static EXCHANGE_RATES = 'currency'; static LNDHUB = 'lndhub'; - static ELECTRUM_HOST = 'electrum_host'; - static ELECTRUM_TCP_PORT = 'electrum_tcp_port'; - static ELECTRUM_SSL_PORT = 'electrum_ssl_port'; - static ELECTRUM_SERVER_HISTORY = 'electrum_server_history'; - static PREFERRED_CURRENCY = 'preferredCurrency'; static ADVANCED_MODE_ENABLED = 'advancedmodeenabled'; + static DO_NOT_TRACK = 'donottrack'; static HODL_HODL_API_KEY = 'HODL_HODL_API_KEY'; static HODL_HODL_SIGNATURE_KEY = 'HODL_HODL_SIGNATURE_KEY'; static HODL_HODL_CONTRACTS = 'HODL_HODL_CONTRACTS'; static HANDOFF_STORAGE_KEY = 'HandOff'; + static keys2migrate = [AppStorage.HANDOFF_STORAGE_KEY, AppStorage.DO_NOT_TRACK, AppStorage.ADVANCED_MODE_ENABLED]; + constructor() { /** {Array.} */ this.wallets = []; @@ -48,6 +47,19 @@ export class AppStorage { this.cachedPassword = false; } + async migrateKeys() { + if (!(typeof navigator !== 'undefined' && navigator.product === 'ReactNative')) return; + for (const key of this.constructor.keys2migrate) { + try { + const value = await RNSecureKeyStore.get(key); + if (value) { + await AsyncStorage.setItem(key, value); + await RNSecureKeyStore.remove(key); + } + } catch (_) {} + } + } + /** * Wrapper for storage call. Secure store works only in RN environment. AsyncStorage is * used for cli/tests @@ -79,11 +91,36 @@ export class AppStorage { } }; + /** + * @throws Error + * @param key {string} + * @returns {Promise<*>|null} + */ + getItemWithFallbackToRealm = async key => { + let value; + try { + return await this.getItem(key); + } catch (error) { + console.warn('error reading', key, error.message); + console.warn('fallback to realm'); + const realmKeyValue = await this.openRealmKeyValue(); + const obj = realmKeyValue.objectForPrimaryKey('KeyValue', key); // search for a realm object with a primary key + value = obj?.value; + realmKeyValue.close(); + if (value) { + console.warn('successfully recovered', value.length, 'bytes from realm for key', key); + return value; + } + return null; + } + }; + storageIsEncrypted = async () => { let data; try { - data = await this.getItem(AppStorage.FLAG_ENCRYPTED); + data = await this.getItemWithFallbackToRealm(AppStorage.FLAG_ENCRYPTED); } catch (error) { + console.warn('error reading `' + AppStorage.FLAG_ENCRYPTED + '` key:', error.message); return false; } @@ -211,6 +248,58 @@ export class AppStorage { }); } + /** + * Returns instace of the Realm database, which is encrypted by device unique id + * Database file is static. + * + * @returns {Promise} + */ + async openRealmKeyValue() { + const service = 'realm_encryption_key'; + let password; + const credentials = await Keychain.getGenericPassword({ service }); + if (credentials) { + password = credentials.password; + } else { + const buf = await randomBytes(64); + password = buf.toString('hex'); + await Keychain.setGenericPassword(service, password, { service }); + } + + const buf = Buffer.from(password, 'hex'); + const encryptionKey = Int8Array.from(buf); + const path = 'keyvalue.realm'; + + const schema = [ + { + name: 'KeyValue', + primaryKey: 'key', + properties: { + key: { type: 'string', indexed: true }, + value: 'string', // stringified json, or whatever + }, + }, + ]; + return Realm.open({ + schema, + path, + encryptionKey, + }); + } + + saveToRealmKeyValue(realmkeyValue, key, value) { + realmkeyValue.write(() => { + realmkeyValue.create( + 'KeyValue', + { + key: key, + value: value, + }, + Realm.UpdateMode.Modified, + ); + }); + } + /** * Loads from storage all wallets and * maps them to `this.wallets` @@ -219,7 +308,7 @@ export class AppStorage { * @returns {Promise.} */ async loadFromDisk(password) { - let data = await this.getItem('data'); + let data = await this.getItemWithFallbackToRealm('data'); if (password) { data = this.decryptData(data, password); if (data) { @@ -275,6 +364,14 @@ export class AppStorage { break; case HDAezeedWallet.type: unserializedWallet = HDAezeedWallet.fromJson(key); + // migrate password to this.passphrase field + // remove this code somewhere in year 2022 + if (unserializedWallet.secret.includes(':')) { + const [mnemonic, passphrase] = unserializedWallet.secret.split(':'); + unserializedWallet.secret = mnemonic; + unserializedWallet.passphrase = passphrase; + } + break; case SLIP39SegwitP2SHWallet.type: unserializedWallet = SLIP39SegwitP2SHWallet.fromJson(key); @@ -302,8 +399,7 @@ export class AppStorage { console.log('using wallet-wide settings ', lndhub, 'for ln wallet'); unserializedWallet.setBaseURI(lndhub); } else { - console.log('using default', LightningCustodianWallet.defaultBaseUri, 'for ln wallet'); - unserializedWallet.setBaseURI(LightningCustodianWallet.defaultBaseUri); + console.log('wallet does not have a baseURI. Continuing init...'); } unserializedWallet.init(); break; @@ -405,71 +501,87 @@ export class AppStorage { * @returns {Promise} Result of storage save */ async saveToDisk() { - const walletsToSave = []; - const realm = await this.getRealm(); - for (const key of this.wallets) { - if (typeof key === 'boolean' || key.type === PlaceholderWallet.type) continue; - key.prepareForSerialization(); - delete key.current; - const keyCloned = Object.assign({}, key); // stripped-down version of a wallet to save to secure keystore - if (key._hdWalletInstance) keyCloned._hdWalletInstance = Object.assign({}, key._hdWalletInstance); - this.offloadWalletToRealm(realm, key); - // stripping down: - if (key._txs_by_external_index) { - keyCloned._txs_by_external_index = {}; - keyCloned._txs_by_internal_index = {}; - } - if (key._hdWalletInstance) { - keyCloned._hdWalletInstance._txs_by_external_index = {}; - keyCloned._hdWalletInstance._txs_by_internal_index = {}; - } - walletsToSave.push(JSON.stringify({ ...keyCloned, type: keyCloned.type })); + if (savingInProgress) { + console.warn('saveToDisk is in progress'); + if (++savingInProgress > 10) alert('Critical error. Last actions were not saved'); // should never happen + await new Promise(resolve => setTimeout(resolve, 1000 * savingInProgress)); // sleep + return this.saveToDisk(); } - realm.close(); - let data = { - wallets: walletsToSave, - tx_metadata: this.tx_metadata, - }; + savingInProgress = 1; - if (this.cachedPassword) { - // should find the correct bucket, encrypt and then save - let buckets = await this.getItem('data'); - buckets = JSON.parse(buckets); - const newData = []; - let num = 0; - for (const bucket of buckets) { - let decrypted; - // if we had `usedBucketNum` during loadFromDisk(), no point to try to decode each bucket to find the one we - // need, we just to find bucket with the same index - if (usedBucketNum !== false) { - if (num === usedBucketNum) { - decrypted = true; - } - num++; - } else { - // we dont have `usedBucketNum` for whatever reason, so lets try to decrypt each bucket after bucket - // till we find the right one - decrypted = encryption.decrypt(bucket, this.cachedPassword); - } - - if (!decrypted) { - // no luck decrypting, its not our bucket - newData.push(bucket); - } else { - // decrypted ok, this is our bucket - // we serialize our object's data, encrypt it, and add it to buckets - newData.push(encryption.encrypt(JSON.stringify(data), this.cachedPassword)); - await this.setItem(AppStorage.FLAG_ENCRYPTED, '1'); - } - } - data = newData; - } else { - await this.setItem(AppStorage.FLAG_ENCRYPTED, ''); // drop the flag - } try { - return await this.setItem('data', JSON.stringify(data)); + const walletsToSave = []; + const realm = await this.getRealm(); + for (const key of this.wallets) { + if (typeof key === 'boolean' || key.type === PlaceholderWallet.type) continue; + key.prepareForSerialization(); + delete key.current; + const keyCloned = Object.assign({}, key); // stripped-down version of a wallet to save to secure keystore + if (key._hdWalletInstance) keyCloned._hdWalletInstance = Object.assign({}, key._hdWalletInstance); + this.offloadWalletToRealm(realm, key); + // stripping down: + if (key._txs_by_external_index) { + keyCloned._txs_by_external_index = {}; + keyCloned._txs_by_internal_index = {}; + } + if (key._hdWalletInstance) { + keyCloned._hdWalletInstance._txs_by_external_index = {}; + keyCloned._hdWalletInstance._txs_by_internal_index = {}; + } + walletsToSave.push(JSON.stringify({ ...keyCloned, type: keyCloned.type })); + } + realm.close(); + let data = { + wallets: walletsToSave, + tx_metadata: this.tx_metadata, + }; + + if (this.cachedPassword) { + // should find the correct bucket, encrypt and then save + let buckets = await this.getItemWithFallbackToRealm('data'); + buckets = JSON.parse(buckets); + const newData = []; + let num = 0; + for (const bucket of buckets) { + let decrypted; + // if we had `usedBucketNum` during loadFromDisk(), no point to try to decode each bucket to find the one we + // need, we just to find bucket with the same index + if (usedBucketNum !== false) { + if (num === usedBucketNum) { + decrypted = true; + } + num++; + } else { + // we dont have `usedBucketNum` for whatever reason, so lets try to decrypt each bucket after bucket + // till we find the right one + decrypted = encryption.decrypt(bucket, this.cachedPassword); + } + + if (!decrypted) { + // no luck decrypting, its not our bucket + newData.push(bucket); + } else { + // decrypted ok, this is our bucket + // we serialize our object's data, encrypt it, and add it to buckets + newData.push(encryption.encrypt(JSON.stringify(data), this.cachedPassword)); + } + } + data = newData; + } + + await this.setItem('data', JSON.stringify(data)); + await this.setItem(AppStorage.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); + + // now, backing up same data in realm: + const realmkeyValue = await this.openRealmKeyValue(); + this.saveToRealmKeyValue(realmkeyValue, 'data', JSON.stringify(data)); + this.saveToRealmKeyValue(realmkeyValue, AppStorage.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); + realmkeyValue.close(); } catch (error) { - alert(error.message); + console.error('save to disk exception:', error.message); + alert('save to disk exception: ' + error.message); + } finally { + savingInProgress = 0; } } @@ -644,13 +756,35 @@ export class AppStorage { isAdancedModeEnabled = async () => { try { - return !!(await this.getItem(AppStorage.ADVANCED_MODE_ENABLED)); + return !!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED)); } catch (_) {} return false; }; setIsAdancedModeEnabled = async value => { - await this.setItem(AppStorage.ADVANCED_MODE_ENABLED, value ? '1' : ''); + await AsyncStorage.setItem(AppStorage.ADVANCED_MODE_ENABLED, value ? '1' : ''); + }; + + isHandoffEnabled = async () => { + try { + return !!(await AsyncStorage.getItem(AppStorage.HANDOFF_STORAGE_KEY)); + } catch (_) {} + return false; + }; + + setIsHandoffEnabled = async value => { + await AsyncStorage.setItem(AppStorage.HANDOFF_STORAGE_KEY, value ? '1' : ''); + }; + + isDoNotTrackEnabled = async () => { + try { + return !!(await AsyncStorage.getItem(AppStorage.DO_NOT_TRACK)); + } catch (_) {} + return false; + }; + + setDoNotTrack = async value => { + await AsyncStorage.setItem(AppStorage.DO_NOT_TRACK, value ? '1' : ''); }; /** diff --git a/class/biometrics.js b/class/biometrics.js index 554e1ad64..d1e14b944 100644 --- a/class/biometrics.js +++ b/class/biometrics.js @@ -8,9 +8,10 @@ import RNSecureKeyStore from 'react-native-secure-key-store'; import loc from '../loc'; import { useContext } from 'react'; import { BlueStorageContext } from '../blue_modules/storage-context'; +import * as Sentry from '@sentry/react-native'; function Biometric() { - const { getItem, setItem, setResetOnAppUninstallTo } = useContext(BlueStorageContext); + const { getItem, setItem } = useContext(BlueStorageContext); Biometric.STORAGEKEY = 'Biometrics'; Biometric.FaceID = 'Face ID'; Biometric.TouchID = 'Touch ID'; @@ -42,10 +43,9 @@ function Biometric() { try { const enabledBiometrics = await getItem(Biometric.STORAGEKEY); return !!enabledBiometrics; - } catch (_e) { - await setItem(Biometric.STORAGEKEY, ''); - return false; - } + } catch (_) {} + + return false; }; Biometric.isBiometricUseCapableAndEnabled = async () => { @@ -72,10 +72,10 @@ function Biometric() { }; Biometric.clearKeychain = async () => { + Sentry.captureMessage('Biometric.clearKeychain()'); await RNSecureKeyStore.remove('data'); await RNSecureKeyStore.remove('data_encrypted'); await RNSecureKeyStore.remove(Biometric.STORAGEKEY); - await setResetOnAppUninstallTo(true); NavigationService.dispatch(StackActions.replace('WalletsRoot')); }; diff --git a/class/deeplink-schema-match.js b/class/deeplink-schema-match.js index 1835f164c..503cf3287 100644 --- a/class/deeplink-schema-match.js +++ b/class/deeplink-schema-match.js @@ -158,6 +158,7 @@ class DeeplinkSchemaMatch { const safelloStateToken = urlObject.query['safello-state-token']; let wallet; + // eslint-disable-next-line no-unreachable-loop for (const w of context.wallets) { wallet = w; break; @@ -254,8 +255,7 @@ class DeeplinkSchemaMatch { { screen: 'LappBrowser', params: { - fromSecret: lnWallet.getSecret(), - fromWallet: lnWallet, + walletID: lnWallet.getID(), url: urlObject.query.url, }, }, diff --git a/class/hd-segwit-bech32-transaction.js b/class/hd-segwit-bech32-transaction.js index de82f92e7..c7402c665 100644 --- a/class/hd-segwit-bech32-transaction.js +++ b/class/hd-segwit-bech32-transaction.js @@ -348,6 +348,7 @@ export class HDSegwitBech32Transaction { let add = 0; while (add <= 128) { + // eslint-disable-next-line no-var var { tx, inputs, outputs, fee } = this._wallet.createTransaction( unconfirmedUtxos, [{ address: myAddress }], diff --git a/class/lnurl.js b/class/lnurl.js index 8a7a3922c..b3004d8ca 100644 --- a/class/lnurl.js +++ b/class/lnurl.js @@ -1,7 +1,6 @@ -import bech32 from 'bech32'; +import { bech32 } from 'bech32'; import bolt11 from 'bolt11'; const CryptoJS = require('crypto-js'); - const createHash = require('create-hash'); /** @@ -20,7 +19,7 @@ export default class Lnurl { } static findlnurl(bodyOfText) { - var res = /^(?:http.*[&?]lightning=|lightning:)?(lnurl1[02-9ac-hj-np-z]+)/.exec(bodyOfText.toLowerCase()); + const res = /^(?:http.*[&?]lightning=|lightning:)?(lnurl1[02-9ac-hj-np-z]+)/.exec(bodyOfText.toLowerCase()); if (res) { return res[1]; } @@ -97,7 +96,14 @@ export default class Lnurl { if (!this._lnurlPayServicePayload) throw new Error('this._lnurlPayServicePayload is not set'); if (!this._lnurlPayServicePayload.callback) throw new Error('this._lnurlPayServicePayload.callback is not set'); if (amountSat < this._lnurlPayServicePayload.min || amountSat > this._lnurlPayServicePayload.max) - throw new Error('amount is not right, ' + amountSat + ' should be between ' + this._lnurlPayServicePayload.min + ' and ' + this._lnurlPayServicePayload.max); + throw new Error( + 'amount is not right, ' + + amountSat + + ' should be between ' + + this._lnurlPayServicePayload.min + + ' and ' + + this._lnurlPayServicePayload.max, + ); const nonce = Math.floor(Math.random() * 2e16).toString(16); const separator = this._lnurlPayServicePayload.callback.indexOf('?') === -1 ? '?' : '&'; const urlToFetch = this._lnurlPayServicePayload.callback + separator + 'amount=' + Math.floor(amountSat * 1000) + '&nonce=' + nonce; @@ -131,8 +137,8 @@ export default class Lnurl { const data = reply; // parse metadata and extract things from it - var image; - var description; + let image; + let description; const kvs = JSON.parse(data.metadata); for (let i = 0; i < kvs.length; i++) { const [k, v] = kvs[i]; @@ -156,7 +162,7 @@ export default class Lnurl { fixed: min === max, min, max, - domain: data.callback.match(new RegExp('https://([^/]+)/'))[1], + domain: data.callback.match(/https:\/\/([^/]+)\//)[1], metadata: data.metadata, description, image, diff --git a/class/multisig-cosigner.js b/class/multisig-cosigner.js index 6cab04f26..81a62f4ff 100644 --- a/class/multisig-cosigner.js +++ b/class/multisig-cosigner.js @@ -69,6 +69,21 @@ export class MultisigCosigner { this._valid = false; } + // is it cobo crypto-account URv2 ? + try { + const json = JSON.parse(data); + if (json && json.ExtPubKey && json.MasterFingerprint && json.AccountKeyPath) { + this._fp = json.MasterFingerprint; + this._xpub = json.ExtPubKey; + this._path = json.AccountKeyPath; + this._cosigners = [true]; + this._valid = true; + return; + } + } catch (_) { + this._valid = false; + } + // is it coldcard json? try { const json = JSON.parse(data); @@ -149,4 +164,48 @@ export class MultisigCosigner { getAllCosigners() { return this._cosigners; } + + isNativeSegwit() { + return this.getXpub().startsWith('Zpub'); + } + + isWrappedSegwit() { + return this.getXpub().startsWith('Ypub'); + } + + isLegacy() { + return this.getXpub().startsWith('xpub'); + } + + getChainCodeHex() { + let data = b58.decode(this.getXpub()); + data = data.slice(4); + data = data.slice(1); + data = data.slice(4); + data = data.slice(4, 36); + return data.toString('hex'); + } + + getKeyHex() { + let data = b58.decode(this.getXpub()); + data = data.slice(4); + data = data.slice(1); + data = data.slice(4); + data = data.slice(36); + return data.toString('hex'); + } + + getParentFingerprintHex() { + let data = b58.decode(this.getXpub()); + data = data.slice(4); + data = data.slice(1); + data = data.slice(0, 4); + return data.toString('hex'); + } + + getDepthNumber() { + let data = b58.decode(this.getXpub()); + data = data.slice(4, 5); + return data.readInt8(); + } } diff --git a/class/quick-actions.js b/class/quick-actions.js index 5506d73cb..5beb014dd 100644 --- a/class/quick-actions.js +++ b/class/quick-actions.js @@ -34,6 +34,11 @@ function DeviceQuickActions() { }); }; + DeviceQuickActions.popInitialAction = async () => { + const data = await QuickActions.popInitialAction(); + return data; + }; + DeviceQuickActions.getEnabled = async () => { try { const isEnabled = await AsyncStorage.getItem(DeviceQuickActions.STORAGE_KEY); diff --git a/class/quick-actions.windows.js b/class/quick-actions.windows.js index a56acf66b..842d0f50f 100644 --- a/class/quick-actions.windows.js +++ b/class/quick-actions.windows.js @@ -7,6 +7,8 @@ function DeviceQuickActions() { return false; }; + DeviceQuickActions.popInitialAction = () => {}; + return null; } diff --git a/class/wallet-import.js b/class/wallet-import.js index 7c9d81b1b..eb5dd5f79 100644 --- a/class/wallet-import.js +++ b/class/wallet-import.js @@ -8,13 +8,13 @@ import { HDLegacyP2PKHWallet, HDSegwitBech32Wallet, LightningCustodianWallet, - PlaceholderWallet, SegwitBech32Wallet, HDLegacyElectrumSeedP2PKHWallet, HDSegwitElectrumSeedP2WPKHWallet, HDAezeedWallet, MultisigHDWallet, SLIP39LegacyP2PKHWallet, + PlaceholderWallet, SLIP39SegwitP2SHWallet, SLIP39SegwitBech32Wallet, } from '.'; @@ -30,16 +30,15 @@ const wif = require('wif'); const prompt = require('../blue_modules/prompt'); function WalletImport() { - const { wallets, pendingWallets, setPendingWallets, saveToDisk, addWallet } = useContext(BlueStorageContext); + const { wallets, saveToDisk, addWallet, setIsImportingWallet } = useContext(BlueStorageContext); /** * * @param w {AbstractWallet} - * @param additionalProperties key-values passed from outside. Used only to set up `masterFingerprint` property for watch-only wallet * @returns {Promise} * @private */ - WalletImport._saveWallet = async (w, additionalProperties) => { + WalletImport._saveWallet = async w => { IdleTimerManager.setIdleTimerDisabled(false); if (WalletImport.isWalletImported(w)) { WalletImport.presentWalletAlreadyExistsAlert(); @@ -49,11 +48,6 @@ function WalletImport() { ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false }); if (w.getLabel() === emptyWalletLabel) w.setLabel(loc.wallets.import_imported + ' ' + w.typeReadable); w.setUserHasSavedExport(true); - if (additionalProperties) { - for (const [key, value] of Object.entries(additionalProperties)) { - w[key] = value; - } - } addWallet(w); await saveToDisk(); A(A.ENUM.CREATED_WALLET); @@ -61,15 +55,20 @@ function WalletImport() { Notifications.majorTomToGroundControl(w.getAllExternalAddresses(), [], []); }; - WalletImport.removePlaceholderWallet = () => { - setPendingWallets([]); + WalletImport.isWalletImported = w => { + const wallet = wallets.some(wallet => wallet.getSecret() === w.secret || wallet.getID() === w.getID()); + return !!wallet; }; - WalletImport.isWalletImported = w => { - const wallet = wallets.some( - wallet => (wallet.getSecret() === w.secret || wallet.getID() === w.getID()) && wallet.type !== PlaceholderWallet.type, - ); - return !!wallet; + WalletImport.removePlaceholderWallet = () => { + setIsImportingWallet(false); + }; + + WalletImport.addPlaceholderWallet = (importText, isFailure = false) => { + const placeholderWallet = new PlaceholderWallet(); + placeholderWallet.setSecret(importText); + placeholderWallet.setIsFailure(isFailure); + setIsImportingWallet(placeholderWallet); }; WalletImport.presentWalletAlreadyExistsAlert = () => { @@ -77,25 +76,80 @@ function WalletImport() { alert('This wallet has been previously imported.'); }; - WalletImport.addPlaceholderWallet = (importText, isFailure = false) => { - const wallet = new PlaceholderWallet(); - wallet.setSecret(importText); - wallet.setIsFailure(isFailure); - setPendingWallets([...pendingWallets, wallet]); - return wallet; - }; + /** + * + * @param importText + * @returns {Promise} + * @returns {Promise<{text: string, password: string|void}>} + */ + WalletImport.askPasswordIfNeeded = async importText => { + const text = importText.trim(); + let password; - WalletImport.isCurrentlyImportingWallet = () => { - return wallets.some(wallet => wallet.type === PlaceholderWallet.type); + // BIP38 password required + if (text.startsWith('6P')) { + do { + password = await prompt(loc.wallets.looks_like_bip38, loc.wallets.enter_bip38_password); + } while (!password); + return { text, password }; + } + + // HD BIP39 wallet password is optinal + const hd = new HDSegwitBech32Wallet(); + hd.setSecret(text); + if (hd.validateMnemonic()) { + password = await prompt(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message); + return { text, password }; + } + + // AEZEED password needs to be correct + const aezeed = new HDAezeedWallet(); + aezeed.setSecret(text); + if (await aezeed.mnemonicInvalidPassword()) { + do { + password = await prompt('', loc.wallets.enter_bip38_password); + aezeed.setPassphrase(password); + } while (await aezeed.mnemonicInvalidPassword()); + return { text, password }; + } + + // SLIP39 wallet password is optinal + if (text.includes('\n')) { + const s1 = new SLIP39SegwitP2SHWallet(); + s1.setSecret(text); + + if (s1.validateMnemonic()) { + password = await prompt(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message); + return { text, password }; + } + } + + // ELECTRUM segwit wallet password is optinal + const electrum1 = new HDSegwitElectrumSeedP2WPKHWallet(); + electrum1.setSecret(importText); + if (electrum1.validateMnemonic()) { + password = await prompt(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message); + return { text, password }; + } + + // ELECTRUM legacy wallet password is optinal + const electrum2 = new HDLegacyElectrumSeedP2PKHWallet(); + electrum2.setSecret(importText); + if (electrum2.validateMnemonic()) { + password = await prompt(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message); + return { text, password }; + } + + return { text, password }; }; /** * * @param importText - * @param additionalProperties key-values passed from outside. Used only to set up `masterFingerprint` property for watch-only wallet + * @param password * @returns {Promise} */ - WalletImport.processImportText = async (importText, additionalProperties) => { + WalletImport.processImportText = async (importText, password) => { IdleTimerManager.setIdleTimerDisabled(true); // Plan: // -2. check if BIP38 encrypted @@ -107,6 +161,7 @@ function WalletImport() { // 3. check if its HDLegacyBreadwalletWallet (no BIP, just "m/0") // 3.1 check HD Electrum legacy // 3.2 check if its AEZEED + // 3.3 check if its SLIP39 // 4. check if its Segwit WIF (P2SH) // 5. check if its Legacy WIF // 6. check if its address (watch-only wallet) @@ -116,11 +171,6 @@ function WalletImport() { importText = importText.trim(); if (importText.startsWith('6P')) { - let password = false; - do { - password = await prompt(loc.wallets.looks_like_bip38, loc.wallets.enter_bip38_password, false); - } while (!password); - const decryptedKey = await bip38.decrypt(importText, password); if (decryptedKey) { @@ -147,9 +197,6 @@ function WalletImport() { const split = importText.split('@'); lnd.setBaseURI(split[1]); lnd.setSecret(split[0]); - } else { - lnd.setBaseURI(LightningCustodianWallet.defaultBaseUri); - lnd.setSecret(importText); } lnd.init(); await lnd.authorize(); @@ -164,8 +211,7 @@ function WalletImport() { const hd4 = new HDSegwitBech32Wallet(); hd4.setSecret(importText); if (hd4.validateMnemonic()) { - // OK its a valid BIP39 seed - + hd4.setPassphrase(password); if (await hd4.wasEverUsed()) { await hd4.fetchBalance(); // fetching balance for BIP84 only on purpose return WalletImport._saveWallet(hd4); @@ -173,18 +219,21 @@ function WalletImport() { const hd2 = new HDSegwitP2SHWallet(); hd2.setSecret(importText); + hd2.setPassphrase(password); if (await hd2.wasEverUsed()) { return WalletImport._saveWallet(hd2); } const hd3 = new HDLegacyP2PKHWallet(); hd3.setSecret(importText); + hd3.setPassphrase(password); if (await hd3.wasEverUsed()) { return WalletImport._saveWallet(hd3); } const hd1 = new HDLegacyBreadwalletWallet(); hd1.setSecret(importText); + hd1.setPassphrase(password); if (await hd1.wasEverUsed()) { return WalletImport._saveWallet(hd1); } @@ -230,21 +279,32 @@ function WalletImport() { // if we're here - nope, its not a valid WIF + // maybe its a watch-only address? + const watchOnly = new WatchOnlyWallet(); + watchOnly.setSecret(importText); + if (watchOnly.valid()) { + await watchOnly.fetchBalance(); + return WalletImport._saveWallet(watchOnly); + } + // nope, not watch-only + try { - const hdElectrumSeedLegacy = new HDSegwitElectrumSeedP2WPKHWallet(); - hdElectrumSeedLegacy.setSecret(importText); - if (hdElectrumSeedLegacy.validateMnemonic()) { + const hdElectrum = new HDSegwitElectrumSeedP2WPKHWallet(); + hdElectrum.setSecret(importText); + hdElectrum.setPassphrase(password); + if (hdElectrum.validateMnemonic()) { // not fetching txs or balances, fuck it, yolo, life is too short - return WalletImport._saveWallet(hdElectrumSeedLegacy); + return WalletImport._saveWallet(hdElectrum); } } catch (_) {} try { - const hdElectrumSeedLegacy = new HDLegacyElectrumSeedP2PKHWallet(); - hdElectrumSeedLegacy.setSecret(importText); - if (hdElectrumSeedLegacy.validateMnemonic()) { + const hdElectrum = new HDLegacyElectrumSeedP2PKHWallet(); + hdElectrum.setSecret(importText); + hdElectrum.setPassphrase(password); + if (hdElectrum.validateMnemonic()) { // not fetching txs or balances, fuck it, yolo, life is too short - return WalletImport._saveWallet(hdElectrumSeedLegacy); + return WalletImport._saveWallet(hdElectrum); } } catch (_) {} @@ -252,33 +312,13 @@ function WalletImport() { try { const aezeed = new HDAezeedWallet(); aezeed.setSecret(importText); + aezeed.setPassphrase(password); if (await aezeed.validateMnemonicAsync()) { // not fetching txs or balances, fuck it, yolo, life is too short return WalletImport._saveWallet(aezeed); - } else { - // there is a chance that a password is required - if (await aezeed.mnemonicInvalidPassword()) { - const password = await prompt(loc.wallets.enter_bip38_password, '', false); - if (!password) { - // no passord is basically cancel whole aezeed import process - throw new Error(loc._.bad_password); - } - - const mnemonics = importText.split(':')[0]; - return WalletImport.processImportText(mnemonics + ':' + password); - } } } catch (_) {} - // not valid? maybe its a watch-only address? - - const watchOnly = new WatchOnlyWallet(); - watchOnly.setSecret(importText); - if (watchOnly.valid()) { - await watchOnly.fetchBalance(); - return WalletImport._saveWallet(watchOnly, additionalProperties); - } - // if it is multi-line string, then it is probably SLIP39 wallet // each line - one share if (importText.includes('\n')) { @@ -286,11 +326,13 @@ function WalletImport() { s1.setSecret(importText); if (s1.validateMnemonic()) { + s1.setPassphrase(password); if (await s1.wasEverUsed()) { return WalletImport._saveWallet(s1); } const s2 = new SLIP39LegacyP2PKHWallet(); + s2.setPassphrase(password); s2.setSecret(importText); if (await s2.wasEverUsed()) { return WalletImport._saveWallet(s2); @@ -298,9 +340,8 @@ function WalletImport() { const s3 = new SLIP39SegwitBech32Wallet(); s3.setSecret(importText); - if (await s3.wasEverUsed()) { - return WalletImport._saveWallet(s3); - } + s3.setPassphrase(password); + return WalletImport._saveWallet(s3); } } diff --git a/class/wallets/abstract-hd-electrum-wallet.js b/class/wallets/abstract-hd-electrum-wallet.js index bb9d42a34..5a71e0490 100644 --- a/class/wallets/abstract-hd-electrum-wallet.js +++ b/class/wallets/abstract-hd-electrum-wallet.js @@ -1,4 +1,4 @@ -import bip39 from 'bip39'; +import * as bip39 from 'bip39'; import BigNumber from 'bignumber.js'; import b58 from 'bs58check'; @@ -824,11 +824,18 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { } weOwnAddress(address) { + if (!address) return false; + let cleanAddress = address; + + if (this.segwitType === 'p2wpkh') { + cleanAddress = address.toLowerCase(); + } + for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { - if (this._getExternalAddressByIndex(c) === address) return true; + if (this._getExternalAddressByIndex(c) === cleanAddress) return true; } for (let c = 0; c < this.next_free_change_address_index + this.gap_limit; c++) { - if (this._getInternalAddressByIndex(c) === address) return true; + if (this._getInternalAddressByIndex(c) === cleanAddress) return true; } return false; } @@ -1116,7 +1123,7 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet { * @returns {string} Hex fingerprint */ static mnemonicToFingerprint(mnemonic) { - const seed = bip39.mnemonicToSeed(mnemonic); + const seed = bip39.mnemonicToSeedSync(mnemonic); return AbstractHDElectrumWallet.seedToFingerprint(seed); } diff --git a/class/wallets/abstract-hd-wallet.js b/class/wallets/abstract-hd-wallet.js index 085231e42..58412ff03 100644 --- a/class/wallets/abstract-hd-wallet.js +++ b/class/wallets/abstract-hd-wallet.js @@ -52,7 +52,8 @@ export class AbstractHDWallet extends LegacyWallet { */ _getSeed() { const mnemonic = this.secret; - return bip39.mnemonicToSeed(mnemonic); + const passphrase = this.passphrase; + return bip39.mnemonicToSeedSync(mnemonic, passphrase); } setSecret(newSecret) { @@ -61,6 +62,14 @@ export class AbstractHDWallet extends LegacyWallet { return this; } + setPassphrase(passphrase) { + this.passphrase = passphrase; + } + + getPassphrase() { + return this.passphrase; + } + /** * @return {Boolean} is mnemonic in `this.secret` valid */ @@ -68,10 +77,6 @@ export class AbstractHDWallet extends LegacyWallet { return bip39.validateMnemonic(this.secret); } - getMnemonicToSeedHex() { - return bip39.mnemonicToSeedHex(this.secret); - } - /** * Derives from hierarchy, returns next free address * (the one that has no transactions). Looks for several, @@ -242,15 +247,6 @@ export class AbstractHDWallet extends LegacyWallet { throw new Error('Not implemented'); } - weOwnAddress(addr) { - const hashmap = {}; - for (const a of this.usedAddresses) { - hashmap[a] = 1; - } - - return hashmap[addr] === 1; - } - _getDerivationPathByAddress(address) { throw new Error('Not implemented'); } diff --git a/class/wallets/abstract-wallet.js b/class/wallets/abstract-wallet.ts similarity index 60% rename from class/wallets/abstract-wallet.js rename to class/wallets/abstract-wallet.ts index c62d4e463..12432f0be 100644 --- a/class/wallets/abstract-wallet.js +++ b/class/wallets/abstract-wallet.ts @@ -1,26 +1,64 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import b58 from 'bs58check'; -const createHash = require('create-hash'); +import createHash from 'create-hash'; + +type WalletStatics = { + type: string; + typeReadable: string; + segwitType?: string; + derivationPath?: string; +}; + +type WalletWithPassphrase = AbstractWallet & { getPassphrase: () => string }; +type UtxoMetadata = { + frozen?: boolean; + memo?: string; +}; export class AbstractWallet { static type = 'abstract'; static typeReadable = 'abstract'; - static fromJson(obj) { + static fromJson(obj: string): AbstractWallet { const obj2 = JSON.parse(obj); const temp = new this(); for (const key2 of Object.keys(obj2)) { + // @ts-ignore This kind of magic is not allowed in typescript, we should try and be more specific temp[key2] = obj2[key2]; } return temp; } + type: string; + typeReadable: string; + segwitType?: string; + _derivationPath?: string; + label: string; + secret: string; + balance: number; + unconfirmed_balance: number; // eslint-disable-line camelcase + _address: string | false; + utxo: string[]; + _lastTxFetch: number; + _lastBalanceFetch: number; + preferredBalanceUnit: BitcoinUnit; + chain: Chain; + hideBalance: boolean; + userHasSavedExport: boolean; + _hideTransactionsInWalletsList: boolean; + _utxoMetadata: Record; + use_with_hardware_wallet: boolean; // eslint-disable-line camelcase + masterFingerprint: number | false; + constructor() { - this.type = this.constructor.type; - this.typeReadable = this.constructor.typeReadable; - this.segwitType = this.constructor.segwitType; - this._derivationPath = this.constructor.derivationPath; + const Constructor = (this.constructor as unknown) as WalletStatics; + + this.type = Constructor.type; + this.typeReadable = Constructor.typeReadable; + this.segwitType = Constructor.segwitType; + this._derivationPath = Constructor.derivationPath; this.label = ''; this.secret = ''; // private key or recovery phrase this.balance = 0; @@ -35,36 +73,42 @@ export class AbstractWallet { this.userHasSavedExport = false; this._hideTransactionsInWalletsList = false; this._utxoMetadata = {}; + this.use_with_hardware_wallet = false; + this.masterFingerprint = false; } /** * @returns {number} Timestamp (millisecsec) of when last transactions were fetched from the network */ - getLastTxFetch() { + getLastTxFetch(): number { return this._lastTxFetch; } - getID() { - return createHash('sha256').update(this.getSecret()).digest().toString('hex'); + getID(): string { + const thisWithPassphrase = (this as unknown) as WalletWithPassphrase; + const passphrase = thisWithPassphrase.getPassphrase ? thisWithPassphrase.getPassphrase() : ''; + const string2hash = this.getSecret() + passphrase; + return createHash('sha256').update(string2hash).digest().toString('hex'); } - getTransactions() { + // TODO: return type is incomplete + getTransactions(): { received: number }[] { throw new Error('not implemented'); } - getUserHasSavedExport() { + getUserHasSavedExport(): boolean { return this.userHasSavedExport; } - setUserHasSavedExport(value) { + setUserHasSavedExport(value: boolean): void { this.userHasSavedExport = value; } - getHideTransactionsInWalletsList() { + getHideTransactionsInWalletsList(): boolean { return this._hideTransactionsInWalletsList; } - setHideTransactionsInWalletsList(value) { + setHideTransactionsInWalletsList(value: boolean): void { this._hideTransactionsInWalletsList = value; } @@ -72,14 +116,14 @@ export class AbstractWallet { * * @returns {string} */ - getLabel() { + getLabel(): string { if (this.label.trim().length === 0) { return 'Wallet'; } return this.label; } - getXpub() { + getXpub(): string | false { return this._address; } @@ -87,11 +131,11 @@ export class AbstractWallet { * * @returns {number} Available to spend amount, int, in sats */ - getBalance() { + getBalance(): number { return this.balance + (this.getUnconfirmedBalance() < 0 ? this.getUnconfirmedBalance() : 0); } - getPreferredBalanceUnit() { + getPreferredBalanceUnit(): BitcoinUnit { for (const value of Object.values(BitcoinUnit)) { if (value === this.preferredBalanceUnit) { return this.preferredBalanceUnit; @@ -100,47 +144,47 @@ export class AbstractWallet { return BitcoinUnit.BTC; } - allowReceive() { + allowReceive(): boolean { return true; } - allowSend() { + allowSend(): boolean { return true; } - allowRBF() { + allowRBF(): boolean { return false; } - allowHodlHodlTrading() { + allowHodlHodlTrading(): boolean { return false; } - allowPayJoin() { + allowPayJoin(): boolean { return false; } - allowCosignPsbt() { + allowCosignPsbt(): boolean { return false; } - allowSignVerifyMessage() { + allowSignVerifyMessage(): boolean { return false; } - allowMasterFingerprint() { + allowMasterFingerprint(): boolean { return false; } - allowXpub() { + allowXpub(): boolean { return false; } - weOwnAddress(address) { + weOwnAddress(address: string): boolean { throw Error('not implemented'); } - weOwnTransaction(txid) { + weOwnTransaction(txid: string): boolean { throw Error('not implemented'); } @@ -150,20 +194,20 @@ export class AbstractWallet { * * @return {number} Satoshis */ - getUnconfirmedBalance() { + getUnconfirmedBalance(): number { return this.unconfirmed_balance; } - setLabel(newLabel) { + setLabel(newLabel: string): this { this.label = newLabel; return this; } - getSecret() { + getSecret(): string { return this.secret; } - setSecret(newSecret) { + setSecret(newSecret: string): this { this.secret = newSecret.trim().replace('bitcoin:', '').replace('BITCOIN:', ''); if (this.secret.startsWith('BC1')) this.secret = this.secret.toLowerCase(); @@ -172,10 +216,12 @@ export class AbstractWallet { const re = /\[([^\]]+)\](.*)/; const m = this.secret.match(re); if (m && m.length === 3) { - let hexFingerprint = m[1].split('/')[0]; + let [hexFingerprint, ...derivationPathArray] = m[1].split('/'); + const derivationPath = `m/${derivationPathArray.join('/').replace(/h/g, "'")}`; if (hexFingerprint.length === 8) { hexFingerprint = Buffer.from(hexFingerprint, 'hex').reverse().toString('hex'); this.masterFingerprint = parseInt(hexFingerprint, 16); + this._derivationPath = derivationPath; } this.secret = m[2]; } @@ -193,7 +239,7 @@ export class AbstractWallet { parsedSecret = JSON.parse(newSecret); } if (parsedSecret && parsedSecret.keystore && parsedSecret.keystore.xpub) { - let masterFingerprint = false; + let masterFingerprint: number | false = false; if (parsedSecret.keystore.ckcc_xfp) { // It is a ColdCard Hardware Wallet masterFingerprint = Number(parsedSecret.keystore.ckcc_xfp); @@ -203,42 +249,57 @@ export class AbstractWallet { if (parsedSecret.keystore.label) { this.setLabel(parsedSecret.keystore.label); } + if (parsedSecret.keystore.derivation) { + this._derivationPath = parsedSecret.keystore.derivation; + } this.secret = parsedSecret.keystore.xpub; this.masterFingerprint = masterFingerprint; if (parsedSecret.keystore.type === 'hardware') this.use_with_hardware_wallet = true; } // It is a Cobo Vault Hardware Wallet - if (parsedSecret && parsedSecret.ExtPubKey && parsedSecret.MasterFingerprint) { + if (parsedSecret && parsedSecret.ExtPubKey && parsedSecret.MasterFingerprint && parsedSecret.AccountKeyPath) { this.secret = parsedSecret.ExtPubKey; const mfp = Buffer.from(parsedSecret.MasterFingerprint, 'hex').reverse().toString('hex'); this.masterFingerprint = parseInt(mfp, 16); - this.setLabel('Cobo Vault ' + parsedSecret.MasterFingerprint); + this._derivationPath = `m/${parsedSecret.AccountKeyPath}`; if (parsedSecret.CoboVaultFirmwareVersion) this.use_with_hardware_wallet = true; } } catch (_) {} + + if (!this._derivationPath) { + if (this.secret.startsWith('xpub')) { + this._derivationPath = "m/44'/0'/0'"; // Assume default BIP44 path for legacy wallets + } else if (this.secret.startsWith('ypub')) { + this._derivationPath = "m/49'/0'/0'"; // Assume default BIP49 path for segwit wrapped wallets + } else if (this.secret.startsWith('zpub')) { + this._derivationPath = "m/84'/0'/0'"; // Assume default BIP84 for native segwit wallets + } + } + return this; } - getLatestTransactionTime() { + getLatestTransactionTime(): number { return 0; } - getLatestTransactionTimeEpoch() { + getLatestTransactionTimeEpoch(): number { if (this.getTransactions().length === 0) { return 0; } let max = 0; for (const tx of this.getTransactions()) { - max = Math.max(new Date(tx.received) * 1, max); + max = Math.max(new Date(tx.received).getTime(), max); } return max; } /** * @deprecated + * TODO: be more precise on the type */ - createTx() { + createTx(): any { throw Error('not implemented'); } @@ -252,28 +313,38 @@ export class AbstractWallet { * @param skipSigning {boolean} Whether we should skip signing, use returned `psbt` in that case * @param masterFingerprint {number} Decimal number of wallet's master fingerprint * @returns {{outputs: Array, tx: Transaction, inputs: Array, fee: Number, psbt: Psbt}} + * + * TODO: be more specific on the return type */ - createTransaction(utxos, targets, feeRate, changeAddress, sequence, skipSigning = false, masterFingerprint) { + createTransaction( + utxos: { vout: number; value: number; txId: string; address: string }[], + targets: { value: number; address: string }, + feeRate: number, + changeAddress: string, + sequence: number, + skipSigning = false, + masterFingerprint: number, + ): { outputs: any[]; tx: any; inputs: any[]; fee: number; psbt: any } { throw Error('not implemented'); } - getAddress() { + getAddress(): string { throw Error('not implemented'); } - getAddressAsync() { + getAddressAsync(): Promise { return new Promise(resolve => resolve(this.getAddress())); } - async getChangeAddressAsync() { + async getChangeAddressAsync(): Promise { return new Promise(resolve => resolve(this.getAddress())); } - useWithHardwareWalletEnabled() { + useWithHardwareWalletEnabled(): boolean { return false; } - async wasEverUsed() { + async wasEverUsed(): Promise { throw new Error('Not implemented'); } @@ -283,7 +354,7 @@ export class AbstractWallet { * * @returns string[] Addresses */ - getAllExternalAddresses() { + getAllExternalAddresses(): string[] { return []; } @@ -293,7 +364,7 @@ export class AbstractWallet { * @param {String} zpub * @returns {String} xpub */ - static _zpubToXpub(zpub) { + static _zpubToXpub(zpub: string): string { let data = b58.decode(zpub); data = data.slice(4); data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]); @@ -306,7 +377,7 @@ export class AbstractWallet { * @param {String} ypub - wallet ypub * @returns {*} */ - static _ypubToXpub(ypub) { + static _ypubToXpub(ypub: string): string { let data = b58.decode(ypub); if (data.readUInt32BE() !== 0x049d7cb2) throw new Error('Not a valid ypub extended key!'); data = data.slice(4); @@ -315,7 +386,7 @@ export class AbstractWallet { return b58.encode(data); } - prepareForSerialization() {} + prepareForSerialization(): void {} /* * Get metadata (frozen, memo) for a specific UTXO @@ -323,7 +394,7 @@ export class AbstractWallet { * @param {String} txid - transaction id * @param {number} vout - an index number of the output in transaction */ - getUTXOMetadata(txid, vout) { + getUTXOMetadata(txid: string, vout: number): UtxoMetadata { return this._utxoMetadata[`${txid}:${vout}`] || {}; } @@ -334,7 +405,7 @@ export class AbstractWallet { * @param {number} vout - an index number of the output in transaction * @param {{memo: String, frozen: Boolean}} opts - options to attach to UTXO */ - setUTXOMetadata(txid, vout, opts) { + setUTXOMetadata(txid: string, vout: number, opts: UtxoMetadata): void { const meta = this._utxoMetadata[`${txid}:${vout}`] || {}; if ('memo' in opts) meta.memo = opts.memo; if ('frozen' in opts) meta.frozen = opts.frozen; @@ -344,7 +415,7 @@ export class AbstractWallet { /** * @returns {string} Root derivation path for wallet if any */ - getDerivationPath() { + getDerivationPath(): string { return this._derivationPath ?? ''; } } diff --git a/class/wallets/hd-aezeed-wallet.js b/class/wallets/hd-aezeed-wallet.js index 423b7b431..ac0be902c 100644 --- a/class/wallets/hd-aezeed-wallet.js +++ b/class/wallets/hd-aezeed-wallet.js @@ -21,7 +21,7 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet { setSecret(newSecret) { this.secret = newSecret.trim(); - this.secret = this.secret.replace(/[^a-zA-Z0-9:]/g, ' ').replace(/\s+/g, ' '); + this.secret = this.secret.replace(/[^a-zA-Z0-9]/g, ' ').replace(/\s+/g, ' '); return this; } @@ -51,14 +51,14 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet { return this._xpub; } - validateMnemonic(): boolean { + validateMnemonic() { throw new Error('Use validateMnemonicAsync()'); } async validateMnemonicAsync() { - const [mnemonic3, password] = this.secret.split(':'); + const passphrase = this.getPassphrase() || 'aezeed'; try { - const cipherSeed1 = await CipherSeed.fromMnemonic(mnemonic3, password || 'aezeed'); + const cipherSeed1 = await CipherSeed.fromMnemonic(this.secret, passphrase); this._entropyHex = cipherSeed1.entropy.toString('hex'); // save cache return !!cipherSeed1.entropy; } catch (_) { @@ -67,9 +67,9 @@ export class HDAezeedWallet extends AbstractHDElectrumWallet { } async mnemonicInvalidPassword() { - const [mnemonic3, password] = this.secret.split(':'); + const passphrase = this.getPassphrase() || 'aezeed'; try { - const cipherSeed1 = await CipherSeed.fromMnemonic(mnemonic3, password || 'aezeed'); + const cipherSeed1 = await CipherSeed.fromMnemonic(this.secret, passphrase); this._entropyHex = cipherSeed1.entropy.toString('hex'); // save cache } catch (error) { return error.message === 'Invalid Password'; diff --git a/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.js b/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.js index 6e10d686f..bc515e305 100644 --- a/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.js +++ b/class/wallets/hd-legacy-electrum-seed-p2pkh-wallet.js @@ -5,9 +5,6 @@ const mn = require('electrum-mnemonic'); const HDNode = require('bip32'); const PREFIX = mn.PREFIXES.standard; -const MNEMONIC_TO_SEED_OPTS = { - prefix: PREFIX, -}; /** * ElectrumSeed means that instead of BIP39 seed format it works with the format invented by Electrum wallet. Otherwise @@ -32,7 +29,9 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet { if (this._xpub) { return this._xpub; // cache hit } - const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS)); + const args = { prefix: PREFIX }; + if (this.passphrase) args.passphrase = this.passphrase; + const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args)); this._xpub = root.neutered().toBase58(); return this._xpub; } @@ -63,7 +62,9 @@ export class HDLegacyElectrumSeedP2PKHWallet extends HDLegacyP2PKHWallet { _getWIFByIndex(internal, index) { if (!this.secret) return false; - const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS)); + const args = { prefix: PREFIX }; + if (this.passphrase) args.passphrase = this.passphrase; + const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args)); const path = `m/${internal ? 1 : 0}/${index}`; const child = root.derivePath(path); diff --git a/class/wallets/hd-legacy-p2pkh-wallet.js b/class/wallets/hd-legacy-p2pkh-wallet.js index a0fcb5b22..21a60fce4 100644 --- a/class/wallets/hd-legacy-p2pkh-wallet.js +++ b/class/wallets/hd-legacy-p2pkh-wallet.js @@ -162,4 +162,9 @@ export class HDLegacyP2PKHWallet extends AbstractHDElectrumWallet { return psbt; } + + _getDerivationPathByAddress(address, BIP = 44) { + // only changing defaults for function arguments + return super._getDerivationPathByAddress(address, BIP); + } } diff --git a/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.js b/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.js index 3621ed0f1..f3d36274c 100644 --- a/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.js +++ b/class/wallets/hd-segwit-electrum-seed-p2wpkh-wallet.js @@ -1,3 +1,4 @@ +import b58 from 'bs58check'; import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; const bitcoin = require('bitcoinjs-lib'); @@ -5,9 +6,6 @@ const mn = require('electrum-mnemonic'); const HDNode = require('bip32'); const PREFIX = mn.PREFIXES.segwit; -const MNEMONIC_TO_SEED_OPTS = { - prefix: PREFIX, -}; /** * ElectrumSeed means that instead of BIP39 seed format it works with the format invented by Electrum wallet. Otherwise @@ -32,8 +30,17 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { if (this._xpub) { return this._xpub; // cache hit } - const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS)); - this._xpub = root.derivePath("m/0'").neutered().toBase58(); + const args = { prefix: PREFIX }; + if (this.passphrase) args.passphrase = this.passphrase; + const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args)); + const xpub = root.derivePath("m/0'").neutered().toBase58(); + + // bitcoinjs does not support zpub yet, so we just convert it from xpub + let data = b58.decode(xpub); + data = data.slice(4); + data = Buffer.concat([Buffer.from('04b24746', 'hex'), data]); + this._xpub = b58.encode(data); + return this._xpub; } @@ -41,7 +48,8 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { index = index * 1; // cast to int if (this.internal_addresses_cache[index]) return this.internal_addresses_cache[index]; // cache hit - const node = bitcoin.bip32.fromBase58(this.getXpub()); + const xpub = this.constructor._zpubToXpub(this.getXpub()); + const node = bitcoin.bip32.fromBase58(xpub); const address = bitcoin.payments.p2wpkh({ pubkey: node.derive(1).derive(index).publicKey, }).address; @@ -53,7 +61,8 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { index = index * 1; // cast to int if (this.external_addresses_cache[index]) return this.external_addresses_cache[index]; // cache hit - const node = bitcoin.bip32.fromBase58(this.getXpub()); + const xpub = this.constructor._zpubToXpub(this.getXpub()); + const node = bitcoin.bip32.fromBase58(xpub); const address = bitcoin.payments.p2wpkh({ pubkey: node.derive(0).derive(index).publicKey, }).address; @@ -63,7 +72,9 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { _getWIFByIndex(internal, index) { if (!this.secret) return false; - const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, MNEMONIC_TO_SEED_OPTS)); + const args = { prefix: PREFIX }; + if (this.passphrase) args.passphrase = this.passphrase; + const root = bitcoin.bip32.fromSeed(mn.mnemonicToSeedSync(this.secret, args)); const path = `m/0'/${internal ? 1 : 0}/${index}`; const child = root.derivePath(path); @@ -74,13 +85,13 @@ export class HDSegwitElectrumSeedP2WPKHWallet extends HDSegwitBech32Wallet { index = index * 1; // cast to int if (node === 0 && !this._node0) { - const xpub = this.getXpub(); + const xpub = this.constructor._zpubToXpub(this.getXpub()); const hdNode = HDNode.fromBase58(xpub); this._node0 = hdNode.derive(node); } if (node === 1 && !this._node1) { - const xpub = this.getXpub(); + const xpub = this.constructor._zpubToXpub(this.getXpub()); const hdNode = HDNode.fromBase58(xpub); this._node1 = hdNode.derive(node); } diff --git a/class/wallets/hd-segwit-p2sh-wallet.js b/class/wallets/hd-segwit-p2sh-wallet.js index eb4096948..5f9fe1014 100644 --- a/class/wallets/hd-segwit-p2sh-wallet.js +++ b/class/wallets/hd-segwit-p2sh-wallet.js @@ -148,4 +148,9 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet { }); return address; } + + _getDerivationPathByAddress(address, BIP = 49) { + // only changing defaults for function arguments + return super._getDerivationPathByAddress(address, BIP); + } } diff --git a/class/wallets/legacy-wallet.js b/class/wallets/legacy-wallet.js index cb51e8b7c..3d46c92d0 100644 --- a/class/wallets/legacy-wallet.js +++ b/class/wallets/legacy-wallet.js @@ -5,7 +5,7 @@ import { AbstractWallet } from './abstract-wallet'; import { HDSegwitBech32Wallet } from '..'; const bitcoin = require('bitcoinjs-lib'); const BlueElectrum = require('../../blue_modules/BlueElectrum'); -const coinSelectAccumulative = require('coinselect/accumulative'); +const coinSelect = require('coinselect'); const coinSelectSplit = require('coinselect/split'); /** @@ -337,7 +337,7 @@ export class LegacyWallet extends AbstractWallet { coinselect(utxos, targets, feeRate, changeAddress) { if (!changeAddress) throw new Error('No change address provided'); - let algo = coinSelectAccumulative; + let algo = coinSelect; // if targets has output without a value, we want send MAX to it if (targets.some(i => !('value' in i))) { algo = coinSelectSplit; @@ -465,7 +465,14 @@ export class LegacyWallet extends AbstractWallet { } weOwnAddress(address) { - return this.getAddress() === address || this._address === address; + if (!address) return false; + let cleanAddress = address; + + if (this.segwitType === 'p2wpkh') { + cleanAddress = address.toLowerCase(); + } + + return this.getAddress() === cleanAddress || this._address === cleanAddress; } weOwnTransaction(txid) { diff --git a/class/wallets/lightning-custodian-wallet.js b/class/wallets/lightning-custodian-wallet.js index 06187db94..79afcb8b2 100644 --- a/class/wallets/lightning-custodian-wallet.js +++ b/class/wallets/lightning-custodian-wallet.js @@ -2,12 +2,13 @@ import { LegacyWallet } from './legacy-wallet'; import Frisbee from 'frisbee'; import bolt11 from 'bolt11'; import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; +import { isTorCapable } from '../../blue_modules/environment'; const torrific = require('../../blue_modules/torrific'); export class LightningCustodianWallet extends LegacyWallet { static type = 'lightningCustodianWallet'; static typeReadable = 'Lightning'; - static defaultBaseUri = 'https://lndhub.herokuapp.com/'; + constructor(props) { super(props); this.setBaseURI(); // no args to init with default value @@ -30,11 +31,7 @@ export class LightningCustodianWallet extends LegacyWallet { * @param URI */ setBaseURI(URI) { - if (URI) { - this.baseURI = URI; - } else { - this.baseURI = LightningCustodianWallet.defaultBaseUri; - } + this.baseURI = URI; } getBaseURI() { @@ -54,9 +51,6 @@ export class LightningCustodianWallet extends LegacyWallet { } getSecret() { - if (this.baseURI === LightningCustodianWallet.defaultBaseUri) { - return this.secret; - } return this.secret + '@' + this.baseURI; } @@ -79,7 +73,7 @@ export class LightningCustodianWallet extends LegacyWallet { baseURI: this.baseURI, }); - if (this.baseURI.indexOf('.onion') !== -1) { + if (isTorCapable && this.baseURI && this.baseURI?.indexOf('.onion') !== -1) { this._api = new torrific.Torsbee({ baseURI: this.baseURI, }); @@ -377,7 +371,7 @@ export class LightningCustodianWallet extends LegacyWallet { txs = txs.concat(this.pending_transactions_raw.slice(), this.transactions_raw.slice().reverse(), this.user_invoices_raw.slice()); // slice so array is cloned // transforming to how wallets/list screen expects it for (const tx of txs) { - tx.fromWallet = this.getSecret(); + tx.walletID = this.getID(); if (tx.amount) { // pending tx tx.amt = tx.amount * -100000000; diff --git a/class/wallets/multisig-hd-wallet.js b/class/wallets/multisig-hd-wallet.js index dbaffc6cc..1baae76b9 100644 --- a/class/wallets/multisig-hd-wallet.js +++ b/class/wallets/multisig-hd-wallet.js @@ -1,7 +1,7 @@ import { AbstractHDElectrumWallet } from './abstract-hd-electrum-wallet'; -import bip39 from 'bip39'; +import * as bip39 from 'bip39'; import b58 from 'bs58check'; -import { decodeUR } from 'bc-ur'; +import { decodeUR } from '../../blue_modules/ur'; const BlueElectrum = require('../../blue_modules/BlueElectrum'); const HDNode = require('bip32'); const bitcoin = require('bitcoinjs-lib'); @@ -298,7 +298,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { if (mnemonic.startsWith(ELECTRUM_SEED_PREFIX)) { seed = MultisigHDWallet.convertElectrumMnemonicToSeed(mnemonic); } else { - seed = bip39.mnemonicToSeed(mnemonic); + seed = bip39.mnemonicToSeedSync(mnemonic); } const root = bitcoin.bip32.fromSeed(seed); @@ -593,8 +593,8 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { case MultisigHDWallet.FORMAT_P2SH_P2WSH_ALT: this.setWrappedSegwit(); break; - default: case MultisigHDWallet.FORMAT_P2WSH: + default: this.setNativeSegwit(); break; } @@ -835,7 +835,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { // dont sign more than we need, otherwise there will be "Too many signatures" error continue; } - let seed = bip39.mnemonicToSeed(cosigner); + let seed = bip39.mnemonicToSeedSync(cosigner); if (cosigner.startsWith(ELECTRUM_SEED_PREFIX)) { seed = MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner); } @@ -979,7 +979,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { for (const [cosignerIndex, cosigner] of this._cosigners.entries()) { if (!MultisigHDWallet.isXpubString(cosigner)) { // ok this is a mnemonic, lets try to sign - const seed = bip39.mnemonicToSeed(cosigner); + const seed = bip39.mnemonicToSeedSync(cosigner); const hdRoot = bitcoin.bip32.fromSeed(seed); try { psbt.signInputHD(cc, hdRoot); @@ -995,7 +995,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { // correctly points to `/internal/index`, so we extract pubkey from our stored mnemonics+path and // match it to the one provided in PSBT's input, and if we have a match - we are in luck! we can sign // with this private key. - const seed = bip39.mnemonicToSeed(cosigner); + const seed = bip39.mnemonicToSeedSync(cosigner); const root = HDNode.fromSeed(seed); const splt = derivation.path.split('/'); const internal = +splt[splt.length - 2]; diff --git a/class/wallets/placeholder-wallet.js b/class/wallets/placeholder-wallet.ts similarity index 73% rename from class/wallets/placeholder-wallet.js rename to class/wallets/placeholder-wallet.ts index 1d756e2fc..d14a6ba79 100644 --- a/class/wallets/placeholder-wallet.js +++ b/class/wallets/placeholder-wallet.ts @@ -4,34 +4,38 @@ export class PlaceholderWallet extends AbstractWallet { static type = 'placeholder'; static typeReadable = 'Placeholder'; + _isFailure: boolean; + constructor() { super(); this._isFailure = false; } - setSecret(newSecret) { + setSecret(newSecret: string): this { // so TRY AGAIN when something goes wrong during import has more consistent prefilled text this.secret = newSecret; + + return this; } - allowSend() { + allowSend(): boolean { return false; } - getLabel() { + getLabel(): string { // no longer used in wallets carousel return this.getIsFailure() ? 'Wallet Import' : 'Importing Wallet...'; } - allowReceive() { + allowReceive(): boolean { return false; } - getIsFailure() { + getIsFailure(): boolean { return this._isFailure; } - setIsFailure(value) { + setIsFailure(value: boolean): void { this._isFailure = value; } } diff --git a/class/wallets/slip39-wallets.js b/class/wallets/slip39-wallets.js index ed7a891c0..044ab4dbd 100644 --- a/class/wallets/slip39-wallets.js +++ b/class/wallets/slip39-wallets.js @@ -8,7 +8,7 @@ import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet'; // collection of SLIP39 functions const SLIP39Mixin = { _getSeed() { - const master = slip39.recoverSecret(this.secret); + const master = slip39.recoverSecret(this.secret, this.passphrase); return Buffer.from(master); }, @@ -39,7 +39,7 @@ const SLIP39Mixin = { }, getID() { - const string2hash = this.secret.sort().join(','); + const string2hash = this.secret.sort().join(',') + (this.getPassphrase() || ''); return createHash('sha256').update(string2hash).digest().toString('hex'); }, }; diff --git a/class/wallets/watch-only-wallet.js b/class/wallets/watch-only-wallet.js index 21925c68a..26f7c5971 100644 --- a/class/wallets/watch-only-wallet.js +++ b/class/wallets/watch-only-wallet.js @@ -221,6 +221,8 @@ export class WatchOnlyWallet extends LegacyWallet { throw new Error('Not initialized'); } + if (address && address.startsWith('BC1')) address = address.toLowerCase(); + return this.getAddress() === address; } diff --git a/components/AddressInput.js b/components/AddressInput.js index 005743a08..b64fe2bfc 100644 --- a/components/AddressInput.js +++ b/components/AddressInput.js @@ -66,10 +66,15 @@ const AddressInput = ({ }); } }} + accessibilityRole="button" style={[styles.scan, stylesHook.scan]} + accessibilityLabel={loc.send.details_scan} + accessibilityHint={loc.send.details_scan_hint} > - - {loc.send.details_scan} + + + {loc.send.details_scan} + ); diff --git a/components/AmountInput.js b/components/AmountInput.js index f49bab7db..3ecc1735a 100644 --- a/components/AmountInput.js +++ b/components/AmountInput.js @@ -240,7 +240,12 @@ class AmountInput extends Component { {!disabled && amount !== BitcoinUnit.MAX && ( - + )} diff --git a/components/BottomModal.js b/components/BottomModal.js index f73bcd165..54c0e6385 100644 --- a/components/BottomModal.js +++ b/components/BottomModal.js @@ -1,21 +1,33 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { StyleSheet, useWindowDimensions } from 'react-native'; +import { StyleSheet, Platform, useWindowDimensions, View } from 'react-native'; import Modal from 'react-native-modal'; +import { BlueButton, BlueSpacing40 } from '../BlueComponents'; +import loc from '../loc'; +import { useTheme } from '@react-navigation/native'; const styles = StyleSheet.create({ root: { justifyContent: 'flex-end', margin: 0, }, + hasDoneButton: { + padding: 16, + paddingBottom: 24, + }, }); -const BottomModal = ({ onBackButtonPress, onBackdropPress, onClose, windowHeight, windowWidth, ...props }) => { +const BottomModal = ({ onBackButtonPress, onBackdropPress, onClose, windowHeight, windowWidth, doneButton, ...props }) => { const valueWindowHeight = useWindowDimensions().height; const valueWindowWidth = useWindowDimensions().width; const handleBackButtonPress = onBackButtonPress ?? onClose; const handleBackdropPress = onBackdropPress ?? onClose; - + const { colors } = useTheme(); + const stylesHook = StyleSheet.create({ + hasDoneButton: { + backgroundColor: colors.elevated, + }, + }); return ( + useNativeDriverForBackdrop={Platform.OS === 'android'} + > + {props.children} + {doneButton && ( + + + + + )} + ); }; @@ -36,6 +56,7 @@ BottomModal.propTypes = { onBackButtonPress: PropTypes.func, onBackdropPress: PropTypes.func, onClose: PropTypes.func, + doneButton: PropTypes.bool, windowHeight: PropTypes.number, windowWidth: PropTypes.number, }; diff --git a/components/CoinsSelected.js b/components/CoinsSelected.js index 58c383ff9..2c7c12550 100644 --- a/components/CoinsSelected.js +++ b/components/CoinsSelected.js @@ -35,11 +35,11 @@ const styles = StyleSheet.create({ }); const CoinsSelected = ({ number, onContainerPress, onClose }) => ( - + {loc.formatString(loc.cc.coins_selected, { number })} - + diff --git a/components/DynamicQRCode.js b/components/DynamicQRCode.js index 10dd7fdad..3aa3f1cf0 100644 --- a/components/DynamicQRCode.js +++ b/components/DynamicQRCode.js @@ -2,7 +2,7 @@ import React, { Component } from 'react'; import { Text } from 'react-native-elements'; import { Dimensions, LayoutAnimation, StyleSheet, TouchableOpacity, View } from 'react-native'; -import { encodeUR } from 'bc-ur/dist'; +import { encodeUR } from '../blue_modules/ur'; import QRCode from 'react-native-qrcode-svg'; import { BlueCurrentTheme } from '../components/themes'; import { BlueSpacing20 } from '../BlueComponents'; @@ -105,6 +105,7 @@ export class DynamicQRCode extends Component { return ( { @@ -136,18 +137,21 @@ export class DynamicQRCode extends Component { {loc.send.dynamic_prev} {this.state.intervalHandler ? loc.send.dynamic_stop : loc.send.dynamic_start} diff --git a/components/FloatButtons.js b/components/FloatButtons.js index 8aaf0d03a..433e55c38 100644 --- a/components/FloatButtons.js +++ b/components/FloatButtons.js @@ -119,7 +119,7 @@ export const FButton = ({ text, icon, width, first, last, ...props }) => { } return ( - + {icon} {text} diff --git a/components/MultipleStepsListItem.js b/components/MultipleStepsListItem.js index 6a091d6a3..bcb7b25d6 100644 --- a/components/MultipleStepsListItem.js +++ b/components/MultipleStepsListItem.js @@ -139,6 +139,7 @@ const MultipleStepsListItem = props => { {props.button.buttonType === undefined || (props.button.buttonType === MultipleStepsListItemButtohType.full && ( { {props.button.leftText} { )} {!showActivityIndicator && props.rightButton && checked && ( - + {props.rightButton.text} diff --git a/components/SquareButton.js b/components/SquareButton.js index 3003be53b..4bbaf0395 100644 --- a/components/SquareButton.js +++ b/components/SquareButton.js @@ -29,6 +29,7 @@ export const SquareButton = forwardRef((props, ref) => { }} {...props} ref={ref} + accessibilityRole="button" > {props.icon && } diff --git a/components/SquareEnumeratedWords.js b/components/SquareEnumeratedWords.js index 986076b78..680ab840e 100644 --- a/components/SquareEnumeratedWords.js +++ b/components/SquareEnumeratedWords.js @@ -35,7 +35,11 @@ const SquareEnumeratedWords = props => { ); } else { component.push( - + {secret} diff --git a/components/WalletsCarousel.js b/components/WalletsCarousel.js index 1299d3435..4098a6a25 100644 --- a/components/WalletsCarousel.js +++ b/components/WalletsCarousel.js @@ -1,4 +1,4 @@ -import React, { useRef, useCallback, useState, useImperativeHandle, forwardRef, useContext } from 'react'; +import React, { useRef, useCallback, useImperativeHandle, forwardRef, useContext } from 'react'; import PropTypes from 'prop-types'; import { ActivityIndicator, @@ -12,25 +12,21 @@ import { TouchableWithoutFeedback, useWindowDimensions, View, + Dimensions, + FlatList, } from 'react-native'; import { useTheme } from '@react-navigation/native'; import LinearGradient from 'react-native-linear-gradient'; -import Carousel from 'react-native-snap-carousel'; - import loc, { formatBalance, transactionTimeToReadable } from '../loc'; import { LightningCustodianWallet, MultisigHDWallet, PlaceholderWallet } from '../class'; import WalletGradient from '../class/wallet-gradient'; import { BluePrivateBalance } from '../BlueComponents'; import { BlueStorageContext } from '../blue_modules/storage-context'; +import { isHandset, isTablet, isDesktop } from '../blue_modules/environment'; const nStyles = StyleSheet.create({ - root: { - marginVertical: 17, - paddingRight: 10, - }, + root: {}, container: { - paddingHorizontal: 24, - paddingVertical: 16, borderRadius: 10, minHeight: Platform.OS === 'ios' ? 164 : 181, justifyContent: 'center', @@ -56,11 +52,72 @@ const nStyles = StyleSheet.create({ }, }); +const PlaceholderWalletCarouselItem = props => { + const { colors } = useTheme(); + + const { isImportingWallet } = useContext(BlueStorageContext); + + return ( + { + if (isImportingWallet && isImportingWallet.getIsFailure()) { + props.onPressedIn(); + } else { + props.onPressedOut(); + } + }} + onPressOut={isImportingWallet && isImportingWallet.getIsFailure() ? props.onPressedOut : null} + onPress={isImportingWallet && isImportingWallet.getIsFailure() ? props.onPress : null} + > + + + + + {isImportingWallet.getIsFailure() ? loc.wallets.import_placeholder_fail : loc.wallets.import_placeholder_inprogress} + + {isImportingWallet.getIsFailure() ? ( + + {loc.wallets.list_import_error} + + ) : ( + + )} + + + ); +}; + +PlaceholderWalletCarouselItem.propTypes = { onPress: PropTypes.func, onPressedOut: PropTypes.func, onPressedIn: PropTypes.func }; + const NewWalletPanel = ({ onPress }) => { const { colors } = useTheme(); + const { width } = useWindowDimensions(); + const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; + const isLargeScreen = Platform.OS === 'android' ? isTablet() : width >= Dimensions.get('screen').width / 2 && (isTablet() || isDesktop); + const nStylesHooks = StyleSheet.create({ + container: isLargeScreen + ? { + paddingHorizontal: 24, + marginVertical: 16, + } + : { paddingVertical: 16, paddingHorizontal: 24 }, + }); + return ( - - + + {loc.wallets.list_create_a_wallet} {loc.wallets.list_create_a_wallet_text} @@ -76,10 +133,8 @@ NewWalletPanel.propTypes = { }; const iStyles = StyleSheet.create({ - root: { - paddingRight: 10, - marginVertical: 17, - }, + root: { paddingRight: 20 }, + rootLargeDevice: { marginVertical: 20 }, grad: { padding: 15, borderRadius: 12, @@ -131,8 +186,10 @@ const iStyles = StyleSheet.create({ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedWallet }) => { const scaleValue = new Animated.Value(1.0); const { colors } = useTheme(); - const { walletTransactionUpdateStatus } = useContext(BlueStorageContext); - + const { walletTransactionUpdateStatus, isImportingWallet } = useContext(BlueStorageContext); + const { width } = useWindowDimensions(); + const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; + const isLargeScreen = Platform.OS === 'android' ? isTablet() : width >= Dimensions.get('screen').width / 2 && (isTablet() || isDesktop); const onPressedIn = () => { const props = { duration: 50 }; props.useNativeDriver = true; @@ -148,7 +205,16 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW }; if (!item) - return ( + return isImportingWallet ? ( + + + + ) : ( { onPressedOut(); @@ -157,47 +223,6 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW /> ); - if (item.type === PlaceholderWallet.type) { - return ( - - { - if (item.getIsFailure()) { - onPressedOut(); - onPress(index); - onPressedOut(); - } - }} - > - - - - - {item.getIsFailure() ? loc.wallets.import_placeholder_fail : loc.wallets.import_placeholder_inprogress} - - {item.getIsFailure() ? ( - - {loc.wallets.list_import_error} - - ) : ( - - )} - - - - ); - } - const opacity = isSelectedWallet === false ? 0.5 : 1.0; let image; switch (item.type) { @@ -224,7 +249,10 @@ const WalletCarouselItem = ({ item, index, onPress, handleLongPress, isSelectedW return ( { - const carouselRef = useRef(); - const [loading, setLoading] = useState(true); - const { preferredFiatCurrency, language } = useContext(BlueStorageContext); + const { preferredFiatCurrency, language, isImportingWallet } = useContext(BlueStorageContext); const renderItem = useCallback( ({ item, index }) => ( { /> ), // eslint-disable-next-line react-hooks/exhaustive-deps - [props.vertical, props.selectedWallet, props.handleLongPress, props.onPress, preferredFiatCurrency, language], + [props.horizontal, props.selectedWallet, props.handleLongPress, props.onPress, preferredFiatCurrency, language, isImportingWallet], ); + const flatListRef = useRef(); + const ListHeaderComponent = () => ; useImperativeHandle(ref, () => ({ - snapToItem: item => carouselRef?.current?.snapToItem(item), + scrollToItem: ({ item }) => { + setTimeout(() => { + flatListRef?.current?.scrollToItem({ item, viewOffset: 16 }); + }, 300); + }, + scrollToIndex: ({ index }) => { + setTimeout(() => { + flatListRef?.current?.scrollToIndex({ index, viewOffset: 16 }); + }, 300); + }, })); + const onScrollToIndexFailed = error => { + console.log('onScrollToIndexFailed'); + console.log(error); + flatListRef.current.scrollToOffset({ offset: error.averageItemLength * error.index, animated: true }); + setTimeout(() => { + if (props.data.length !== 0 && flatListRef.current !== null) { + flatListRef.current.scrollToIndex({ index: error.index, animated: true }); + } + }, 100); + }; + const { width } = useWindowDimensions(); - const sliderWidth = width * 1; - const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; const sliderHeight = 190; - - const onLayout = () => setLoading(false); - + const itemWidth = width * 0.82 > 375 ? 375 : width * 0.82; return ( - <> - {loading && ( - - - - )} - - + index.toString()} + showsVerticalScrollIndicator={false} + pagingEnabled + disableIntervalMomentum={isHandset} + snapToInterval={itemWidth} // Adjust to your content width + decelerationRate="fast" + contentContainerStyle={props.horizontal ? cStyles.content : cStyles.contentLargeScreen} + directionalLockEnabled + showsHorizontalScrollIndicator={false} + initialNumToRender={10} + ListHeaderComponent={ListHeaderComponent} + style={props.horizontal ? { height: sliderHeight + 9 } : {}} + onScrollToIndexFailed={onScrollToIndexFailed} + {...props} + /> ); }); WalletsCarousel.propTypes = { - vertical: PropTypes.bool, + horizontal: PropTypes.bool, selectedWallet: PropTypes.string, onPress: PropTypes.func.isRequired, handleLongPress: PropTypes.func.isRequired, + data: PropTypes.array, }; export default WalletsCarousel; diff --git a/components/addresses/AddressItem.js b/components/addresses/AddressItem.js index ab7521e98..300b3b4f4 100644 --- a/components/addresses/AddressItem.js +++ b/components/addresses/AddressItem.js @@ -1,6 +1,6 @@ import React, { useRef } from 'react'; import { StyleSheet, Text, View } from 'react-native'; -import { useTheme } from '@react-navigation/native'; +import { useNavigation, useTheme } from '@react-navigation/native'; import { ListItem } from 'react-native-elements'; import PropTypes from 'prop-types'; import { AddressTypeBadge } from './AddressTypeBadge'; @@ -9,11 +9,13 @@ import TooltipMenu from '../TooltipMenu'; import Clipboard from '@react-native-clipboard/clipboard'; import Share from 'react-native-share'; -const AddressItem = ({ item, balanceUnit, onPress }) => { +const AddressItem = ({ item, balanceUnit, walletID, allowSignVerifyMessage }) => { const { colors } = useTheme(); const tooltip = useRef(); const listItem = useRef(); + const hasTransactions = item.transactions > 0; + const stylesHook = StyleSheet.create({ container: { borderBottomColor: colors.lightBorder, @@ -28,8 +30,33 @@ const AddressItem = ({ item, balanceUnit, onPress }) => { balance: { color: colors.alternativeTextColor, }, + address: { + color: hasTransactions ? colors.darkGray : colors.buttonTextColor, + }, }); + const { navigate } = useNavigation(); + + const navigateToReceive = () => { + navigate('ReceiveDetailsRoot', { + screen: 'ReceiveDetails', + params: { + walletID, + address: item.address, + }, + }); + }; + + const navigateToSignVerify = () => { + navigate('SignVerifyRoot', { + screen: 'SignVerify', + params: { + walletID, + address: item.address, + }, + }); + }; + const showToolTipMenu = () => { tooltip.current.showMenu(); }; @@ -44,30 +71,40 @@ const AddressItem = ({ item, balanceUnit, onPress }) => { Share.open({ message: item.address }).catch(error => console.log(error)); }; + const getAvailableActions = () => { + const actions = [ + { + id: 'copyToClipboard', + text: loc.transactions.details_copy, + onPress: handleCopyPress, + }, + { + id: 'share', + text: loc.receive.details_share, + onPress: handleSharePress, + }, + ]; + + if (allowSignVerifyMessage) { + actions.push({ + id: 'signVerify', + text: loc.addresses.sign_title, + onPress: navigateToSignVerify, + }); + } + + return actions; + }; + const render = () => { return ( - + @@ -76,9 +113,16 @@ const AddressItem = ({ item, balanceUnit, onPress }) => { {item.index + 1}{' '} {item.address} - {balance} + + {balance} + - + + + + {loc.addresses.transactions}: {item.transactions} + + ); @@ -89,7 +133,7 @@ const AddressItem = ({ item, balanceUnit, onPress }) => { const styles = StyleSheet.create({ address: { - fontWeight: '600', + fontWeight: 'bold', marginHorizontal: 40, }, index: { @@ -99,6 +143,12 @@ const styles = StyleSheet.create({ marginTop: 8, marginLeft: 14, }, + subtitle: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + width: '100%', + }, }); AddressItem.propTypes = { diff --git a/components/addresses/AddressTypeBadge.js b/components/addresses/AddressTypeBadge.js index bcbcda666..b59ade2da 100644 --- a/components/addresses/AddressTypeBadge.js +++ b/components/addresses/AddressTypeBadge.js @@ -2,34 +2,53 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useTheme } from '@react-navigation/native'; import { StyleSheet, View, Text } from 'react-native'; -import loc from '../../loc'; +import loc, { formatStringAddTwoWhiteSpaces } from '../../loc'; const styles = StyleSheet.create({ container: { paddingVertical: 4, paddingHorizontal: 10, borderRadius: 20, + alignSelf: 'flex-end', }, badgeText: { fontSize: 12, + textAlign: 'center', }, }); -const AddressTypeBadge = ({ isInternal }) => { +const AddressTypeBadge = ({ isInternal, hasTransactions }) => { const { colors } = useTheme(); const stylesHook = StyleSheet.create({ changeBadge: { backgroundColor: colors.changeBackground }, receiveBadge: { backgroundColor: colors.receiveBackground }, + usedBadge: { backgroundColor: colors.buttonDisabledBackgroundColor }, changeText: { color: colors.changeText }, receiveText: { color: colors.receiveText }, + usedText: { color: colors.alternativeTextColor }, }); - const badgeLabel = isInternal ? loc.addresses.type_change : loc.addresses.type_receive; + // eslint-disable-next-line prettier/prettier + const badgeLabel = hasTransactions + ? loc.addresses.type_used + : isInternal + ? formatStringAddTwoWhiteSpaces(loc.addresses.type_change) + : formatStringAddTwoWhiteSpaces(loc.addresses.type_receive); - const badgeStyle = isInternal ? stylesHook.changeBadge : stylesHook.receiveBadge; + // eslint-disable-next-line prettier/prettier + const badgeStyle = hasTransactions + ? stylesHook.usedBadge + : isInternal + ? stylesHook.changeBadge + : stylesHook.receiveBadge; - const textStyle = isInternal ? stylesHook.changeText : stylesHook.receiveText; + // eslint-disable-next-line prettier/prettier + const textStyle = hasTransactions + ? stylesHook.usedText + : isInternal + ? stylesHook.changeText + : stylesHook.receiveText; return ( @@ -40,6 +59,7 @@ const AddressTypeBadge = ({ isInternal }) => { AddressTypeBadge.propTypes = { isInternal: PropTypes.bool, + hasTransactions: PropTypes.bool, }; export { AddressTypeBadge }; diff --git a/components/addresses/AddressTypeTabs.js b/components/addresses/AddressTypeTabs.js new file mode 100644 index 000000000..a7a2da63b --- /dev/null +++ b/components/addresses/AddressTypeTabs.js @@ -0,0 +1,96 @@ +import { useTheme } from '@react-navigation/native'; +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import loc from '../../loc'; + +export const TABS = { + EXTERNAL: 'receive', + INTERNAL: 'change', +}; + +const AddressTypeTabs = ({ currentTab, setCurrentTab }) => { + const { colors } = useTheme(); + + const stylesHook = StyleSheet.create({ + activeTab: { + backgroundColor: colors.modal, + }, + activeText: { + fontWeight: 'bold', + color: colors.foregroundColor, + }, + inactiveTab: { + fontWeight: 'normal', + color: colors.foregroundColor, + }, + backTabs: { + backgroundColor: colors.buttonDisabledBackgroundColor, + }, + }); + + const tabs = Object.entries(TABS).map(([key, value]) => { + return { + key, + value, + name: loc.addresses[`type_${value}`], + }; + }); + + const changeToTab = tabKey => { + if (tabKey in TABS) { + setCurrentTab(TABS[tabKey]); + } + }; + + const render = () => { + const tabsButtons = tabs.map(tab => { + const isActive = tab.value === currentTab; + + const tabStyle = isActive ? stylesHook.activeTab : stylesHook.inactiveTab; + const textStyle = isActive ? stylesHook.activeText : stylesHook.inactiveTab; + + return ( + changeToTab(tab.key)} style={[styles.tab, tabStyle]}> + changeToTab(tab.key)} style={textStyle}> + {tab.name} + + + ); + }); + + return ( + + + {tabsButtons} + + + ); + }; + + return render(); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'row', + justifyContent: 'center', + }, + backTabs: { + padding: 4, + marginVertical: 8, + borderRadius: 8, + }, + tabs: { + flex: 1, + flexDirection: 'row', + justifyContent: 'center', + }, + tab: { + borderRadius: 6, + paddingVertical: 8, + paddingHorizontal: 16, + }, +}); + +export { AddressTypeTabs }; diff --git a/components/navigationStyle.js b/components/navigationStyle.tsx similarity index 58% rename from components/navigationStyle.js rename to components/navigationStyle.tsx index 4f3d66aa0..5042cbb75 100644 --- a/components/navigationStyle.js +++ b/components/navigationStyle.tsx @@ -1,5 +1,7 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import React from 'react'; import { Image, Keyboard, TouchableOpacity, StyleSheet } from 'react-native'; +import { Theme } from './themes'; const styles = StyleSheet.create({ button: { @@ -10,7 +12,39 @@ const styles = StyleSheet.create({ }, }); -const navigationStyle = ({ closeButton = false, closeButtonFunc, ...opts }, formatter) => { +type NavigationOptions = { + headerStyle?: { + borderBottomWidth: number; + elevation: number; + shadowOpacity?: number; + shadowOffset: { height?: number; width?: number }; + }; + headerTitleStyle?: { + fontWeight: string; + color: string; + }; + headerLeft?: (() => React.ReactElement) | null; + headerRight?: (() => React.ReactElement) | null; + headerBackTitleVisible?: false; + headerTintColor?: string; + title?: string; +}; + +type OptionsFormatter = (options: NavigationOptions, deps: { theme: Theme; navigation: any; route: any }) => NavigationOptions; + +export type NavigationOptionsGetter = (theme: Theme) => (deps: { navigation: any; route: any }) => NavigationOptions; + +const navigationStyle = ( + { + closeButton = false, + closeButtonFunc, + ...opts + }: NavigationOptions & { + closeButton?: boolean; + closeButtonFunc?: (deps: { navigation: any; route: any }) => React.ReactElement; + }, + formatter: OptionsFormatter, +): NavigationOptionsGetter => { return theme => ({ navigation, route }) => { let headerRight = null; if (closeButton) { @@ -21,13 +55,13 @@ const navigationStyle = ({ closeButton = false, closeButtonFunc, ...opts }, form navigation.goBack(null); }; headerRight = () => ( - + ); } - let options = { + let options: NavigationOptions = { headerStyle: { borderBottomWidth: 0, elevation: 0, @@ -38,7 +72,7 @@ const navigationStyle = ({ closeButton = false, closeButtonFunc, ...opts }, form fontWeight: '600', color: theme.colors.foregroundColor, }, - headerRight, + headerRight: headerRight, headerBackTitleVisible: false, headerTintColor: theme.colors.foregroundColor, ...opts, @@ -54,9 +88,9 @@ const navigationStyle = ({ closeButton = false, closeButtonFunc, ...opts }, form export default navigationStyle; -export const navigationStyleTx = (opts, formatter) => { +export const navigationStyleTx = (opts: NavigationOptions, formatter: OptionsFormatter): NavigationOptionsGetter => { return theme => ({ navigation, route }) => { - let options = { + let options: NavigationOptions = { headerStyle: { borderBottomWidth: 0, elevation: 0, @@ -71,6 +105,7 @@ export const navigationStyleTx = (opts, formatter) => { headerTintColor: theme.colors.foregroundColor, headerLeft: () => ( { Keyboard.dismiss(); diff --git a/components/themes.js b/components/themes.ts similarity index 89% rename from components/themes.js rename to components/themes.ts index 05e3f496c..e21210fac 100644 --- a/components/themes.js +++ b/components/themes.ts @@ -1,4 +1,4 @@ -import { DefaultTheme, DarkTheme } from '@react-navigation/native'; +import { DefaultTheme, DarkTheme, useTheme as useThemeBase } from '@react-navigation/native'; import { Appearance } from 'react-native'; export const BlueDefaultTheme = { @@ -67,7 +67,9 @@ export const BlueDefaultTheme = { }, }; -export const BlueDarkTheme = { +export type Theme = typeof BlueDefaultTheme; + +export const BlueDarkTheme: Theme = { ...DarkTheme, closeImage: require('../img/close-white.png'), scanImage: require('../img/scan-white.png'), @@ -119,8 +121,15 @@ export const BlueDarkTheme = { }, }; +// Casting theme value to get autocompletion +export const useTheme = (): Theme => useThemeBase() as Theme; + export class BlueCurrentTheme { - static updateColorScheme() { + static colors: Theme['colors']; + static closeImage: Theme['closeImage']; + static scanImage: Theme['scanImage']; + + static updateColorScheme(): void { const isColorSchemeDark = Appearance.getColorScheme() === 'dark'; BlueCurrentTheme.colors = isColorSchemeDark ? BlueDarkTheme.colors : BlueDefaultTheme.colors; BlueCurrentTheme.closeImage = isColorSchemeDark ? BlueDarkTheme.closeImage : BlueDefaultTheme.closeImage; diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 000000000..b0167fc68 --- /dev/null +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,44 @@ +A Bitcoin wallet that allows you to store, send Bitcoin, receive Bitcoin and buy Bitcoin with focus on security and simplicity. +On BlueWallet, a bitcoin wallet you own you private keys. A Bitcoin wallet made by Bitcoin users for the community. +You can instantly transact with anyone in the world and transform the financial system right from your pocket. +Create for free unlimited number of bitcoin wallets or import your existing one on your Android device. It's simple and fast. +_____ + +Here's what you get: + +1 - Security by design + +• Open Source +MIT licensed, you can build it and run it on your own! Made with ReactNative + +• Plausible deniability +Password which decrypts fake bitcoin wallets if you are forced to disclose your access + +• Full encryption +On top of the iOS multi-layer encryption, we encrypt everything with added passwords + +• SegWit & HD wallets +SegWit supported and HD wallets enable + +2 - Focused on your experience + +• Be in control +Private keys never leave your device.
You control your private keys + +• Flexible fees +Starting from 1 Satoshi. Defined by you, the user + +• Replace-By-Fee +(RBF) Speed-up your transactions by increasing the fee (BIP125) + +• Watch-only wallets +Watch-only wallets allow you to keep an eye on your cold storage without touching the hardware. + +• Lightning Network +Lightning wallet with zero-configuration. Unfairly cheap and fast transactions with the best Bitcoin user experience. + +• Buy Bitcoin +Enter in the open financial revolution with the ability to buy Bitcoin directly in your wallet. + +• Local Trader +A p2p Bitcoin Trading platform, that allows you to buy and sell bitcoin directly to other users without 3rd parties. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png new file mode 100644 index 000000000..ec541528a Binary files /dev/null and b/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/01-Home.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/01-Home.png new file mode 100644 index 000000000..5d8514a42 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/01-Home.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/02-Wallet.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/02-Wallet.png new file mode 100644 index 000000000..fde57d6fe Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/02-Wallet.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/03-Send.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/03-Send.png new file mode 100644 index 000000000..899369e9c Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/03-Send.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/04-Receive.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/04-Receive.png new file mode 100644 index 000000000..8f9812169 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/04-Receive.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/05-Create.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/05-Create.png new file mode 100644 index 000000000..e5002a77d Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/05-Create.png differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 000000000..1f22049f9 --- /dev/null +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +Thin Bitcoin Wallet Built with React Native and Electrum \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 000000000..b3071c5ff --- /dev/null +++ b/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +BlueWallet Bitcoin Wallet \ No newline at end of file diff --git a/index.js b/index.js index 0be6e19be..234e6826f 100644 --- a/index.js +++ b/index.js @@ -3,8 +3,9 @@ import './shim.js'; import { AppRegistry } from 'react-native'; import App from './App'; import { BlueStorageProvider } from './blue_modules/storage-context'; +import { enableScreens } from 'react-native-screens'; const A = require('./blue_modules/analytics'); - +enableScreens(false); if (!Error.captureStackTrace) { // captureStackTrace is only available when debugging Error.captureStackTrace = () => {}; diff --git a/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index f6e42f7a3..a55b1dc7c 100644 --- a/ios/BlueWallet.xcodeproj/project.pbxproj +++ b/ios/BlueWallet.xcodeproj/project.pbxproj @@ -18,87 +18,43 @@ 3271B0BB236E329400DA766F /* TodayAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3271B0BA236E329400DA766F /* TodayAPI.swift */; }; 32B5A32A2334450100F8D608 /* Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5A3292334450100F8D608 /* Bridge.swift */; }; 32F0A29A2311DBB20095C559 /* ComplicationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F0A2992311DBB20095C559 /* ComplicationController.swift */; }; - 590C62D2ED8BF487C33945B0 /* libPods-WalletInformationAndMarketWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 98455D960744E4E5DD50BA87 /* libPods-WalletInformationAndMarketWidgetExtension.a */; platformFilter = ios; }; 6D2A6464258BA92D0092292B /* Stickers.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D2A6463258BA92D0092292B /* Stickers.xcassets */; }; 6D2A6468258BA92D0092292B /* Stickers.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6D2A6461258BA92C0092292B /* Stickers.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 6D2AA7FA2568B8750090B089 /* FiatUnits.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D2AA7F92568B8750090B089 /* FiatUnits.plist */; }; - 6D2AA7FB2568B8750090B089 /* FiatUnits.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D2AA7F92568B8750090B089 /* FiatUnits.plist */; }; - 6D2AA7FC2568B8750090B089 /* FiatUnits.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D2AA7F92568B8750090B089 /* FiatUnits.plist */; }; - 6D2AA7FD2568B8750090B089 /* FiatUnits.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D2AA7F92568B8750090B089 /* FiatUnits.plist */; }; - 6D2AA8082568B8F40090B089 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; }; - 6D2AA8092568B8F40090B089 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; }; - 6D2AA80A2568B8F40090B089 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; }; - 6D2AA80B2568B8F40090B089 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; }; 6D32C5C62596CE3A008C077C /* EventEmitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 6D32C5C52596CE3A008C077C /* EventEmitter.m */; }; 6D4AF15925D21172009DD853 /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; }; 6D4AF16325D21185009DD853 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; }; 6D4AF16D25D21192009DD853 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; }; - 6D4AF17725D211A3009DD853 /* FiatUnits.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6D2AA7F92568B8750090B089 /* FiatUnits.plist */; }; 6D4AF17825D211A3009DD853 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; }; 6D4AF18425D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; }; 6D4AF18525D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; }; - 6D4AF18625D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; }; - 6D4AF18725D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; }; - 6D4AF18825D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; }; - 6D4AF18925D215D1009DD853 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; }; - 6D641F18255226DA003792DF /* MarketView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F17255226DA003792DF /* MarketView.swift */; }; - 6D641F19255226DA003792DF /* MarketView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F17255226DA003792DF /* MarketView.swift */; }; - 6D641F2325525054003792DF /* WalletInformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F2225525053003792DF /* WalletInformationView.swift */; }; - 6D641F2425525054003792DF /* WalletInformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F2225525053003792DF /* WalletInformationView.swift */; }; - 6D641F3525526311003792DF /* SendReceiveButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F3425526311003792DF /* SendReceiveButtons.swift */; }; - 6D641F3625526311003792DF /* SendReceiveButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F3425526311003792DF /* SendReceiveButtons.swift */; }; - 6D6CA4B9255872E3009312A5 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */; platformFilter = ios; }; - 6D6CA4BA255872E3009312A5 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */; platformFilter = ios; }; - 6D6CA4BD255872E3009312A5 /* PriceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA4BC255872E3009312A5 /* PriceWidget.swift */; }; - 6D6CA4C3255872E7009312A5 /* PriceWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6D6CA4B8255872E3009312A5 /* PriceWidgetExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 6D6CA4D725587397009312A5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D9A2E08254BA348007B5B82 /* Assets.xcassets */; }; - 6D6CA4E0255873BC009312A5 /* UserDefaultsGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */; }; - 6D6CA5152558EBA4009312A5 /* WidgetAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */; }; - 6D6CA5162558EBA4009312A5 /* WidgetAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */; }; - 6D6CA5282558EC52009312A5 /* PriceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5272558EC52009312A5 /* PriceView.swift */; }; - 6D6CA5292558EC52009312A5 /* PriceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5272558EC52009312A5 /* PriceView.swift */; }; - 6D6CA5322558ED4D009312A5 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */; }; - 6D6CA5332558ED54009312A5 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; }; - 6D6CA53C2558F316009312A5 /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; }; - 6D6CA5452558F365009312A5 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; }; - 6D99465F2555A660000E52E8 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */; platformFilter = ios; }; - 6D9946602555A660000E52E8 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */; platformFilter = ios; }; - 6D9946632555A660000E52E8 /* MarketWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9946622555A660000E52E8 /* MarketWidget.swift */; }; - 6D9946692555A661000E52E8 /* MarketWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6D99465E2555A660000E52E8 /* MarketWidgetExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 6D99467B2555A68A000E52E8 /* MarketView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F17255226DA003792DF /* MarketView.swift */; }; - 6D9946832555A695000E52E8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D9A2E08254BA348007B5B82 /* Assets.xcassets */; }; - 6D9946842555A695000E52E8 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; }; - 6D9946852555A695000E52E8 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */; }; - 6D9946862555A695000E52E8 /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; }; - 6D9946872555A695000E52E8 /* UserDefaultsGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */; }; - 6D9946882555A695000E52E8 /* SendReceiveButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F3425526311003792DF /* SendReceiveButtons.swift */; }; - 6D9946892555A695000E52E8 /* WalletInformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F2225525053003792DF /* WalletInformationView.swift */; }; - 6D99468A2555A695000E52E8 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; }; - 6D9A2E03254BA347007B5B82 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */; platformFilter = ios; }; - 6D9A2E04254BA347007B5B82 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */; platformFilter = ios; }; - 6D9A2E07254BA347007B5B82 /* WalletInformationAndMarketWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E06254BA347007B5B82 /* WalletInformationAndMarketWidget.swift */; }; - 6D9A2E09254BA348007B5B82 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D9A2E08254BA348007B5B82 /* Assets.xcassets */; }; - 6D9A2E0D254BA348007B5B82 /* WalletInformationAndMarketWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6D9A2E02254BA347007B5B82 /* WalletInformationAndMarketWidgetExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 6D9A2E6D254BAB1B007B5B82 /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; }; - 6D9A2E6F254BAB1B007B5B82 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; }; - 6DA7047E254E24D5005FE5E2 /* UserDefaultsGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */; }; - 6DEB4AAE254FB59C00E9F9AA /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */; platformFilter = ios; }; - 6DEB4AAF254FB59C00E9F9AA /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */; platformFilter = ios; }; - 6DEB4AB2254FB59C00E9F9AA /* WalletInformationWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4AB1254FB59C00E9F9AA /* WalletInformationWidget.swift */; }; - 6DEB4AB8254FB59E00E9F9AA /* WalletInformationWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6DEB4AAD254FB59B00E9F9AA /* WalletInformationWidgetExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 6DEB4B05254FB79100E9F9AA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D9A2E08254BA348007B5B82 /* Assets.xcassets */; }; - 6DEB4BD9254FB98E00E9F9AA /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; }; - 6DEB4BDA254FB98E00E9F9AA /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; }; - 6DEB4BDB254FB98E00E9F9AA /* UserDefaultsGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */; }; - 6DEB4BFB254FBA0E00E9F9AA /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; }; - 6DEB4BFC254FBA0E00E9F9AA /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; }; - 6DEB4C3B254FBF4800E9F9AA /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */; }; - 6DEB4C3C254FBF4800E9F9AA /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */; }; + 6DD4109D266CADF10087DE03 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3A252FE1A3004D72DF /* WidgetKit.framework */; }; + 6DD4109E266CADF10087DE03 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */; }; + 6DD410A1266CADF10087DE03 /* Widgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DD410A0266CADF10087DE03 /* Widgets.swift */; }; + 6DD410A7266CADF40087DE03 /* WidgetsExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6DD4109C266CADF10087DE03 /* WidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 6DD410AC266CAE470087DE03 /* PriceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA4BC255872E3009312A5 /* PriceWidget.swift */; }; + 6DD410AE266CAF1F0087DE03 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; }; + 6DD410AF266CAF5C0087DE03 /* WalletInformationAndMarketWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E06254BA347007B5B82 /* WalletInformationAndMarketWidget.swift */; }; + 6DD410B0266CAF5C0087DE03 /* WalletInformationWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4AB1254FB59C00E9F9AA /* WalletInformationWidget.swift */; }; + 6DD410B1266CAF5C0087DE03 /* WidgetAPI+Electrum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */; }; + 6DD410B2266CAF5C0087DE03 /* WalletInformationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F2225525053003792DF /* WalletInformationView.swift */; }; + 6DD410B3266CAF5C0087DE03 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */; }; + 6DD410B4266CAF5C0087DE03 /* WidgetAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */; }; + 6DD410B5266CAF5C0087DE03 /* WidgetDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */; }; + 6DD410B6266CAF5C0087DE03 /* PriceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5272558EC52009312A5 /* PriceView.swift */; }; + 6DD410B7266CAF5C0087DE03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D9A2E08254BA348007B5B82 /* Assets.xcassets */; }; + 6DD410B8266CAF5C0087DE03 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */; }; + 6DD410B9266CAF5C0087DE03 /* UserDefaultsGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */; }; + 6DD410BA266CAF5C0087DE03 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; }; + 6DD410BB266CAF5C0087DE03 /* MarketView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F17255226DA003792DF /* MarketView.swift */; }; + 6DD410BE266CAF5C0087DE03 /* SendReceiveButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D641F3425526311003792DF /* SendReceiveButtons.swift */; }; + 6DD410BF266CB13D0087DE03 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */; }; + 6DD410C0266CB1460087DE03 /* MarketWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D9946622555A660000E52E8 /* MarketWidget.swift */; }; 6DF25A9F249DB97E001D06F5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6DF25A9E249DB97E001D06F5 /* LaunchScreen.storyboard */; }; 6DFC807024EA0B6C007B8700 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; productRef = 6DFC806F24EA0B6C007B8700 /* SwiftPackageProductDependency */; }; 6DFC807224EA2FA9007B8700 /* ViewQRCodefaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DFC807124EA2FA9007B8700 /* ViewQRCodefaceController.swift */; }; 764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B468CC34D5B41F3950078EF /* libsqlite3.0.tbd */; }; 782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B9D9B3A7B2CB4255876B67AF /* libz.tbd */; }; + 8CBB9CC9ACE4B44B45C28DF8 /* libPods-WidgetsExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E45F9A25C15BEA362225C9B /* libPods-WidgetsExtension.a */; }; 906451CAD44154C2950030EC /* libPods-BlueWallet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 731973BA0AC6EA78962CE5B6 /* libPods-BlueWallet.a */; }; B40D4E34225841EC00428FCC /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E32225841EC00428FCC /* Interface.storyboard */; }; B40D4E36225841ED00428FCC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; }; @@ -119,7 +75,8 @@ B43D037C225847C500FBAA95 /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0376225847C500FBAA95 /* Wallet.swift */; }; B43D037D225847C500FBAA95 /* WalletInformation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43D0377225847C500FBAA95 /* WalletInformation.swift */; }; B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; }; - D0CC417D5450A724DE9F87FE /* libPods-MarketWidgetExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 41BD3AC9FD81723B68A63C12 /* libPods-MarketWidgetExtension.a */; platformFilter = ios; }; + E5D4794B26781FC0007838C1 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; }; + E5D4794C26781FC1007838C1 /* fiatUnits.json in Resources */ = {isa = PBXBuildFile; fileRef = 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -137,20 +94,6 @@ remoteGlobalIDString = 6D2A6460258BA92C0092292B; remoteInfo = Stickers; }; - 6D6CA4C1255872E7009312A5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 6D6CA4B7255872E3009312A5; - remoteInfo = PriceWidgetExtension; - }; - 6D9946442555A583000E52E8 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 6D99465D2555A660000E52E8; - remoteInfo = MarketWidgetExtension; - }; 6D9946672555A661000E52E8 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; @@ -158,19 +101,12 @@ remoteGlobalIDString = 6D99465D2555A660000E52E8; remoteInfo = MarketWidgetExtension; }; - 6D9A2E0B254BA348007B5B82 /* PBXContainerItemProxy */ = { + 6DD410A5266CADF40087DE03 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; proxyType = 1; - remoteGlobalIDString = 6D9A2E01254BA347007B5B82; - remoteInfo = MarketWidgetExtension; - }; - 6DEB4AB6254FB59E00E9F9AA /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 6DEB4AAC254FB59B00E9F9AA; - remoteInfo = WalletInformationWidgetExtension; + remoteGlobalIDString = 6DD4109B266CADF10087DE03; + remoteInfo = WidgetsExtension; }; B40D4E3E225841ED00428FCC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; @@ -196,11 +132,8 @@ dstSubfolderSpec = 13; files = ( 6D2A6468258BA92D0092292B /* Stickers.appex in Embed App Extensions */, - 6D9946692555A661000E52E8 /* MarketWidgetExtension.appex in Embed App Extensions */, - 6D6CA4C3255872E7009312A5 /* PriceWidgetExtension.appex in Embed App Extensions */, - 6D9A2E0D254BA348007B5B82 /* WalletInformationAndMarketWidgetExtension.appex in Embed App Extensions */, - 6DEB4AB8254FB59E00E9F9AA /* WalletInformationWidgetExtension.appex in Embed App Extensions */, 3271B0B5236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex in Embed App Extensions */, + 6DD410A7266CADF40087DE03 /* WidgetsExtension.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; @@ -319,7 +252,6 @@ 6D2A6461258BA92C0092292B /* Stickers.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = Stickers.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 6D2A6463258BA92D0092292B /* Stickers.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Stickers.xcassets; sourceTree = ""; }; 6D2A6465258BA92D0092292B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 6D2AA7F92568B8750090B089 /* FiatUnits.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = FiatUnits.plist; sourceTree = ""; }; 6D2AA8072568B8F40090B089 /* FiatUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiatUnit.swift; sourceTree = ""; }; 6D32C5C42596CE2F008C077C /* EventEmitter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EventEmitter.h; sourceTree = ""; }; 6D32C5C52596CE3A008C077C /* EventEmitter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EventEmitter.m; sourceTree = ""; }; @@ -330,28 +262,22 @@ 6D641F17255226DA003792DF /* MarketView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketView.swift; sourceTree = ""; }; 6D641F2225525053003792DF /* WalletInformationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInformationView.swift; sourceTree = ""; }; 6D641F3425526311003792DF /* SendReceiveButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendReceiveButtons.swift; sourceTree = ""; }; - 6D6CA4B8255872E3009312A5 /* PriceWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PriceWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 6D6CA4BC255872E3009312A5 /* PriceWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceWidget.swift; sourceTree = ""; }; - 6D6CA4C0255872E7009312A5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WidgetAPI+Electrum.swift"; sourceTree = ""; }; 6D6CA5272558EC52009312A5 /* PriceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PriceView.swift; sourceTree = ""; }; - 6D6CA6192558F6AB009312A5 /* PriceWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PriceWidgetExtension.entitlements; sourceTree = ""; }; - 6D99465E2555A660000E52E8 /* MarketWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MarketWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 6D9946622555A660000E52E8 /* MarketWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketWidget.swift; sourceTree = ""; }; - 6D9946662555A661000E52E8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 6D9947152555AB9E000E52E8 /* MarketWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MarketWidgetExtension.entitlements; sourceTree = ""; }; - 6D9A2E02254BA347007B5B82 /* WalletInformationAndMarketWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WalletInformationAndMarketWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 6D9A2E06254BA347007B5B82 /* WalletInformationAndMarketWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInformationAndMarketWidget.swift; sourceTree = ""; }; 6D9A2E08254BA348007B5B82 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 6D9A2E0A254BA348007B5B82 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 6D9A2E6A254BAB1B007B5B82 /* WidgetAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetAPI.swift; sourceTree = ""; }; 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WidgetDataStore.swift; sourceTree = ""; }; 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsGroup.swift; sourceTree = ""; }; + 6DD4109C266CADF10087DE03 /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 6DD410A0266CADF10087DE03 /* Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widgets.swift; sourceTree = ""; }; + 6DD410A4266CADF40087DE03 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = fiatUnits.json; path = ../../../../models/fiatUnits.json; sourceTree = ""; }; + 6DD410C3266CCB780087DE03 /* WidgetsExtension.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = WidgetsExtension.entitlements; sourceTree = SOURCE_ROOT; }; 6DEB496F254E38DE00E9F9AA /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainInterface.strings; sourceTree = ""; }; - 6DEB4AAD254FB59B00E9F9AA /* WalletInformationWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WalletInformationWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 6DEB4AB1254FB59C00E9F9AA /* WalletInformationWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletInformationWidget.swift; sourceTree = ""; }; - 6DEB4AB5254FB59E00E9F9AA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 6DEB4ACE254FB5D800E9F9AA /* WalletInformationWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WalletInformationWidgetExtension.entitlements; sourceTree = ""; }; 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; 6DF25A9E249DB97E001D06F5 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; @@ -362,8 +288,10 @@ 78A87E7251D94144A71A2F67 /* FontAwesome5_Solid.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Solid.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf"; sourceTree = ""; }; 7B468CC34D5B41F3950078EF /* libsqlite3.0.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.0.tbd; path = usr/lib/libsqlite3.0.tbd; sourceTree = SDKROOT; }; 7BAA8F97E61B677D33CF1944 /* Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-WalletInformationAndMarketWidgetExtension/Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig"; sourceTree = ""; }; + 7E45F9A25C15BEA362225C9B /* libPods-WidgetsExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-WidgetsExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 8448882949434D41A054C0B2 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ToolTipMenuTests.xctest; sourceTree = ""; }; 8637D4B5E14D443A9031DA95 /* libRNFS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNFS.a; sourceTree = ""; }; + 8C30FBCA42C6BF0B45757763 /* Pods-WidgetsExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetsExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-WidgetsExtension/Pods-WidgetsExtension.debug.xcconfig"; sourceTree = ""; }; 90F86BC5194548CA87D729A9 /* libToolTipMenu.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libToolTipMenu.a; sourceTree = ""; }; 94565BFC6A0C4235B3EC7B01 /* libRNSVG.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSVG.a; sourceTree = ""; }; 95208B2A05884A76B5BB99C0 /* libRCTGoogleAnalyticsBridge.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTGoogleAnalyticsBridge.a; sourceTree = ""; }; @@ -409,6 +337,7 @@ CD746B955C55410793BB72C0 /* libRNGestureHandler.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNGestureHandler.a; sourceTree = ""; }; CF4A4D7AAD974D67A2D62B3E /* MaterialIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"; sourceTree = ""; }; D6EC5B694E664FD7B02EDD2F /* libRNSentry.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSentry.a; sourceTree = ""; }; + E0A230940404CA7C51FBED37 /* Pods-WidgetsExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetsExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-WidgetsExtension/Pods-WidgetsExtension.release.xcconfig"; sourceTree = ""; }; E6B44173A8854B6D85D7F933 /* libRNVectorIcons-tvOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = "libRNVectorIcons-tvOS.a"; sourceTree = ""; }; E8E8CE89B3D142C6A8A56C34 /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; @@ -447,41 +376,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 6D6CA4B5255872E3009312A5 /* Frameworks */ = { + 6DD41099266CADF10087DE03 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6D6CA4BA255872E3009312A5 /* SwiftUI.framework in Frameworks */, - 6D6CA4B9255872E3009312A5 /* WidgetKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 6D99465B2555A660000E52E8 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 6D9946602555A660000E52E8 /* SwiftUI.framework in Frameworks */, - 6D99465F2555A660000E52E8 /* WidgetKit.framework in Frameworks */, - D0CC417D5450A724DE9F87FE /* libPods-MarketWidgetExtension.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 6D9A2DFF254BA347007B5B82 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 6D9A2E04254BA347007B5B82 /* SwiftUI.framework in Frameworks */, - 6D9A2E03254BA347007B5B82 /* WidgetKit.framework in Frameworks */, - 590C62D2ED8BF487C33945B0 /* libPods-WalletInformationAndMarketWidgetExtension.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 6DEB4AAA254FB59B00E9F9AA /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 6DEB4AAF254FB59C00E9F9AA /* SwiftUI.framework in Frameworks */, - 6DEB4AAE254FB59C00E9F9AA /* WidgetKit.framework in Frameworks */, + 6DD4109E266CADF10087DE03 /* SwiftUI.framework in Frameworks */, + 6DD4109D266CADF10087DE03 /* WidgetKit.framework in Frameworks */, + 8CBB9CC9ACE4B44B45C28DF8 /* libPods-WidgetsExtension.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -559,6 +460,7 @@ 6D333B3C252FE1A3004D72DF /* SwiftUI.framework */, 98455D960744E4E5DD50BA87 /* libPods-WalletInformationAndMarketWidgetExtension.a */, 41BD3AC9FD81723B68A63C12 /* libPods-MarketWidgetExtension.a */, + 7E45F9A25C15BEA362225C9B /* libPods-WidgetsExtension.a */, ); name = Frameworks; sourceTree = ""; @@ -610,7 +512,7 @@ 6D2AA8062568B8E50090B089 /* Fiat */ = { isa = PBXGroup; children = ( - 6D2AA7F92568B8750090B089 /* FiatUnits.plist */, + 6DD410AD266CAF1F0087DE03 /* fiatUnits.json */, 6D2AA8072568B8F40090B089 /* FiatUnit.swift */, ); path = Fiat; @@ -620,7 +522,6 @@ isa = PBXGroup; children = ( 6D6CA4BC255872E3009312A5 /* PriceWidget.swift */, - 6D6CA4C0255872E7009312A5 /* Info.plist */, ); path = PriceWidget; sourceTree = ""; @@ -629,7 +530,6 @@ isa = PBXGroup; children = ( 6D9946622555A660000E52E8 /* MarketWidget.swift */, - 6D9946662555A661000E52E8 /* Info.plist */, ); path = MarketWidget; sourceTree = ""; @@ -638,32 +538,33 @@ isa = PBXGroup; children = ( 6D9A2E06254BA347007B5B82 /* WalletInformationAndMarketWidget.swift */, - 6D9A2E0A254BA348007B5B82 /* Info.plist */, ); path = WalletInformationAndMarketWidget; sourceTree = ""; }; - 6DEB4AB0254FB59C00E9F9AA /* WalletInformationWidget */ = { - isa = PBXGroup; - children = ( - 6DEB4AB1254FB59C00E9F9AA /* WalletInformationWidget.swift */, - 6DEB4AB5254FB59E00E9F9AA /* Info.plist */, - ); - name = WalletInformationWidget; - path = ..; - sourceTree = ""; - }; - 6DEB4B18254FB7D700E9F9AA /* Widgets */ = { + 6DD4109F266CADF10087DE03 /* Widgets */ = { isa = PBXGroup; children = ( + 6DD410C3266CCB780087DE03 /* WidgetsExtension.entitlements */, + 6DD410A0266CADF10087DE03 /* Widgets.swift */, + 6DD410A4266CADF40087DE03 /* Info.plist */, + 6D9A2E08254BA348007B5B82 /* Assets.xcassets */, 6DEB4BC1254FB98300E9F9AA /* Shared */, 6D9946612555A660000E52E8 /* MarketWidget */, 6D6CA4BB255872E3009312A5 /* PriceWidget */, 6D9A2E05254BA347007B5B82 /* WalletInformationAndMarketWidget */, 6DEB4AB0254FB59C00E9F9AA /* WalletInformationWidget */, ); - name = Widgets; - path = WalletInformationWidget/Widgets; + path = Widgets; + sourceTree = ""; + }; + 6DEB4AB0254FB59C00E9F9AA /* WalletInformationWidget */ = { + isa = PBXGroup; + children = ( + 6DEB4AB1254FB59C00E9F9AA /* WalletInformationWidget.swift */, + ); + name = WalletInformationWidget; + path = ../WalletInformationWidget; sourceTree = ""; }; 6DEB4BC1254FB98300E9F9AA /* Shared */ = { @@ -675,7 +576,6 @@ 6D6CA5142558EBA3009312A5 /* WidgetAPI+Electrum.swift */, 6D9A2E6B254BAB1B007B5B82 /* WidgetDataStore.swift */, 6DA7047D254E24D5005FE5E2 /* UserDefaultsGroup.swift */, - 6D9A2E08254BA348007B5B82 /* Assets.xcassets */, 6DEB4BFA254FBA0E00E9F9AA /* Models.swift */, 6DEB4C3A254FBF4800E9F9AA /* Colors.swift */, 6D4AF18325D215D1009DD853 /* UserDefaultsExtension.swift */, @@ -698,16 +598,13 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( - 6D6CA6192558F6AB009312A5 /* PriceWidgetExtension.entitlements */, - 6D9947152555AB9E000E52E8 /* MarketWidgetExtension.entitlements */, - 6DEB4ACE254FB5D800E9F9AA /* WalletInformationWidgetExtension.entitlements */, 13B07FAE1A68108700A75B9A /* BlueWallet */, 00E356EF1AD99517003FC87E /* BlueWalletTests */, B40D4E31225841EC00428FCC /* BlueWalletWatch */, B40D4E40225841ED00428FCC /* BlueWalletWatch Extension */, 3271B0AC236E2E0700DA766F /* TodayExtension */, - 6DEB4B18254FB7D700E9F9AA /* Widgets */, 6D2A6462258BA92C0092292B /* Stickers */, + 6DD4109F266CADF10087DE03 /* Widgets */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, B40FE50A21FAD228005D5578 /* Recovered References */, @@ -726,11 +623,8 @@ B40D4E30225841EC00428FCC /* BlueWalletWatch.app */, B40D4E3C225841ED00428FCC /* BlueWalletWatch Extension.appex */, 3271B0A9236E2E0700DA766F /* BlueWallet - Bitcoin Price.appex */, - 6D9A2E02254BA347007B5B82 /* WalletInformationAndMarketWidgetExtension.appex */, - 6DEB4AAD254FB59B00E9F9AA /* WalletInformationWidgetExtension.appex */, - 6D99465E2555A660000E52E8 /* MarketWidgetExtension.appex */, - 6D6CA4B8255872E3009312A5 /* PriceWidgetExtension.appex */, 6D2A6461258BA92C0092292B /* Stickers.appex */, + 6DD4109C266CADF10087DE03 /* WidgetsExtension.appex */, ); name = Products; sourceTree = ""; @@ -744,6 +638,8 @@ FF45EB303C9601ED114589A4 /* Pods-MarketWidgetExtension.release.xcconfig */, AA7DCFB2C7887DF26EDB5710 /* Pods-WalletInformationAndMarketWidgetExtension.debug.xcconfig */, 7BAA8F97E61B677D33CF1944 /* Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig */, + 8C30FBCA42C6BF0B45757763 /* Pods-WidgetsExtension.debug.xcconfig */, + E0A230940404CA7C51FBED37 /* Pods-WidgetsExtension.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -849,12 +745,9 @@ dependencies = ( B40D4E4C225841ED00428FCC /* PBXTargetDependency */, 3271B0B4236E2E0700DA766F /* PBXTargetDependency */, - 6D9A2E0C254BA348007B5B82 /* PBXTargetDependency */, - 6DEB4AB7254FB59E00E9F9AA /* PBXTargetDependency */, - 6D9946452555A583000E52E8 /* PBXTargetDependency */, 6D9946682555A661000E52E8 /* PBXTargetDependency */, - 6D6CA4C2255872E7009312A5 /* PBXTargetDependency */, 6D2A6467258BA92D0092292B /* PBXTargetDependency */, + 6DD410A6266CADF40087DE03 /* PBXTargetDependency */, ); name = BlueWallet; productName = "Hello World"; @@ -894,74 +787,22 @@ productReference = 6D2A6461258BA92C0092292B /* Stickers.appex */; productType = "com.apple.product-type.app-extension.messages-sticker-pack"; }; - 6D6CA4B7255872E3009312A5 /* PriceWidgetExtension */ = { + 6DD4109B266CADF10087DE03 /* WidgetsExtension */ = { isa = PBXNativeTarget; - buildConfigurationList = 6D6CA4C6255872E7009312A5 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */; + buildConfigurationList = 6DD410A9266CADF40087DE03 /* Build configuration list for PBXNativeTarget "WidgetsExtension" */; buildPhases = ( - 6D6CA4B4255872E3009312A5 /* Sources */, - 6D6CA4B5255872E3009312A5 /* Frameworks */, - 6D6CA4B6255872E3009312A5 /* Resources */, + 0CD668CB2F65F36C4CADD5AB /* [CP] Check Pods Manifest.lock */, + 6DD41098266CADF10087DE03 /* Sources */, + 6DD41099266CADF10087DE03 /* Frameworks */, + 6DD4109A266CADF10087DE03 /* Resources */, ); buildRules = ( ); dependencies = ( ); - name = PriceWidgetExtension; - productName = PriceWidgetExtension; - productReference = 6D6CA4B8255872E3009312A5 /* PriceWidgetExtension.appex */; - productType = "com.apple.product-type.app-extension"; - }; - 6D99465D2555A660000E52E8 /* MarketWidgetExtension */ = { - isa = PBXNativeTarget; - buildConfigurationList = 6D99466A2555A661000E52E8 /* Build configuration list for PBXNativeTarget "MarketWidgetExtension" */; - buildPhases = ( - 3DC2B4F006D9D733C3782C62 /* [CP] Check Pods Manifest.lock */, - 6D99465A2555A660000E52E8 /* Sources */, - 6D99465B2555A660000E52E8 /* Frameworks */, - 6D99465C2555A660000E52E8 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = MarketWidgetExtension; - productName = MarketWidgetExtension; - productReference = 6D99465E2555A660000E52E8 /* MarketWidgetExtension.appex */; - productType = "com.apple.product-type.app-extension"; - }; - 6D9A2E01254BA347007B5B82 /* WalletInformationAndMarketWidgetExtension */ = { - isa = PBXNativeTarget; - buildConfigurationList = 6D9A2E0E254BA348007B5B82 /* Build configuration list for PBXNativeTarget "WalletInformationAndMarketWidgetExtension" */; - buildPhases = ( - E236ECCDCA1DDCA1D4270919 /* [CP] Check Pods Manifest.lock */, - 6D9A2DFE254BA347007B5B82 /* Sources */, - 6D9A2DFF254BA347007B5B82 /* Frameworks */, - 6D9A2E00254BA347007B5B82 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = WalletInformationAndMarketWidgetExtension; - productName = MarketWidgetExtension; - productReference = 6D9A2E02254BA347007B5B82 /* WalletInformationAndMarketWidgetExtension.appex */; - productType = "com.apple.product-type.app-extension"; - }; - 6DEB4AAC254FB59B00E9F9AA /* WalletInformationWidgetExtension */ = { - isa = PBXNativeTarget; - buildConfigurationList = 6DEB4ABB254FB59E00E9F9AA /* Build configuration list for PBXNativeTarget "WalletInformationWidgetExtension" */; - buildPhases = ( - 6DEB4AA9254FB59B00E9F9AA /* Sources */, - 6DEB4AAA254FB59B00E9F9AA /* Frameworks */, - 6DEB4AAB254FB59B00E9F9AA /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = WalletInformationWidgetExtension; - productName = WalletInformationWidgetExtension; - productReference = 6DEB4AAD254FB59B00E9F9AA /* WalletInformationWidgetExtension.appex */; + name = WidgetsExtension; + productName = WidgetsExtension; + productReference = 6DD4109C266CADF10087DE03 /* WidgetsExtension.appex */; productType = "com.apple.product-type.app-extension"; }; B40D4E2F225841EC00428FCC /* BlueWalletWatch */ = { @@ -1008,7 +849,7 @@ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1210; + LastSwiftUpdateCheck = 1250; LastUpgradeCheck = 1020; ORGANIZATIONNAME = BlueWallet; TargetAttributes = { @@ -1027,17 +868,8 @@ 6D2A6460258BA92C0092292B = { CreatedOnToolsVersion = 12.1; }; - 6D6CA4B7255872E3009312A5 = { - CreatedOnToolsVersion = 12.1; - }; - 6D99465D2555A660000E52E8 = { - CreatedOnToolsVersion = 12.1; - }; - 6D9A2E01254BA347007B5B82 = { - CreatedOnToolsVersion = 12.1; - }; - 6DEB4AAC254FB59B00E9F9AA = { - CreatedOnToolsVersion = 12.1; + 6DD4109B266CADF10087DE03 = { + CreatedOnToolsVersion = 12.5; }; B40D4E2F225841EC00428FCC = { CreatedOnToolsVersion = 10.2; @@ -1099,11 +931,8 @@ B40D4E2F225841EC00428FCC /* BlueWalletWatch */, B40D4E3B225841ED00428FCC /* BlueWalletWatch Extension */, 3271B0A8236E2E0700DA766F /* TodayExtension */, - 6D99465D2555A660000E52E8 /* MarketWidgetExtension */, - 6DEB4AAC254FB59B00E9F9AA /* WalletInformationWidgetExtension */, - 6D9A2E01254BA347007B5B82 /* WalletInformationAndMarketWidgetExtension */, - 6D6CA4B7255872E3009312A5 /* PriceWidgetExtension */, 6D2A6460258BA92C0092292B /* Stickers */, + 6DD4109B266CADF10087DE03 /* WidgetsExtension */, ); }; /* End PBXProject section */ @@ -1134,39 +963,12 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 6D6CA4B6255872E3009312A5 /* Resources */ = { + 6DD4109A266CADF10087DE03 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 6D2AA7FD2568B8750090B089 /* FiatUnits.plist in Resources */, - 6D6CA4D725587397009312A5 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 6D99465C2555A660000E52E8 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 6D2AA7FA2568B8750090B089 /* FiatUnits.plist in Resources */, - 6D9946832555A695000E52E8 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 6D9A2E00254BA347007B5B82 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 6D2AA7FC2568B8750090B089 /* FiatUnits.plist in Resources */, - 6D9A2E09254BA348007B5B82 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 6DEB4AAB254FB59B00E9F9AA /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 6D2AA7FB2568B8750090B089 /* FiatUnits.plist in Resources */, - 6DEB4B05254FB79100E9F9AA /* Assets.xcassets in Resources */, + 6DD410B7266CAF5C0087DE03 /* Assets.xcassets in Resources */, + 6DD410AE266CAF1F0087DE03 /* fiatUnits.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1175,6 +977,7 @@ buildActionMask = 2147483647; files = ( B40D4E36225841ED00428FCC /* Assets.xcassets in Resources */, + E5D4794C26781FC1007838C1 /* fiatUnits.json in Resources */, B40D4E34225841EC00428FCC /* Interface.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1183,8 +986,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 6D4AF17725D211A3009DD853 /* FiatUnits.plist in Resources */, B4EE583C226703320003363C /* Assets.xcassets in Resources */, + E5D4794B26781FC0007838C1 /* fiatUnits.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1205,22 +1008,26 @@ shellPath = /bin/sh; shellScript = "export SENTRY_PROPERTIES=sentry.properties\nexport NODE_BINARY=node\n../node_modules/@sentry/cli/bin/sentry-cli react-native xcode ../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; - 1D2D05C5A2622FFEA3FB0BEB /* [CP] Embed Pods Frameworks */ = { + 0CD668CB2F65F36C4CADD5AB /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-frameworks.sh", - "${PODS_ROOT}/../../node_modules/react-native-tor/ios/Libsifir_ios.framework", + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Libsifir_ios.framework", + "$(DERIVED_FILE_DIR)/Pods-WidgetsExtension-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 2130DE983D1D45AC8FC45F7E /* Upload Debug Symbols to Sentry */ = { @@ -1237,28 +1044,6 @@ shellPath = /bin/sh; shellScript = "export SENTRY_PROPERTIES=sentry.properties\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym"; }; - 3DC2B4F006D9D733C3782C62 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-MarketWidgetExtension-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 68CD4C52AC5B27E333599B5C /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1349,28 +1134,6 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-BlueWallet/Pods-BlueWallet-resources.sh\"\n"; showEnvVarsInLog = 0; }; - E236ECCDCA1DDCA1D4270919 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-WalletInformationAndMarketWidgetExtension-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1396,76 +1159,27 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 6D6CA4B4255872E3009312A5 /* Sources */ = { + 6DD41098266CADF10087DE03 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 6D6CA5452558F365009312A5 /* WidgetDataStore.swift in Sources */, - 6D6CA4E0255873BC009312A5 /* UserDefaultsGroup.swift in Sources */, - 6D6CA5322558ED4D009312A5 /* Colors.swift in Sources */, - 6D6CA4BD255872E3009312A5 /* PriceWidget.swift in Sources */, - 6D6CA5292558EC52009312A5 /* PriceView.swift in Sources */, - 6D2AA80B2568B8F40090B089 /* FiatUnit.swift in Sources */, - 6D6CA53C2558F316009312A5 /* WidgetAPI.swift in Sources */, - 6D4AF18925D215D1009DD853 /* UserDefaultsExtension.swift in Sources */, - 6D6CA5332558ED54009312A5 /* Models.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 6D99465A2555A660000E52E8 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 6D9946892555A695000E52E8 /* WalletInformationView.swift in Sources */, - 6D9946862555A695000E52E8 /* WidgetAPI.swift in Sources */, - 6D9946842555A695000E52E8 /* Models.swift in Sources */, - 6D9946882555A695000E52E8 /* SendReceiveButtons.swift in Sources */, - 6D9946852555A695000E52E8 /* Colors.swift in Sources */, - 6D99468A2555A695000E52E8 /* WidgetDataStore.swift in Sources */, - 6D2AA8082568B8F40090B089 /* FiatUnit.swift in Sources */, - 6D99467B2555A68A000E52E8 /* MarketView.swift in Sources */, - 6D9946872555A695000E52E8 /* UserDefaultsGroup.swift in Sources */, - 6D9946632555A660000E52E8 /* MarketWidget.swift in Sources */, - 6D4AF18625D215D1009DD853 /* UserDefaultsExtension.swift in Sources */, - 6D6CA5152558EBA4009312A5 /* WidgetAPI+Electrum.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 6D9A2DFE254BA347007B5B82 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 6D641F2325525054003792DF /* WalletInformationView.swift in Sources */, - 6D2AA80A2568B8F40090B089 /* FiatUnit.swift in Sources */, - 6D9A2E6D254BAB1B007B5B82 /* WidgetAPI.swift in Sources */, - 6DEB4C3B254FBF4800E9F9AA /* Colors.swift in Sources */, - 6D6CA5282558EC52009312A5 /* PriceView.swift in Sources */, - 6D9A2E6F254BAB1B007B5B82 /* WidgetDataStore.swift in Sources */, - 6DA7047E254E24D5005FE5E2 /* UserDefaultsGroup.swift in Sources */, - 6D9A2E07254BA347007B5B82 /* WalletInformationAndMarketWidget.swift in Sources */, - 6D641F3525526311003792DF /* SendReceiveButtons.swift in Sources */, - 6DEB4BFB254FBA0E00E9F9AA /* Models.swift in Sources */, - 6D641F18255226DA003792DF /* MarketView.swift in Sources */, - 6D6CA5162558EBA4009312A5 /* WidgetAPI+Electrum.swift in Sources */, - 6D4AF18825D215D1009DD853 /* UserDefaultsExtension.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 6DEB4AA9254FB59B00E9F9AA /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 6D641F2425525054003792DF /* WalletInformationView.swift in Sources */, - 6DEB4AB2254FB59C00E9F9AA /* WalletInformationWidget.swift in Sources */, - 6DEB4C3C254FBF4800E9F9AA /* Colors.swift in Sources */, - 6DEB4BDA254FB98E00E9F9AA /* WidgetAPI.swift in Sources */, - 6DEB4BDB254FB98E00E9F9AA /* UserDefaultsGroup.swift in Sources */, - 6DEB4BD9254FB98E00E9F9AA /* WidgetDataStore.swift in Sources */, - 6D2AA8092568B8F40090B089 /* FiatUnit.swift in Sources */, - 6D641F3625526311003792DF /* SendReceiveButtons.swift in Sources */, - 6DEB4BFC254FBA0E00E9F9AA /* Models.swift in Sources */, - 6D641F19255226DA003792DF /* MarketView.swift in Sources */, - 6D4AF18725D215D1009DD853 /* UserDefaultsExtension.swift in Sources */, + 6DD410BE266CAF5C0087DE03 /* SendReceiveButtons.swift in Sources */, + 6DD410B4266CAF5C0087DE03 /* WidgetAPI.swift in Sources */, + 6DD410A1266CADF10087DE03 /* Widgets.swift in Sources */, + 6DD410AC266CAE470087DE03 /* PriceWidget.swift in Sources */, + 6DD410B2266CAF5C0087DE03 /* WalletInformationView.swift in Sources */, + 6DD410B6266CAF5C0087DE03 /* PriceView.swift in Sources */, + 6DD410B3266CAF5C0087DE03 /* Colors.swift in Sources */, + 6DD410BB266CAF5C0087DE03 /* MarketView.swift in Sources */, + 6DD410B5266CAF5C0087DE03 /* WidgetDataStore.swift in Sources */, + 6DD410C0266CB1460087DE03 /* MarketWidget.swift in Sources */, + 6DD410BA266CAF5C0087DE03 /* FiatUnit.swift in Sources */, + 6DD410AF266CAF5C0087DE03 /* WalletInformationAndMarketWidget.swift in Sources */, + 6DD410BF266CB13D0087DE03 /* Models.swift in Sources */, + 6DD410B0266CAF5C0087DE03 /* WalletInformationWidget.swift in Sources */, + 6DD410B1266CAF5C0087DE03 /* WidgetAPI+Electrum.swift in Sources */, + 6DD410B9266CAF5C0087DE03 /* UserDefaultsGroup.swift in Sources */, + 6DD410B8266CAF5C0087DE03 /* UserDefaultsExtension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1508,37 +1222,18 @@ }; 6D2A6467258BA92D0092292B /* PBXTargetDependency */ = { isa = PBXTargetDependency; + platformFilter = ios; target = 6D2A6460258BA92C0092292B /* Stickers */; targetProxy = 6D2A6466258BA92D0092292B /* PBXContainerItemProxy */; }; - 6D6CA4C2255872E7009312A5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - platformFilter = ios; - target = 6D6CA4B7255872E3009312A5 /* PriceWidgetExtension */; - targetProxy = 6D6CA4C1255872E7009312A5 /* PBXContainerItemProxy */; - }; - 6D9946452555A583000E52E8 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - platformFilter = ios; - target = 6D99465D2555A660000E52E8 /* MarketWidgetExtension */; - targetProxy = 6D9946442555A583000E52E8 /* PBXContainerItemProxy */; - }; 6D9946682555A661000E52E8 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 6D99465D2555A660000E52E8 /* MarketWidgetExtension */; targetProxy = 6D9946672555A661000E52E8 /* PBXContainerItemProxy */; }; - 6D9A2E0C254BA348007B5B82 /* PBXTargetDependency */ = { + 6DD410A6266CADF40087DE03 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - platformFilter = ios; - target = 6D9A2E01254BA347007B5B82 /* WalletInformationAndMarketWidgetExtension */; - targetProxy = 6D9A2E0B254BA348007B5B82 /* PBXContainerItemProxy */; - }; - 6DEB4AB7254FB59E00E9F9AA /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - platformFilter = ios; - target = 6DEB4AAC254FB59B00E9F9AA /* WalletInformationWidgetExtension */; - targetProxy = 6DEB4AB6254FB59E00E9F9AA /* PBXContainerItemProxy */; + target = 6DD4109B266CADF10087DE03 /* WidgetsExtension */; + targetProxy = 6DD410A5266CADF40087DE03 /* PBXContainerItemProxy */; }; B40D4E3F225841ED00428FCC /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -1628,7 +1323,7 @@ CODE_SIGN_ENTITLEMENTS = BlueWallet/BlueWallet.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 611; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = A7W54YZ4WU; ENABLE_BITCODE = NO; @@ -1649,7 +1344,7 @@ "$(inherited)", "$(PROJECT_DIR)", ); - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.3; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1678,7 +1373,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 611; DEVELOPMENT_TEAM = A7W54YZ4WU; ENABLE_BITCODE = NO; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; @@ -1693,7 +1388,7 @@ "$(inherited)", "$(PROJECT_DIR)", ); - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.3; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1723,7 +1418,7 @@ CODE_SIGN_ENTITLEMENTS = "TodayExtension/BlueWallet - Bitcoin Price.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 611; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1734,7 +1429,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension; @@ -1762,7 +1457,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 611; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1773,7 +1468,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.3; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.TodayExtension; PRODUCT_NAME = "BlueWallet - Bitcoin Price"; @@ -1799,17 +1494,18 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 611; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Stickers/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1830,13 +1526,13 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 611; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Stickers/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.3; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.Stickers; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1846,8 +1542,9 @@ }; name = Release; }; - 6D6CA4C4255872E7009312A5 /* Debug */ = { + 6DD410AA266CADF40087DE03 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 8C30FBCA42C6BF0B45757763 /* Pods-WidgetsExtension.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; @@ -1858,120 +1555,40 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements; + CODE_SIGN_ENTITLEMENTS = WidgetsExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 611; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "$(SRCROOT)/WalletInformationWidget/Widgets/PriceWidget/Info.plist"; + INFOPLIST_FILE = Widgets/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.1.2; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.PriceWidget; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SUPPORTS_MACCATALYST = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 6D6CA4C5255872E7009312A5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = PriceWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 310; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = A7W54YZ4WU; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "$(SRCROOT)/WalletInformationWidget/Widgets/PriceWidget/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 6.1.2; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.PriceWidget; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SUPPORTS_MACCATALYST = YES; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 6D99466B2555A661000E52E8 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 367FA8CEB35BC9431019D98A /* Pods-MarketWidgetExtension.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = MarketWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = A7W54YZ4WU; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "$(SRCROOT)/WalletInformationWidget/Widgets/MarketWidget/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,6"; }; name = Debug; }; - 6D99466C2555A661000E52E8 /* Release */ = { + 6DD410AB266CADF40087DE03 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FF45EB303C9601ED114589A4 /* Pods-MarketWidgetExtension.release.xcconfig */; + baseConfigurationReference = E0A230940404CA7C51FBED37 /* Pods-WidgetsExtension.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; @@ -1982,198 +1599,33 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = MarketWidgetExtension.entitlements; + CODE_SIGN_ENTITLEMENTS = WidgetsExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 611; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = A7W54YZ4WU; GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "$(SRCROOT)/WalletInformationWidget/Widgets/MarketWidget/Info.plist"; + INFOPLIST_FILE = Widgets/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.3; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.MarketWidget; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SUPPORTS_MACCATALYST = YES; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 6D9A2E0F254BA348007B5B82 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AA7DCFB2C7887DF26EDB5710 /* Pods-WalletInformationAndMarketWidgetExtension.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = MarketWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = A7W54YZ4WU; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_EXPAND_BUILD_SETTINGS = YES; - INFOPLIST_FILE = WalletInformationWidget/Widgets/MarketWidget/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 6.1.2; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationAndMarketWidget; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SUPPORTS_MACCATALYST = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 6D9A2E10254BA348007B5B82 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7BAA8F97E61B677D33CF1944 /* Pods-WalletInformationAndMarketWidgetExtension.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = MarketWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 310; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = A7W54YZ4WU; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_EXPAND_BUILD_SETTINGS = YES; - INFOPLIST_FILE = WalletInformationWidget/Widgets/MarketWidget/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 6.1.2; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationAndMarketWidget; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; SKIP_INSTALL = YES; SUPPORTS_MACCATALYST = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 6DEB4AB9254FB59E00E9F9AA /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = WalletInformationWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = A7W54YZ4WU; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = WalletInformationWidget/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 6.1.2; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationWidget; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SUPPORTS_MACCATALYST = YES; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 6DEB4ABA254FB59E00E9F9AA /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = WalletInformationWidgetExtension.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 310; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = A7W54YZ4WU; - GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = WalletInformationWidget/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 6.1.2; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.WalletInformationWidget; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SKIP_INSTALL = YES; - SUPPORTS_MACCATALYST = YES; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,6"; }; name = Release; }; @@ -2209,6 +1661,7 @@ COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 "; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -2224,7 +1677,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.2; LIBRARY_SEARCH_PATHS = "\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -2265,6 +1718,7 @@ COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 "; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -2273,7 +1727,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.2; LIBRARY_SEARCH_PATHS = "\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; @@ -2296,7 +1750,7 @@ CODE_SIGN_ENTITLEMENTS = "BlueWalletWatch Extension/BlueWalletWatch Extension.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 611; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -2306,7 +1760,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension; @@ -2336,7 +1790,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 611; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -2346,7 +1800,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.3; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch.extension; PRODUCT_NAME = "${TARGET_NAME}"; @@ -2376,13 +1830,13 @@ CODE_SIGN_ENTITLEMENTS = BlueWalletWatch/BlueWalletWatch.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 611; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; IBSC_MODULE = BlueWalletWatch_Extension; INFOPLIST_FILE = BlueWalletWatch/Info.plist; - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch; @@ -2415,13 +1869,13 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 310; + CURRENT_PROJECT_VERSION = 611; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; IBSC_MODULE = BlueWalletWatch_Extension; INFOPLIST_FILE = BlueWalletWatch/Info.plist; - MARKETING_VERSION = 6.1.2; + MARKETING_VERSION = 6.2.3; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.bluewallet.bluewallet.watch; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2467,38 +1921,11 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 6D6CA4C6255872E7009312A5 /* Build configuration list for PBXNativeTarget "PriceWidgetExtension" */ = { + 6DD410A9266CADF40087DE03 /* Build configuration list for PBXNativeTarget "WidgetsExtension" */ = { isa = XCConfigurationList; buildConfigurations = ( - 6D6CA4C4255872E7009312A5 /* Debug */, - 6D6CA4C5255872E7009312A5 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 6D99466A2555A661000E52E8 /* Build configuration list for PBXNativeTarget "MarketWidgetExtension" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 6D99466B2555A661000E52E8 /* Debug */, - 6D99466C2555A661000E52E8 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 6D9A2E0E254BA348007B5B82 /* Build configuration list for PBXNativeTarget "WalletInformationAndMarketWidgetExtension" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 6D9A2E0F254BA348007B5B82 /* Debug */, - 6D9A2E10254BA348007B5B82 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 6DEB4ABB254FB59E00E9F9AA /* Build configuration list for PBXNativeTarget "WalletInformationWidgetExtension" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 6DEB4AB9254FB59E00E9F9AA /* Debug */, - 6DEB4ABA254FB59E00E9F9AA /* Release */, + 6DD410AA266CADF40087DE03 /* Debug */, + 6DD410AB266CADF40087DE03 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/MarketWidgetExtension.xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/MarketWidgetExtension.xcscheme deleted file mode 100644 index ca8ab5e6c..000000000 --- a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/MarketWidgetExtension.xcscheme +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/PriceWidgetExtension.xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/PriceWidgetExtension.xcscheme deleted file mode 100644 index f92a34ef4..000000000 --- a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/PriceWidgetExtension.xcscheme +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WalletInformationAndMarketWidgetExtension.xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WalletInformationAndMarketWidgetExtension.xcscheme deleted file mode 100644 index fcdc15c16..000000000 --- a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WalletInformationAndMarketWidgetExtension.xcscheme +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WalletInformationWidgetExtension.xcscheme b/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WalletInformationWidgetExtension.xcscheme deleted file mode 100644 index 6a1f99854..000000000 --- a/ios/BlueWallet.xcodeproj/xcshareddata/xcschemes/WalletInformationWidgetExtension.xcscheme +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ios/BlueWallet/AppDelegate.m b/ios/BlueWallet/AppDelegate.m index 4593aaf4c..685ef82a9 100644 --- a/ios/BlueWallet/AppDelegate.m +++ b/ios/BlueWallet/AppDelegate.m @@ -14,6 +14,7 @@ #import #import #import "EventEmitter.h" +@import WatchConnectivity; #if !TARGET_OS_MACCATALYST #ifdef FB_SONARKIT_ENABLED #import @@ -87,6 +88,10 @@ static void InitializeFlipper(UIApplication *application) { return NO; } +- (void)applicationWillTerminate:(UIApplication *)application { + [WCSession.defaultSession updateApplicationContext:@{@"isWalletsInitialized": @NO} error:nil]; +} + - (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL succeeded)) completionHandler { [RNQuickActionManager onQuickActionPress:shortcutItem completionHandler:completionHandler]; } diff --git a/ios/BlueWallet/Info.plist b/ios/BlueWallet/Info.plist index cb5808010..2eada192f 100644 --- a/ios/BlueWallet/Info.plist +++ b/ios/BlueWallet/Info.plist @@ -167,10 +167,6 @@ In order to import an image for scanning, we need your permission to access your photo library. NSSpeechRecognitionUsageDescription This alert should not show up as we do not require this data - NSUserActivityTypes - - ConfigurationIntent - UIAppFonts AntDesign.ttf diff --git a/ios/BlueWalletWatch Extension/ComplicationController.swift b/ios/BlueWalletWatch Extension/ComplicationController.swift index 4b485b77b..88f14dcb2 100644 --- a/ios/BlueWalletWatch Extension/ComplicationController.swift +++ b/ios/BlueWalletWatch Extension/ComplicationController.swift @@ -39,12 +39,14 @@ class ComplicationController: NSObject, CLKComplicationDataSource { let entry: CLKComplicationTimelineEntry let date: Date let valueLabel: String + let valueSmallLabel: String let currencySymbol: String let timeLabel: String - if let price = marketData?.formattedRateForComplication, let marketDatadata = marketData?.date, let lastUpdated = marketData?.formattedDate { + if let price = marketData?.formattedRateForComplication, let priceAbbreviated = marketData?.formattedRateForSmallComplication, let marketDatadata = marketData?.date, let lastUpdated = marketData?.formattedDate { date = marketDatadata valueLabel = price timeLabel = lastUpdated + valueSmallLabel = priceAbbreviated if let preferredFiatCurrency = UserDefaults.standard.string(forKey: "preferredFiatCurrency"), let preferredFiatUnit = fiatUnit(currency: preferredFiatCurrency) { currencySymbol = preferredFiatUnit.symbol } else { @@ -53,24 +55,25 @@ class ComplicationController: NSObject, CLKComplicationDataSource { } else { valueLabel = "--" timeLabel = "--" + valueSmallLabel = "--" currencySymbol = fiatUnit(currency: "USD")!.symbol date = Date() } - let line1Text = CLKSimpleTextProvider(text:valueLabel) let line2Text = CLKSimpleTextProvider(text:currencySymbol) + let line1SmallText = CLKSimpleTextProvider(text: valueSmallLabel) switch complication.family { case .circularSmall: let template = CLKComplicationTemplateCircularSmallStackText() - template.line1TextProvider = line1Text + template.line1TextProvider = line1SmallText template.line2TextProvider = line2Text entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template) handler(entry) case .utilitarianSmallFlat: let template = CLKComplicationTemplateUtilitarianSmallFlat() if #available(watchOSApplicationExtension 6.0, *) { - template.textProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel) + template.textProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueSmallLabel) } else { handler(nil) } @@ -85,7 +88,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource { case .graphicCircular: if #available(watchOSApplicationExtension 6.0, *) { let template = CLKComplicationTemplateGraphicCircularStackText() - template.line1TextProvider = line1Text + template.line1TextProvider = line1SmallText template.line2TextProvider = line2Text entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template) handler(entry) @@ -94,14 +97,14 @@ class ComplicationController: NSObject, CLKComplicationDataSource { } case .modularSmall: let template = CLKComplicationTemplateModularSmallStackText() - template.line1TextProvider = line1Text + template.line1TextProvider = line1SmallText template.line2TextProvider = line2Text entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template) handler(entry) case .graphicCorner: let template = CLKComplicationTemplateGraphicCornerStackText() if #available(watchOSApplicationExtension 6.0, *) { - template.outerTextProvider = CLKTextProvider(format: "%@", valueLabel) + template.outerTextProvider = CLKTextProvider(format: "%@", valueSmallLabel) template.innerTextProvider = CLKTextProvider(format: "%@", currencySymbol) } else { handler(nil) @@ -111,7 +114,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource { case .graphicBezel: let template = CLKComplicationTemplateGraphicBezelCircularText() if #available(watchOSApplicationExtension 6.0, *) { - template.textProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel) + template.textProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueSmallLabel) let imageProvider = CLKFullColorImageProvider(fullColorImage: UIImage(named: "Complication/Graphic Bezel")!) let circularTemplate = CLKComplicationTemplateGraphicCircularImage() circularTemplate.imageProvider = imageProvider @@ -135,7 +138,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource { if #available(watchOSApplicationExtension 6.0, *) { template.headerTextProvider = CLKTextProvider(format: "Bitcoin Price") template.body1TextProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel) - template.body2TextProvider = CLKTextProvider(format: "%@", timeLabel) + template.body2TextProvider = CLKTextProvider(format: "at %@", timeLabel) entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template) handler(entry) } else { @@ -145,7 +148,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource { let template = CLKComplicationTemplateExtraLargeStackText() if #available(watchOSApplicationExtension 6.0, *) { template.line1TextProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel) - template.line2TextProvider = CLKTextProvider(format: "%@", timeLabel) + template.line2TextProvider = CLKTextProvider(format: "at %@", timeLabel) entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template) handler(entry) } else { @@ -156,7 +159,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource { if #available(watchOSApplicationExtension 6.0, *) { template.headerTextProvider = CLKTextProvider(format: "Bitcoin Price") template.body1TextProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel) - template.body2TextProvider = CLKTextProvider(format: "%@", timeLabel) + template.body2TextProvider = CLKTextProvider(format: "at %@", timeLabel) entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template) handler(entry) } else { @@ -166,7 +169,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource { if #available(watchOSApplicationExtension 7.0, *) { let template = CLKComplicationTemplateGraphicExtraLargeCircularStackText() template.line1TextProvider = CLKTextProvider(format: "%@%@", currencySymbol, valueLabel) - template.line1TextProvider = CLKTextProvider(format: "%@", timeLabel) + template.line1TextProvider = CLKTextProvider(format: "at %@", timeLabel) entry = CLKComplicationTimelineEntry(date: date, complicationTemplate: template) handler(entry) } else { diff --git a/ios/BlueWalletWatch Extension/Objects/WatchDataSource.swift b/ios/BlueWalletWatch Extension/Objects/WatchDataSource.swift index dbd25e23a..f9a4f2a80 100644 --- a/ios/BlueWalletWatch Extension/Objects/WatchDataSource.swift +++ b/ios/BlueWalletWatch Extension/Objects/WatchDataSource.swift @@ -20,6 +20,7 @@ class WatchDataSource: NSObject, WCSessionDelegate { static let shared = WatchDataSource() var wallets: [Wallet] = [Wallet]() + var companionWalletsInitialized = false private let keychain = KeychainSwift() override init() { @@ -108,6 +109,9 @@ class WatchDataSource: NSObject, WCSessionDelegate { UserDefaults.standard.set(preferredFiatCurrencyUnit.endPointKey, forKey: "preferredFiatCurrency") UserDefaults.standard.synchronize() ExtensionDelegate.preferredFiatCurrencyChanged() + } else if let isWalletsInitialized = data["isWalletsInitialized"] as? Bool { + companionWalletsInitialized = isWalletsInitialized + NotificationCenter.default.post(Notifications.dataUpdated) } else { WatchDataSource.shared.processWalletsData(walletsInfo: data) } @@ -128,6 +132,8 @@ class WatchDataSource: NSObject, WCSessionDelegate { }) { (error) in print(error) } + } else { + WatchDataSource.shared.companionWalletsInitialized = false } } diff --git a/ios/BlueWalletWatch Extension/ReceiveInterfaceController.swift b/ios/BlueWalletWatch Extension/ReceiveInterfaceController.swift index 07729bac4..8d759e45d 100644 --- a/ios/BlueWalletWatch Extension/ReceiveInterfaceController.swift +++ b/ios/BlueWalletWatch Extension/ReceiveInterfaceController.swift @@ -55,7 +55,7 @@ class ReceiveInterfaceController: WKInterfaceController { self?.toggleViewButtonPressed() WCSession.default.sendMessage(["message": "fetchTransactions"], replyHandler: nil, errorHandler: nil) } else { - self?.presentAlert(withTitle: "Error", message: "Unable to create invoice. Please, make sure your iPhone is paired and nearby.", preferredStyle: .alert, actions: [WKAlertAction(title: "OK", style: .default, handler: { [weak self] in + self?.presentAlert(withTitle: "Error", message: "Unable to create invoice. Please open BlueWallet on your iPhone and unlock your wallets.", preferredStyle: .alert, actions: [WKAlertAction(title: "OK", style: .default, handler: { [weak self] in self?.dismiss() self?.pop() })]) diff --git a/ios/BlueWalletWatch Extension/SpecifyInterfaceController.swift b/ios/BlueWalletWatch Extension/SpecifyInterfaceController.swift index b6116408f..c98665498 100644 --- a/ios/BlueWalletWatch Extension/SpecifyInterfaceController.swift +++ b/ios/BlueWalletWatch Extension/SpecifyInterfaceController.swift @@ -89,8 +89,14 @@ class SpecifyInterfaceController: WKInterfaceController { } @IBAction func createButtonTapped() { - NotificationCenter.default.post(name: NotificationName.createQRCode, object: specifiedQRContent) - dismiss() + if WatchDataSource.shared.companionWalletsInitialized { + NotificationCenter.default.post(name: NotificationName.createQRCode, object: specifiedQRContent) + dismiss() + } else { + presentAlert(withTitle: "Error", message: "Unable to create invoice. Please open BlueWallet on your iPhone and unlock your wallets.", preferredStyle: .alert, actions: [WKAlertAction(title: "OK", style: .default, handler: { [weak self] in + self?.dismiss() + })]) + } } override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? { diff --git a/ios/BlueWalletWatch Extension/WalletDetailsInterfaceController.swift b/ios/BlueWalletWatch Extension/WalletDetailsInterfaceController.swift index 9af9af2ec..31e0df49c 100644 --- a/ios/BlueWalletWatch Extension/WalletDetailsInterfaceController.swift +++ b/ios/BlueWalletWatch Extension/WalletDetailsInterfaceController.swift @@ -92,7 +92,6 @@ class WalletDetailsInterfaceController: WKInterfaceController { transactionsTable.setHidden(wallet?.transactions.isEmpty ?? true) noTransactionsLabel.setHidden(!(wallet?.transactions.isEmpty ?? false)) receiveButton.setHidden(wallet?.receiveAddress.isEmpty ?? true) - createInvoiceButton.setEnabled(WCSession.default.isReachable) } @IBAction func receiveMenuItemTapped() { @@ -116,9 +115,13 @@ class WalletDetailsInterfaceController: WKInterfaceController { } @IBAction func createInvoiceTapped() { - if (WCSession.default.isReachable) { + if (WatchDataSource.shared.companionWalletsInitialized) { pushController(withName: ReceiveInterfaceController.identifier, context: (wallet?.identifier, "createInvoice")) - } + } else { + presentAlert(withTitle: "Error", message: "Unable to create invoice. Please open BlueWallet on your iPhone and unlock your wallets.", preferredStyle: .alert, actions: [WKAlertAction(title: "OK", style: .default, handler: { [weak self] in + self?.dismiss() + })]) + } } override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? { diff --git a/ios/BlueWalletWatch/Base.lproj/Interface.storyboard b/ios/BlueWalletWatch/Base.lproj/Interface.storyboard index 0d1e49864..18a09da65 100644 --- a/ios/BlueWalletWatch/Base.lproj/Interface.storyboard +++ b/ios/BlueWalletWatch/Base.lproj/Interface.storyboard @@ -1,10 +1,10 @@ - + - - + + @@ -80,7 +80,7 @@ -