seekia/gui/createIdentityGui.go

953 lines
32 KiB
Go
Raw Normal View History

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()
}