922 lines
31 KiB
Go
922 lines
31 KiB
Go
|
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, 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)
|
||
|
}
|
||
|
|
||
|
|
||
|
// submitPage function = func(newSeedPhrase string, previousPage func())
|
||
|
|
||
|
func setCreateCustomIdentityHashPage(window fyne.Window, identityType string, previousPage func(), submitPage func(string, func()) ){
|
||
|
|
||
|
setLoadingScreen(window, "Create Custom Identity Hash", "Loading...")
|
||
|
|
||
|
currentPage := func(){ setCreateCustomIdentityHashPage(window, identityType, previousPage, submitPage) }
|
||
|
|
||
|
// Output:
|
||
|
// -int64: Hashes per second
|
||
|
// -error
|
||
|
getIdentityHashGenerationSpeed := func()(int64, error){
|
||
|
|
||
|
//TODO: Fix to retrieve language from settings
|
||
|
currentLanguage := "English"
|
||
|
currentLanguageWordList, err := wordLists.GetWordListFromLanguage(currentLanguage)
|
||
|
if (err != nil) { return 0, err }
|
||
|
|
||
|
// 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(2)
|
||
|
|
||
|
go generateIdentityHashesFunction()
|
||
|
go generateIdentityHashesFunction()
|
||
|
|
||
|
time.Sleep(time.Second)
|
||
|
|
||
|
generateHashesStatusBoolMutex.Lock()
|
||
|
generateHashesStatusBool = false
|
||
|
generateHashesStatusBoolMutex.Unlock()
|
||
|
|
||
|
identityHashGenerationWaitgroup.Wait()
|
||
|
|
||
|
if (errorEncountered != nil){
|
||
|
return 0, errorEncountered
|
||
|
}
|
||
|
|
||
|
return counter, nil
|
||
|
}
|
||
|
|
||
|
hashGenerationSpeed, err := getIdentityHashGenerationSpeed()
|
||
|
if (err != nil){
|
||
|
setErrorEncounteredPage(window, err, previousPage)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
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(hashGenerationSpeed)
|
||
|
|
||
|
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, hashGenerationSpeed, 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
|
||
|
//TODO: Figure out optimal number of goroutines to use for maximum speed
|
||
|
|
||
|
func setRunCustomIdentityHashGenerationPage(window fyne.Window, identityType string, desiredPrefix string, estimatedTimeRequired string, initialHashesPerSecond int64, seedPhrasesFoundList []string, previousPage func(), submitPage func(string, func()) ){
|
||
|
|
||
|
currentPage := func(){setRunCustomIdentityHashGenerationPage(window, identityType, desiredPrefix, estimatedTimeRequired, 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, 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(2)
|
||
|
|
||
|
go generateIdentityHashesFunction()
|
||
|
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, 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()
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|