view: nsec, ncryptsec, event signing things.

This commit is contained in:
fiatjaf
2025-11-27 06:40:23 -03:00
parent 2b189756d1
commit eb6fdfdd39
5 changed files with 100 additions and 19 deletions

1
go.mod
View File

@@ -20,7 +20,6 @@ require (
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-tty v0.0.7 github.com/mattn/go-tty v0.0.7
github.com/mdp/qrterminal/v3 v3.2.1 github.com/mdp/qrterminal/v3 v3.2.1
github.com/mitchellh/go-homedir v1.1.0
github.com/puzpuzpuz/xsync/v3 v3.5.1 github.com/puzpuzpuz/xsync/v3 v3.5.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d

2
go.sum
View File

@@ -163,8 +163,6 @@ github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFe
github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU= github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

View File

@@ -48,19 +48,40 @@ func updateEvent() {
} }
} }
final := nostr.Event{ result := nostr.Event{
Kind: kind, Kind: kind,
Content: event.contentEdit.ToPlainText(), Content: event.contentEdit.ToPlainText(),
CreatedAt: nostr.Timestamp(event.createdAtEdit.DateTime().ToMSecsSinceEpoch() / 1000), CreatedAt: nostr.Timestamp(event.createdAtEdit.DateTime().ToMSecsSinceEpoch() / 1000),
Tags: tags, Tags: tags,
} }
if currentKeyer != nil { finalize := func() {
currentKeyer.SignEvent(context.Background(), &final) jsonBytes, _ := json.MarshalIndent(result, "", " ")
event.outputEdit.SetPlainText(string(jsonBytes))
} }
jsonBytes, _ := json.MarshalIndent(event, "", " ") if currentKeyer != nil {
event.outputEdit.SetPlainText(string(jsonBytes)) signAndFinalize := func() {
if currentKeyer != nil {
if err := currentKeyer.SignEvent(context.Background(), &result); err == nil {
finalize()
} else {
statusLabel.SetText("failed to sign: " + err.Error())
}
}
}
if currentSec == [32]byte{} {
// empty key, we must have a bunker
debounced.Call(signAndFinalize)
} else {
// we have a key, can sign immediately
signAndFinalize()
return
}
}
finalize()
} }
func setupEventTab() *widgets.QWidget { func setupEventTab() *widgets.QWidget {

View File

@@ -27,7 +27,8 @@ func handleSecretKeyOrBunker(sec string) (nostr.SecretKey, nostr.Keyer, error) {
} }
if prefix, ski, err := nip19.Decode(sec); err == nil && prefix == "nsec" { if prefix, ski, err := nip19.Decode(sec); err == nil && prefix == "nsec" {
return ski.(nostr.SecretKey), nil, nil sk := ski.(nostr.SecretKey)
return sk, keyer.NewPlainKeySigner(sk), nil
} }
sk, err := nostr.SecretKeyFromHex(sec) sk, err := nostr.SecretKeyFromHex(sec)

View File

@@ -1,9 +1,15 @@
package main package main
import ( import (
"encoding/hex"
"os" "os"
"strings"
"time"
"fiatjaf.com/lib/debouncer"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"fiatjaf.com/nostr/nip19"
"fiatjaf.com/nostr/nip49"
"github.com/therecipe/qt/widgets" "github.com/therecipe/qt/widgets"
) )
@@ -11,6 +17,7 @@ var (
currentSec nostr.SecretKey currentSec nostr.SecretKey
currentKeyer nostr.Keyer currentKeyer nostr.Keyer
statusLabel *widgets.QLabel statusLabel *widgets.QLabel
debounced = debouncer.New(800 * time.Millisecond)
) )
func main() { func main() {
@@ -29,33 +36,87 @@ func main() {
// private key input // private key input
secLabel := widgets.NewQLabel2("private key (hex or nsec):", nil, 0) secLabel := widgets.NewQLabel2("private key (hex or nsec):", nil, 0)
mainLayout.AddWidget(secLabel, 0, 0) mainLayout.AddWidget(secLabel, 0, 0)
secEdit := widgets.NewQLineEdit(nil)
mainLayout.AddWidget(secEdit, 0, 0)
secEdit.ConnectTextChanged(func(text string) { secHBox := widgets.NewQHBoxLayout()
mainLayout.AddLayout(secHBox, 0)
secEdit := widgets.NewQLineEdit(nil)
secHBox.AddWidget(secEdit, 0, 0)
generateButton := widgets.NewQPushButton2("generate", nil)
secHBox.AddWidget(generateButton, 0, 0)
// password input
passwordHBox := widgets.NewQHBoxLayout()
passwordWidget := widgets.NewQWidget(nil, 0)
passwordWidget.SetLayout(passwordHBox)
passwordWidget.SetVisible(false)
mainLayout.AddWidget(passwordWidget, 0, 0)
passwordLabel := widgets.NewQLabel2("password:", nil, 0)
passwordHBox.AddWidget(passwordLabel, 0, 0)
secPasswordEdit := widgets.NewQLineEdit(nil)
secPasswordEdit.SetEchoMode(widgets.QLineEdit__Password)
passwordHBox.AddWidget(secPasswordEdit, 0, 0)
keyChanged := func(text string) {
text = strings.TrimSpace(text)
var sk nostr.SecretKey
var keyer nostr.Keyer
var err error
if text == "" { if text == "" {
currentSec = nostr.SecretKey{} passwordWidget.SetVisible(false)
currentKeyer = nil goto empty
statusLabel.SetText("")
return
} }
sk, bunker, err := handleSecretKeyOrBunker(text)
if strings.HasPrefix(text, "ncryptsec1") {
passwordWidget.SetVisible(true)
password := secPasswordEdit.Text()
if password != "" {
sk, err = nip49.Decrypt(text, password)
if err != nil {
statusLabel.SetText("decryption failed: " + err.Error())
goto empty
}
text = hex.EncodeToString(sk[:])
} else {
goto empty
}
} else {
passwordWidget.SetVisible(false)
}
sk, keyer, err = handleSecretKeyOrBunker(text)
if err != nil { if err != nil {
statusLabel.SetText(err.Error()) statusLabel.SetText(err.Error())
currentSec = nostr.SecretKey{} currentSec = nostr.SecretKey{}
currentKeyer = nil currentKeyer = nil
return return
} }
currentSec = sk currentSec = sk
currentKeyer = bunker currentKeyer = keyer
statusLabel.SetText("") statusLabel.SetText("")
updateEvent() updateEvent()
return
empty:
currentSec = nostr.SecretKey{}
currentKeyer = nil
statusLabel.SetText("")
return
}
secEdit.ConnectTextChanged(keyChanged)
secPasswordEdit.ConnectTextChanged(keyChanged)
generateButton.ConnectClicked(func(bool) {
sk := nostr.Generate()
nsec := nip19.EncodeNsec(sk)
secEdit.SetText(nsec)
keyChanged(nsec)
}) })
tabWidget := widgets.NewQTabWidget(nil) tabWidget := widgets.NewQTabWidget(nil)
eventTab := setupEventTab() eventTab := setupEventTab()
reqTab := widgets.NewQWidget(nil, 0) reqTab := setupReqTab()
tabWidget.AddTab(eventTab, "event") tabWidget.AddTab(eventTab, "event")
tabWidget.AddTab(reqTab, "req") tabWidget.AddTab(reqTab, "req")
@@ -67,6 +128,7 @@ func main() {
// initial render // initial render
updateEvent() updateEvent()
updateReq()
window.Show() window.Show()
app.Exec() app.Exec()