nak event --pow

closes https://github.com/fiatjaf/nak/issues/29
This commit is contained in:
fiatjaf 2024-08-20 10:29:18 -03:00
parent 85e9610265
commit 9d43e66fac
3 changed files with 90 additions and 18 deletions

View File

@ -194,6 +194,12 @@ listening at [wss://relay.damus.io wss://nos.lol wss://relay.nsecbunker.com]:
• events stored: 4, subscriptions opened: 1 • events stored: 4, subscriptions opened: 1
``` ```
### make an event with a PoW target
```shell
~> nak event -c 'hello getwired.app and labour.fiatjaf.com' --pow 24
{"kind":1,"id":"0000009dcc7c62056eafdb41fac817379ec2becf0ce27c5fbe98d0735d968147","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1724160828,"tags":[["nonce","515504","24"]],"content":"hello getwired.app and labour.fiatjaf.com","sig":"7edb988065ccc12779fe99270945b212f3723838f315d76d5e90e9ffa27198f13fa556614295f518d968d55bab81878167d4162b3a7cf81a6b423c6761bd504c"}
```
## contributing to this repository ## contributing to this repository
Use NIP-34 to send your patches to `naddr1qqpkucttqy28wumn8ghj7un9d3shjtnwdaehgu3wvfnsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7q3q80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsxpqqqpmej2wctpn`. Use NIP-34 to send your patches to `naddr1qqpkucttqy28wumn8ghj7un9d3shjtnwdaehgu3wvfnsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7q3q80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsxpqqqpmej2wctpn`.

View File

@ -11,11 +11,16 @@ import (
"github.com/fiatjaf/cli/v3" "github.com/fiatjaf/cli/v3"
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip13"
"github.com/nbd-wtf/go-nostr/nip19" "github.com/nbd-wtf/go-nostr/nip19"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
const CATEGORY_EVENT_FIELDS = "EVENT FIELDS" const (
CATEGORY_EVENT_FIELDS = "EVENT FIELDS"
CATEGORY_SIGNER = "SIGNER OPTIONS"
CATEGORY_EXTRAS = "EXTRAS"
)
var event = &cli.Command{ var event = &cli.Command{
Name: "event", Name: "event",
@ -38,19 +43,23 @@ example:
Usage: "secret key to sign the event, as nsec, ncryptsec or hex", Usage: "secret key to sign the event, as nsec, ncryptsec or hex",
DefaultText: "the key '1'", DefaultText: "the key '1'",
Value: "0000000000000000000000000000000000000000000000000000000000000001", Value: "0000000000000000000000000000000000000000000000000000000000000001",
Category: CATEGORY_SIGNER,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "prompt-sec", Name: "prompt-sec",
Usage: "prompt the user to paste a hex or nsec with which to sign the event", Usage: "prompt the user to paste a hex or nsec with which to sign the event",
Category: CATEGORY_SIGNER,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "connect", Name: "connect",
Usage: "sign event using NIP-46, expects a bunker://... URL", Usage: "sign event using NIP-46, expects a bunker://... URL",
Category: CATEGORY_SIGNER,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "connect-as", Name: "connect-as",
Usage: "private key to when communicating with the bunker given on --connect", Usage: "private key to when communicating with the bunker given on --connect",
DefaultText: "a random key", DefaultText: "a random key",
Category: CATEGORY_SIGNER,
}, },
// ~ these args are only for the convoluted musig2 signing process // ~ these args are only for the convoluted musig2 signing process
// they will be generally copy-shared-pasted across some manual coordination method between participants // they will be generally copy-shared-pasted across some manual coordination method between participants
@ -59,6 +68,7 @@ example:
Usage: "number of signers to use for musig2", Usage: "number of signers to use for musig2",
Value: 1, Value: 1,
DefaultText: "1 -- i.e. do not use musig2 at all", DefaultText: "1 -- i.e. do not use musig2 at all",
Category: CATEGORY_SIGNER,
}, },
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "musig-pubkey", Name: "musig-pubkey",
@ -77,17 +87,25 @@ example:
Hidden: true, Hidden: true,
}, },
// ~~~ // ~~~
&cli.UintFlag{
Name: "pow",
Usage: "NIP-13 difficulty to target when doing hash work on the event id",
Category: CATEGORY_EXTRAS,
},
&cli.BoolFlag{ &cli.BoolFlag{
Name: "envelope", Name: "envelope",
Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay", Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay",
Category: CATEGORY_EXTRAS,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "auth", Name: "auth",
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again", Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
Category: CATEGORY_EXTRAS,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "nevent", Name: "nevent",
Usage: "print the nevent code (to stderr) after the event is published", Usage: "print the nevent code (to stderr) after the event is published",
Category: CATEGORY_EXTRAS,
}, },
&cli.UintFlag{ &cli.UintFlag{
Name: "kind", Name: "kind",
@ -193,8 +211,9 @@ example:
mustRehashAndResign = true mustRehashAndResign = true
} }
tags := make(nostr.Tags, 0, 5) tagFlags := c.StringSlice("tag")
for _, tagFlag := range c.StringSlice("tag") { tags := make(nostr.Tags, 0, len(tagFlags)+2)
for _, tagFlag := range tagFlags {
// tags are in the format key=value // tags are in the format key=value
tagName, tagValue, found := strings.Cut(tagFlag, "=") tagName, tagValue, found := strings.Cut(tagFlag, "=")
tag := []string{tagName} tag := []string{tagName}
@ -203,20 +222,17 @@ example:
tagValues := strings.Split(tagValue, ";") tagValues := strings.Split(tagValue, ";")
tag = append(tag, tagValues...) tag = append(tag, tagValues...)
} }
tags = tags.AppendUnique(tag) tags = append(tags, tag)
} }
for _, etag := range c.StringSlice("e") { for _, etag := range c.StringSlice("e") {
tags = tags.AppendUnique([]string{"e", etag}) tags = tags.AppendUnique([]string{"e", etag})
mustRehashAndResign = true
} }
for _, ptag := range c.StringSlice("p") { for _, ptag := range c.StringSlice("p") {
tags = tags.AppendUnique([]string{"p", ptag}) tags = tags.AppendUnique([]string{"p", ptag})
mustRehashAndResign = true
} }
for _, dtag := range c.StringSlice("d") { for _, dtag := range c.StringSlice("d") {
tags = tags.AppendUnique([]string{"d", dtag}) tags = tags.AppendUnique([]string{"d", dtag})
mustRehashAndResign = true
} }
if len(tags) > 0 { if len(tags) > 0 {
for _, tag := range tags { for _, tag := range tags {
@ -233,6 +249,31 @@ example:
mustRehashAndResign = true mustRehashAndResign = true
} }
if difficulty := c.Uint("pow"); difficulty > 0 {
// before doing pow we need the pubkey
if bunker != nil {
evt.PubKey, err = bunker.GetPublicKey(ctx)
if err != nil {
return fmt.Errorf("can't pow: failed to get public key from bunker: %w", err)
}
} else if numSigners := c.Uint("musig"); numSigners > 1 && sec != "" {
pubkeys := c.StringSlice("musig-pubkey")
if int(numSigners) != len(pubkeys) {
return fmt.Errorf("when doing a pow with musig we must know all signer pubkeys upfront")
}
evt.PubKey, err = getMusigAggregatedKey(ctx, pubkeys)
if err != nil {
return err
}
} else {
evt.PubKey, _ = nostr.GetPublicKey(sec)
}
// try to generate work with this difficulty -- essentially forever
nip13.Generate(&evt, int(difficulty), time.Hour*24*365)
mustRehashAndResign = true
}
if evt.Sig == "" || mustRehashAndResign { if evt.Sig == "" || mustRehashAndResign {
if bunker != nil { if bunker != nil {
if err := bunker.SignEvent(ctx, &evt); err != nil { if err := bunker.SignEvent(ctx, &evt); err != nil {

View File

@ -15,6 +15,31 @@ import (
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
) )
func getMusigAggregatedKey(_ context.Context, keys []string) (string, error) {
knownSigners := make([]*btcec.PublicKey, len(keys))
for i, spk := range keys {
bpk, err := hex.DecodeString(spk)
if err != nil {
return "", fmt.Errorf("'%s' is invalid hex: %w", spk, err)
}
if len(bpk) == 32 {
return "", fmt.Errorf("'%s' is missing the leading parity byte", spk)
}
pk, err := btcec.ParsePubKey(bpk)
if err != nil {
return "", fmt.Errorf("'%s' is not a valid pubkey: %w", spk, err)
}
knownSigners[i] = pk
}
aggpk, _, _, err := musig2.AggregateKeys(knownSigners, true)
if err != nil {
return "", fmt.Errorf("aggregation failed: %w", err)
}
return hex.EncodeToString(aggpk.FinalKey.SerializeCompressed()[1:]), nil
}
func performMusig( func performMusig(
_ context.Context, _ context.Context,
sec string, sec string,