package gui // createIdentityGui.go implements pages to choose/create new identity hashes import "fyne.io/fyne/v2" import "fyne.io/fyne/v2/widget" import "fyne.io/fyne/v2/layout" import "fyne.io/fyne/v2/theme" import "fyne.io/fyne/v2/container" import "fyne.io/fyne/v2/dialog" import "fyne.io/fyne/v2/data/binding" import "seekia/resources/wordLists" import "seekia/internal/appMemory" import "seekia/internal/helpers" import "seekia/internal/identity" import "seekia/internal/encoding" import "seekia/internal/myIdentity" import "seekia/internal/seedPhrase" import "seekia/internal/mySeedPhrases" import "sync" import "strings" import "math" import "time" import "errors" func setChooseNewIdentityHashPage(window fyne.Window, myIdentityType string, previousPage func(), onceCompletePage func()){ currentPage := func(){ setChooseNewIdentityHashPage(window, myIdentityType, previousPage, onceCompletePage) } title := getPageTitleCentered(translate("Choose " + myIdentityType + " Identity Hash")) backButton := getBackButtonCentered(previousPage) myIdentityExists, _, err := myIdentity.GetMyIdentityHash(myIdentityType) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (myIdentityExists == true){ // This should not occur. This page should only be called if no identity exists. description1 := getLabelCentered("Your " + myIdentityType + " identity already exists.") description2 := getLabelCentered("Delete your identity before creating a new one.") page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2) setPageContent(page, window) return } description1 := getLabelCentered("Choose your " + myIdentityType + " profile identity hash.") description2 := getLabelCentered("Your identity hash cannot be changed once selected.") createCustomDescription := getLabelCentered("Create a custom identity hash:") submitPageFunction := func(newSeedPhrase string, prevPage func()){ setConfirmMyNewSeedPhrasePage(window, myIdentityType, newSeedPhrase, prevPage, onceCompletePage) } createCustomIdentityHashButton := getWidgetCentered(widget.NewButtonWithIcon("Create Custom", theme.SearchReplaceIcon(), func(){ setCreateCustomIdentityHashPage(window, myIdentityType, false, 0, 0, currentPage, submitPageFunction) })) selectRandomIdentityHashLabel := getLabelCentered("Select a random identity hash:") identityHashLabelColumn := container.NewVBox(widget.NewSeparator()) chooseIdentityHashColumn := container.NewVBox(widget.NewSeparator()) //TODO: Fix to retrieve language from settings myLanguage := "English" for n := 0; n < 3; n++ { newSeedPhrase, newSeedPhraseHash, err := seedPhrase.GetNewRandomSeedPhrase(myLanguage) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } newIdentityHash, err := identity.GetIdentityHashFromSeedPhraseHash(newSeedPhraseHash, myIdentityType) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } newIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(newIdentityHash) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } identityHashText := getBoldLabelCentered(newIdentityHashString) selectButton := widget.NewButtonWithIcon("Select", theme.ConfirmIcon(), func(){ setConfirmMyNewSeedPhrasePage(window, myIdentityType, newSeedPhrase, currentPage, onceCompletePage) }) identityHashLabelColumn.Add(identityHashText) chooseIdentityHashColumn.Add(selectButton) identityHashLabelColumn.Add(widget.NewSeparator()) chooseIdentityHashColumn.Add(widget.NewSeparator()) } randomIdentityHashesGrid := container.NewHBox(layout.NewSpacer(), identityHashLabelColumn, chooseIdentityHashColumn, layout.NewSpacer()) refreshOptionsButton := getWidgetCentered(widget.NewButtonWithIcon("Refresh Choices", theme.ViewRefreshIcon(), currentPage)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description1, description2, widget.NewSeparator(), createCustomDescription, createCustomIdentityHashButton, widget.NewSeparator(), selectRandomIdentityHashLabel, randomIdentityHashesGrid, refreshOptionsButton) setPageContent(page, window) } func setConfirmMyNewSeedPhrasePage(window fyne.Window, myIdentityType string, newSeedPhrase string, previousPage func(), nextPage func()){ currentPage := func(){setConfirmMyNewSeedPhrasePage(window, myIdentityType, newSeedPhrase, previousPage, nextPage)} title := getPageTitleCentered("Confirm New Identity Hash") backButton := getBackButtonCentered(previousPage) description := getLabelCentered("Confirm new identity hash for " + myIdentityType + " profile?") isValid := seedPhrase.VerifySeedPhrase(newSeedPhrase) if (isValid == false){ setErrorEncounteredPage(window, errors.New("setConfirmMyNewSeedPhrasePage called with invalid seed phrase."), previousPage) return } newSeedPhraseHash, err := seedPhrase.ConvertSeedPhraseToSeedPhraseHash(newSeedPhrase) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } newIdentityHash, err := identity.GetIdentityHashFromSeedPhraseHash(newSeedPhraseHash, myIdentityType) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } newIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(newIdentityHash) if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } identityHashLabel := getContainerCentered(getWidgetBoxed(getBoldLabel(newIdentityHashString))) confirmButton := getWidgetCentered(widget.NewButtonWithIcon("Confirm", theme.ConfirmIcon(), func(){ exists, _, err := myIdentity.GetMyIdentityHash(myIdentityType) if (err != nil) { setErrorEncounteredPage(window, err, currentPage) return } if (exists == true){ // This should not occur, as this page should only be shown if existing identity hash is not present. setErrorEncounteredPage(window, errors.New("Trying to set seed phrase when existing identity is present."), previousPage) return } err = mySeedPhrases.SetMySeedPhrase(myIdentityType, newSeedPhrase) if (err != nil) { setErrorEncounteredPage(window, err, currentPage) return } setEnterMyNewSeedPhrasePage(window, myIdentityType, newSeedPhrase, nextPage) })) seedPhraseDescription := getLabelCentered("Write down your seed phrase to backup your identity.") seedPhraseLabel := widget.NewMultiLineEntry() seedPhraseLabel.Wrapping = 3 seedPhraseLabel.SetText(newSeedPhrase) seedPhraseLabel.OnChanged = func(_ string){ seedPhraseLabel.SetText(newSeedPhrase) } seedPhraseLabelBoxed := getWidgetBoxed(seedPhraseLabel) widener := widget.NewLabel(" ") seedPhraseLabelWidened := getContainerCentered(container.NewGridWithColumns(1, seedPhraseLabelBoxed, widener)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description, identityHashLabel, confirmButton, widget.NewSeparator(), seedPhraseDescription, seedPhraseLabelWidened) setPageContent(page, window) } func setEnterMyNewSeedPhrasePage(window fyne.Window, myIdentityType string, newSeedPhrase string, nextPage func()){ title := getPageTitleCentered("Enter Seed Phrase") description1 := getLabelCentered("Enter your new " + myIdentityType + " seed phrase to confirm your wrote it down correctly.") description2 := getLabelCentered("You can skip this step.") descriptionsContainer := container.NewVBox(layout.NewSpacer(), description1, description2, layout.NewSpacer()) seedPhraseLabel := widget.NewMultiLineEntry() seedPhraseLabel.Wrapping = 3 seedPhraseLabel.SetText(newSeedPhrase) seedPhraseLabel.OnChanged = func(_ string){ seedPhraseLabel.SetText(newSeedPhrase) } seedPhraseLabelBoxed := getWidgetBoxed(seedPhraseLabel) seedPhraseLabelWithDescriptions := getContainerCentered(container.NewGridWithColumns(1, descriptionsContainer, seedPhraseLabelBoxed)) isCorrectStatusBinding := binding.NewString() isCorrectStatusBinding.Set("Incorrect") isCorrectStatusLabel := widget.NewLabelWithData(isCorrectStatusBinding) isCorrectStatusLabel.TextStyle = getFyneTextStyle_Bold() isCorrectStatusIcon := widget.NewIcon(theme.CancelIcon()) statusLabel := widget.NewLabel("Status:") isCorrectRow := container.NewHBox(layout.NewSpacer(), statusLabel, isCorrectStatusLabel, isCorrectStatusIcon, layout.NewSpacer()) skipOrExitButton := widget.NewButtonWithIcon("Skip", theme.MediaFastForwardIcon(), nextPage) seedPhraseEntryOnChangedFunction := func(newText string){ if (newText != newSeedPhrase){ isCorrectStatusBinding.Set("Incorrect") isCorrectStatusIcon.SetResource(theme.CancelIcon()) skipOrExitButton.SetText("Skip") skipOrExitButton.SetIcon(theme.MediaFastForwardIcon()) return } isCorrectStatusBinding.Set("Correct") isCorrectStatusIcon.SetResource(theme.ConfirmIcon()) skipOrExitButton.SetText("Exit") skipOrExitButton.SetIcon(theme.ConfirmIcon()) } seedPhraseEntry := widget.NewMultiLineEntry() seedPhraseEntry.Wrapping = 3 seedPhraseEntry.OnChanged = seedPhraseEntryOnChangedFunction seedPhraseEntryBoxed := getWidgetBoxed(seedPhraseEntry) skipOrExitButtonCentered := getWidgetCentered(skipOrExitButton) whitespace := " " emptyLabel := widget.NewLabel("") widener := widget.NewLabel(whitespace) skipOrExitButtonWithWhitespace := container.NewVBox(skipOrExitButtonCentered, emptyLabel, widener) seedPhraseEntryWithButton := getContainerCentered(container.NewGridWithColumns(1, seedPhraseEntryBoxed, skipOrExitButtonWithWhitespace)) page := container.NewVBox(title, widget.NewSeparator(), seedPhraseLabelWithDescriptions, widget.NewSeparator(), isCorrectRow, seedPhraseEntryWithButton) setPageContent(page, window) } //Inputs: // -fyne.Window // -string: Identity Type ("Mate"/"Host"/"Moderator") // -bool: Benchmark is done (is false if we need to perform the benchmark // -int64: Hashes per second (is 0 if benchmark hasn't been performed) // -int: Optimal number of goroutines // -previousPage() // func(string, func()): submitPage function = func(newSeedPhrase string, previousPage func()) func setCreateCustomIdentityHashPage(window fyne.Window, identityType string, benchmarkIsDone bool, computerHashesPerSecond int64, optimalNumberOfGoroutines int, previousPage func(), submitPage func(string, func()) ){ if (benchmarkIsDone == false){ setLoadingScreen(window, "Create Custom Identity Hash", "Performing benchmark, this will take 4 seconds...") // Output: // -int64: Hashes per second // -int: Optimal number of goroutines // -error performBenchmark := func()(int64, int, error){ //TODO: Fix to retrieve language from settings currentLanguage := "English" currentLanguageWordList, err := wordLists.GetWordListFromLanguage(currentLanguage) if (err != nil) { return 0, 0, err } optimalNumberOfGoroutines := 0 optimalNumberOfGoroutinesHashesPerSecond := int64(0) // We will try 1-8 goroutines to see which is the fastest for numberOfGoroutines:=1; numberOfGoroutines<=8; numberOfGoroutines++{ // This counter will store the number of hashes we can compute in 1 second var counterMutex sync.Mutex counter := int64(0) // This bool keeps track of if the identity hash benchmark is happening var generateHashesStatusBoolMutex sync.RWMutex generateHashesStatusBool := false var errorEncounteredMutex sync.Mutex var errorEncountered error setErrorEncounteredFunction := func(inputError error){ errorEncounteredMutex.Lock() errorEncountered = inputError errorEncounteredMutex.Unlock() generateHashesStatusBoolMutex.Lock() generateHashesStatusBool = false generateHashesStatusBoolMutex.Unlock() } var identityHashGenerationWaitgroup sync.WaitGroup generateIdentityHashesFunction := func(){ subCounter := int64(0) for{ _, newSeedPhraseHash, err := seedPhrase.GetNewSeedPhraseFromWordList(currentLanguageWordList) if (err != nil) { setErrorEncounteredFunction(err) break } currentIdentityHashPrefix, err := identity.GetIdentityHash16CharacterPrefixFromSeedPhraseHash(newSeedPhraseHash) if (err != nil) { setErrorEncounteredFunction(err) break } // We have this check because we want to simulate how long it would take to check for a prefix // The majority of the time is spent performing the hashing and ed25519 operations strings.HasPrefix(currentIdentityHashPrefix, "seekia") subCounter += 1 generateHashesStatusBoolMutex.RLock() generatingStatus := generateHashesStatusBool generateHashesStatusBoolMutex.RUnlock() if (generatingStatus == false){ counterMutex.Lock() counter += subCounter counterMutex.Unlock() break } } identityHashGenerationWaitgroup.Done() } generateHashesStatusBool = true identityHashGenerationWaitgroup.Add(numberOfGoroutines) for i:=0; i < numberOfGoroutines; i++{ go generateIdentityHashesFunction() } // We run all goroutines for 1/2 of a second time.Sleep(time.Second/2) generateHashesStatusBoolMutex.Lock() generateHashesStatusBool = false generateHashesStatusBoolMutex.Unlock() identityHashGenerationWaitgroup.Wait() if (errorEncountered != nil){ return 0, 0, errorEncountered } currentGoroutinesNumberOfHashesPerSecond := counter*2 if (currentGoroutinesNumberOfHashesPerSecond > optimalNumberOfGoroutinesHashesPerSecond){ optimalNumberOfGoroutines = numberOfGoroutines optimalNumberOfGoroutinesHashesPerSecond = currentGoroutinesNumberOfHashesPerSecond } } return optimalNumberOfGoroutinesHashesPerSecond, optimalNumberOfGoroutines, nil } optimalNumberOfGoroutinesHashesPerSecond, optimalNumberOfGoroutines, err := performBenchmark() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } setCreateCustomIdentityHashPage(window, identityType, true, optimalNumberOfGoroutinesHashesPerSecond, optimalNumberOfGoroutines, previousPage, submitPage) return } currentPage := func(){ setCreateCustomIdentityHashPage(window, identityType, true, computerHashesPerSecond, optimalNumberOfGoroutines, previousPage, submitPage) } title := getPageTitleCentered("Create Custom Identity Hash") backButton := getBackButtonCentered(previousPage) description := getLabelCentered("Generate an identity hash with a custom prefix.") description2 := getLabelCentered("The longer the prefix, the more difficult it will be to generate.") enterDesiredPrefixText := getBoldLabelCentered(" Enter desired prefix: ") customPrefixEntry := widget.NewEntry() customPrefixEntry.SetPlaceHolder(translate("Enter desired prefix")) estimatedTimeLabelBinding := binding.NewString() estimatedTimeUnitsBinding := binding.NewString() estimatedTimeLabel := widget.NewLabelWithData(estimatedTimeLabelBinding) estimatedTimeLabel.TextStyle = getFyneTextStyle_Bold() estimatedTimeLabelCentered := getWidgetCentered(estimatedTimeLabel) estimatedTimeUnitsText := getWidgetCentered(widget.NewLabelWithData(estimatedTimeUnitsBinding)) customPrefixEntryOnChangedFunction := func(newPrefix string){ if (newPrefix == ""){ err := estimatedTimeLabelBinding.Set("") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } err = estimatedTimeUnitsBinding.Set("") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } return } prefixLength := len(newPrefix) if (prefixLength >= 13){ err := estimatedTimeLabelBinding.Set("Prefix is too long.") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } err = estimatedTimeUnitsBinding.Set("") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } return } isBase32, invalidCharacter := encoding.VerifyStringContainsOnlyBase32Charset(newPrefix) if (isBase32 == false){ err := estimatedTimeLabelBinding.Set("Invalid character detected: " + invalidCharacter) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } err = estimatedTimeUnitsBinding.Set(translate("Allowed characters") + ": abcdefghijklmnopqrstuvwxyz234567") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } return } numberOfCharacters := len(newPrefix) numberOfBits := float64(numberOfCharacters * 5) numberOfRequiredHashes := math.Pow(2, numberOfBits) estimatedTimeToGenerateInSeconds := numberOfRequiredHashes / float64(computerHashesPerSecond) estimatedTimeUnitsTranslated, err := helpers.ConvertUnixTimeDurationToUnitsTimeTranslated(int64(estimatedTimeToGenerateInSeconds), false) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } err = estimatedTimeLabelBinding.Set("Estimated time to generate:") if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } err = estimatedTimeUnitsBinding.Set(estimatedTimeUnitsTranslated) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } } customPrefixEntry.OnChanged = customPrefixEntryOnChangedFunction startGeneratingHashesButton := getWidgetCentered(widget.NewButtonWithIcon("Start Generating", theme.ConfirmIcon(), func(){ prefix := customPrefixEntry.Text if (prefix == "") { dialogTitle := translate("No Prefix Provided") dialogMessage := widget.NewLabel("You must enter a prefix to generate.") dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } prefixLength := len(prefix) if (prefixLength >= 13){ dialogTitle := translate("Prefix Is Too Long") dialogMessage := widget.NewLabel("Prefix is too long.") dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } isValid, offendingCharacter := encoding.VerifyStringContainsOnlyBase32Charset(prefix) if (isValid == false){ dialogTitle := translate("Prefix Not Allowed") dialogMessage := widget.NewLabel("Prefix contains unallowed character: " + offendingCharacter) dialogContent := container.NewVBox(dialogMessage) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return } estimatedTimeUnitsTranslated, err := estimatedTimeUnitsBinding.Get() if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } emptyList := make([]string, 0) setRunCustomIdentityHashGenerationPage(window, identityType, prefix, estimatedTimeUnitsTranslated, optimalNumberOfGoroutines, computerHashesPerSecond, emptyList, currentPage, submitPage) })) customPrefixEntrySection := getContainerCentered(container.NewGridWithColumns(1, enterDesiredPrefixText, customPrefixEntry, startGeneratingHashesButton)) page := container.NewVBox(title, backButton, widget.NewSeparator(), description, description2, widget.NewSeparator(), customPrefixEntrySection, widget.NewSeparator(), estimatedTimeLabelCentered, estimatedTimeUnitsText) setPageContent(page, window) } //TODO: Update the estimated time every ~10 seconds func setRunCustomIdentityHashGenerationPage(window fyne.Window, identityType string, desiredPrefix string, estimatedTimeRequired string, numberOfGoroutines int, initialHashesPerSecond int64, seedPhrasesFoundList []string, previousPage func(), submitPage func(string, func()) ){ currentPage := func(){setRunCustomIdentityHashGenerationPage(window, identityType, desiredPrefix, estimatedTimeRequired, numberOfGoroutines, initialHashesPerSecond, seedPhrasesFoundList, previousPage, submitPage)} appMemory.SetMemoryEntry("CurrentViewedPage", "RunCustomIdentityHashGeneration") title := getPageTitleCentered("Generating Custom Identity Hash") // We use this bool to keep track of the status of the identity hash generation goroutines var generateHashesStatusBoolMutex sync.RWMutex generateHashesStatusBool := false setGenerateHashesStatusBool := func(newStatus bool){ generateHashesStatusBoolMutex.Lock() generateHashesStatusBool = newStatus generateHashesStatusBoolMutex.Unlock() } getGenerateHashesStatusBool := func()bool{ generateHashesStatusBoolMutex.RLock() currentStatus := generateHashesStatusBool generateHashesStatusBoolMutex.RUnlock() return currentStatus } // This waitgroup is used to manage the identity hash generation goroutines var generateHashesWaitgroup sync.WaitGroup numberOfFoundSeedPhrases := len(seedPhrasesFoundList) backButtonFunction := func(){ // We stop generating hashes setGenerateHashesStatusBool(false) generateHashesWaitgroup.Wait() if (numberOfFoundSeedPhrases == 0){ previousPage() return } confirmDialogCallbackFunction := func(response bool){ if (response == true){ previousPage() return } if (numberOfFoundSeedPhrases < 30){ // Not enough hashes found yet // We restart hash generation currentPage() } } dialogTitle := translate("Go Back?") dialogMessageA := getBoldLabelCentered("Confirm to go back?") dialogMessageB := getLabelCentered("You will lose all of your generated identity hashes.") dialogMessageC := getLabelCentered("Write down each identity hash's seed phrase to retain them.") dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC) dialog.ShowCustomConfirm(dialogTitle, translate("Go Back"), translate("Cancel"), dialogContent, confirmDialogCallbackFunction, window) } backButton := getBackButtonCentered(backButtonFunction) getFoundHashesGrid := func()(*fyne.Container, error){ if (numberOfFoundSeedPhrases == 0){ emptyContainer := container.NewVBox() return emptyContainer, nil } identityHashesColumn := container.NewVBox(widget.NewSeparator()) selectButtonsColumn := container.NewVBox(widget.NewSeparator()) for _, currentSeedPhrase := range seedPhrasesFoundList{ currentSeedPhraseHash, err := seedPhrase.ConvertSeedPhraseToSeedPhraseHash(currentSeedPhrase) if (err != nil){ return nil, err } currentIdentityHash, err := identity.GetIdentityHashFromSeedPhraseHash(currentSeedPhraseHash, identityType) if (err != nil){ return nil, err } currentIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(currentIdentityHash) if (err != nil){ return nil, err } currentHashLabel := getBoldLabelCentered(currentIdentityHashString) currentHashSelectButton := widget.NewButtonWithIcon("Select", theme.ConfirmIcon(), func(){ setGenerateHashesStatusBool(false) generateHashesWaitgroup.Wait() submitPage(currentSeedPhrase, currentPage) }) identityHashesColumn.Add(currentHashLabel) selectButtonsColumn.Add(currentHashSelectButton) identityHashesColumn.Add(widget.NewSeparator()) selectButtonsColumn.Add(widget.NewSeparator()) } foundHashesGrid := container.NewHBox(layout.NewSpacer(), identityHashesColumn, selectButtonsColumn, layout.NewSpacer()) return foundHashesGrid, nil } foundHashesGrid, err := getFoundHashesGrid() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } if (numberOfFoundSeedPhrases >= 30){ // We are done generating identity hashes doneDescription1 := getLabelCentered("Hash generation is complete.") doneDescription2 := getBoldLabelCentered("Found 30 identity hashes.") retryButton := getWidgetCentered(widget.NewButtonWithIcon("Retry", theme.ViewRefreshIcon(), func(){ emptyList := make([]string, 0) setRunCustomIdentityHashGenerationPage(window, identityType, desiredPrefix, estimatedTimeRequired, numberOfGoroutines, initialHashesPerSecond, emptyList, previousPage, submitPage) })) page := container.NewVBox(title, backButton, widget.NewSeparator(), doneDescription1, doneDescription2, widget.NewSeparator(), foundHashesGrid, retryButton) setPageContent(page, window) return } getGenerationStatusString := func()string{ if (numberOfFoundSeedPhrases == 0){ return "Generating identity hash." } if (numberOfFoundSeedPhrases == 1){ return "Found 1 identity hash." } numberOfFoundSeedPhrasesString := helpers.ConvertIntToString(numberOfFoundSeedPhrases) return "Found " + numberOfFoundSeedPhrasesString + " identity hashes." } generationStatusString := getGenerationStatusString() generationStatusWithAnimationBinding := binding.NewString() generationStatusLabel := widget.NewLabelWithData(generationStatusWithAnimationBinding) generationStatusLabel.TextStyle = getFyneTextStyle_Bold() generationStatusLabelCentered := getWidgetCentered(generationStatusLabel) timeElapsedStringBinding := binding.NewString() timeElapsedLabel := getWidgetCentered(widget.NewLabelWithData(timeElapsedStringBinding)) estimatedTimeRequiredLabelBinding := binding.NewString() numberOfHashesPerSecondBinding := binding.NewString() numberOfHashesPerSecondLabel := getWidgetCentered(widget.NewLabelWithData(numberOfHashesPerSecondBinding)) initializeBindingsFunction := func()error{ err = estimatedTimeRequiredLabelBinding.Set("Estimated time required: " + estimatedTimeRequired) if (err != nil) { return err } err = timeElapsedStringBinding.Set("Time elapsed: 0 Seconds") if (err != nil) { return err } hashesPerSecondString, err := helpers.ConvertFloat64ToRoundedStringWithTranslatedUnits(float64(initialHashesPerSecond)) if (err != nil) { return err } err = numberOfHashesPerSecondBinding.Set("Hashes per second: " + hashesPerSecondString) if (err != nil) { return err } err = generationStatusWithAnimationBinding.Set(generationStatusString + "... ") if (err != nil) { return err } return nil } err = initializeBindingsFunction() if (err != nil){ setErrorEncounteredPage(window, err, previousPage) return } estimatedTimeRequiredLabel := widget.NewLabelWithData(estimatedTimeRequiredLabelBinding) estimatedTimeHelpDialogButton := widget.NewButtonWithIcon("", theme.QuestionIcon(), func(){ dialogTitle := translate("Estimated Time To Generate") dialogMessageA := getLabelCentered("To generate a custom identity hash, many random identity hashes are created.") dialogMessageB := getLabelCentered("Finding your desired prefix requires luck.") dialogMessageC := getLabelCentered("The time to generate may vary significantly from the estimated time.") dialogContent := container.NewVBox(dialogMessageA, dialogMessageB, dialogMessageC) dialog.ShowCustom(dialogTitle, translate("Close"), dialogContent, window) return }) estimatedTimeRequiredRow := container.NewHBox(layout.NewSpacer(), estimatedTimeRequiredLabel, estimatedTimeHelpDialogButton, layout.NewSpacer()) //TODO: Retrieve language from user settings currentLanguage := "English" currentLanguageWordList, err := wordLists.GetWordListFromLanguage(currentLanguage) if (err != nil) { setErrorEncounteredPage(window, err, previousPage) return } page := container.NewVBox(title, backButton, widget.NewSeparator(), timeElapsedLabel, widget.NewSeparator(), estimatedTimeRequiredRow, widget.NewSeparator(), numberOfHashesPerSecondLabel, widget.NewSeparator(), generationStatusLabelCentered, foundHashesGrid) setPageContent(page, window) // Now we start hash generation startHashGenerationFunction := func(){ // This variable stores the current number of hashes per second that are being generated var currentHashesPerSecondMutex sync.RWMutex currentHashesPerSecond := initialHashesPerSecond // We use this mutex when adding new seed phrases to the list var seedPhrasesFoundListMutex sync.Mutex // We use this error to keep track of any errors var encounteredErrorMutex sync.RWMutex var encounteredError error setErrorEncountered := func(newError error){ encounteredErrorMutex.Lock() encounteredError = newError encounteredErrorMutex.Unlock() } generateIdentityHashesFunction := func(){ // We use this counter to count how many hashes we have generated // We add this periodically to the hashesPerSecond counter // We do this so we can avoid having to Rlock a mutex for each increment subcounter := int64(0) for{ generatingStatus := getGenerateHashesStatusBool() if (generatingStatus == false){ generateHashesWaitgroup.Done() return } newSeedPhrase, newSeedPhraseHash, err := seedPhrase.GetNewSeedPhraseFromWordList(currentLanguageWordList) if (err != nil) { setErrorEncountered(err) break } newIdentityHashPrefix, err := identity.GetIdentityHash16CharacterPrefixFromSeedPhraseHash(newSeedPhraseHash) if (err != nil) { setErrorEncountered(err) break } subcounter += 1 if (subcounter >= 2000){ currentHashesPerSecondMutex.Lock() currentHashesPerSecond += subcounter currentHashesPerSecondMutex.Unlock() subcounter = 0 } hasPrefix := strings.HasPrefix(newIdentityHashPrefix, desiredPrefix) if (hasPrefix == false){ continue } // We found a valid identity hash seedPhrasesFoundListMutex.Lock() if (len(seedPhrasesFoundList) >= 30){ // We have found enough identity hashes seedPhrasesFoundListMutex.Unlock() generateHashesWaitgroup.Done() return } seedPhrasesFoundList = append(seedPhrasesFoundList, newSeedPhrase) seedPhrasesFoundListMutex.Unlock() // We keep searching } // This should only be reached if an error is encountered generateHashesWaitgroup.Done() } setGenerateHashesStatusBool(true) generateHashesWaitgroup.Add(numberOfGoroutines) for i:=0; i < numberOfGoroutines; i++{ go generateIdentityHashesFunction() } numberOfSecondsElapsed := 0 // This variable holds the status of the trailing dots after the generation progress text progressAnimationString := "... " for{ currentHashesPerSecondMutex.Lock() // We reset the counter currentHashesPerSecond = 0 currentHashesPerSecondMutex.Unlock() time.Sleep(time.Second) generatingStatus := getGenerateHashesStatusBool() if (generatingStatus == false){ return } encounteredErrorMutex.RLock() currentEncounteredError := encounteredError encounteredErrorMutex.RUnlock() if (currentEncounteredError != nil){ // One of the goroutines experienced an error break } numberOfSecondsElapsed += 1 timeElapsedUnitsString, err := helpers.ConvertUnixTimeDurationToUnitsTimeTranslated(int64(numberOfSecondsElapsed), true) if (err != nil) { setErrorEncountered(err) break } err = timeElapsedStringBinding.Set("Time elapsed: " + timeElapsedUnitsString) if (err != nil) { setErrorEncountered(err) break } if (progressAnimationString == ". "){ progressAnimationString = ".. " } else if (progressAnimationString == ".. " ){ progressAnimationString = "... " } else if (progressAnimationString == "... " ){ progressAnimationString = "...." } else if (progressAnimationString == "...." ){ progressAnimationString = ". " } err = generationStatusWithAnimationBinding.Set(generationStatusString + progressAnimationString) if (err != nil) { setErrorEncountered(err) break } exists, currentPage := appMemory.GetMemoryEntry("CurrentViewedPage") if (exists == false || currentPage != "RunCustomIdentityHashGeneration"){ setGenerateHashesStatusBool(false) return } seedPhrasesFoundListMutex.Lock() newSeedPhrasesListLength := len(seedPhrasesFoundList) seedPhrasesFoundListMutex.Unlock() if (newSeedPhrasesListLength > numberOfFoundSeedPhrases){ // At least 1 new identity hash has been found. // We will refresh the page. setGenerateHashesStatusBool(false) // We wait for all loops to exit generateHashesWaitgroup.Wait() setRunCustomIdentityHashGenerationPage(window, identityType, desiredPrefix, estimatedTimeRequired, numberOfGoroutines, currentHashesPerSecond, seedPhrasesFoundList, previousPage, submitPage) return } currentHashesPerSecondMutex.Lock() currentHashesPerSecondCopy := currentHashesPerSecond currentHashesPerSecondMutex.Unlock() currentHashesPerSecondString, err := helpers.ConvertFloat64ToRoundedStringWithTranslatedUnits(float64(currentHashesPerSecondCopy)) if (err != nil) { setErrorEncountered(err) break } err = numberOfHashesPerSecondBinding.Set("Hashes per second: " + currentHashesPerSecondString) if (err != nil) { setErrorEncountered(err) break } } // This should only be reached if an error is encountered setGenerateHashesStatusBool(false) // We wait for goroutines to exit generateHashesWaitgroup.Wait() setErrorEncounteredPage(window, errors.New("Something went wrong during hash generation: " + encounteredError.Error()), previousPage) } go startHashGenerationFunction() }