952 lines
32 KiB
Go
952 lines
32 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, 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()
|
|
}
|
|
|
|
|
|
|
|
|
|
|