mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-08 16:48:51 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea53eca74f | ||
|
|
38ed370c59 | ||
|
|
5b04bc4859 | ||
|
|
2988c71ccb | ||
|
|
d7c0ff2bb7 | ||
|
|
43fe41df5d | ||
|
|
3215726417 | ||
|
|
a4886dc445 | ||
|
|
dae7eba8ca | ||
|
|
2b5f3355bc | ||
|
|
bd5ca27661 | ||
|
|
9d02301b2d | ||
|
|
9bbc87b27a | ||
|
|
88a07a3504 | ||
|
|
8a934cc76b | ||
|
|
e0c967efa9 | ||
|
|
36c32ae308 | ||
|
|
6d23509d8c | ||
|
|
29b6ecbafe | ||
|
|
11f37afa5b | ||
|
|
cf1694704e | ||
|
|
b3ef2c1289 | ||
|
|
cfdea699bc | ||
|
|
014c6bc11d | ||
|
|
0240866fa1 | ||
|
|
a4d9ceecfa | ||
|
|
56657d8aa9 | ||
|
|
ea7b88cfd7 | ||
|
|
2042b14578 | ||
|
|
9d43e66fac | ||
|
|
85e9610265 | ||
|
|
2edfa5cbea | ||
|
|
9690dc70cb | ||
|
|
c90e61dbec | ||
|
|
d226cd6ce4 | ||
|
|
3d78e91f62 | ||
|
|
84965f2253 | ||
|
|
928c73513c | ||
|
|
a36142604d |
7
.github/workflows/release-cli.yml
vendored
7
.github/workflows/release-cli.yml
vendored
@@ -25,10 +25,14 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
goos: [linux, freebsd, darwin, windows]
|
||||
goarch: [amd64, arm64]
|
||||
goarch: [amd64, arm64, riscv64]
|
||||
exclude:
|
||||
- goarch: arm64
|
||||
goos: windows
|
||||
- goarch: riscv64
|
||||
goos: windows
|
||||
- goarch: riscv64
|
||||
goos: darwin
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: wangyoucao577/go-release-action@v1.40
|
||||
@@ -36,6 +40,7 @@ jobs:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
ldflags: -X main.version=${{ github.ref_name }}
|
||||
overwrite: true
|
||||
md5sum: false
|
||||
sha256sum: false
|
||||
|
||||
56
README.md
56
README.md
@@ -173,6 +173,62 @@ listening at [wss://relay.damus.io wss://nos.lol wss://relay.nsecbunker.com]:
|
||||
{"kind":1,"id":"f030fccd90c783858dfcee204af94826cf0f1c85d6fc85a0087e9e5172419393","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1719677535,"tags":[["-"],["e","f59911b561c37c90b01e9e5c2557307380835c83399756f4d62d8167227e420a","wss://relay.whatever.com","root","a9e0f110f636f3191644110c19a33448daf09d7cda9708a769e91b7e91340208"],["p","a9e0f110f636f3191644110c19a33448daf09d7cda9708a769e91b7e91340208","wss://p-relay.com"]],"content":"I know the future","sig":"8b36a74e29df8bc12bed66896820da6940d4d9409721b3ed2e910c838833a178cb45fd5bb1c6eb6adc66ab2808bfac9f6644a2c55a6570bb2ad90f221c9c7551"}
|
||||
```
|
||||
|
||||
### download the latest 50000 notes from a relay, regardless of their natural query limits, by paginating requests
|
||||
```shell
|
||||
~> nak req -k 1 --limit 50000 --paginate --paginate-interval 2s nos.lol > events.jsonl
|
||||
~> wc -l events.jsonl
|
||||
50000 events.jsonl
|
||||
```
|
||||
|
||||
### run a somewhat verbose local relay for test purposes
|
||||
```shell
|
||||
~> nak serve
|
||||
> relay running at ws://localhost:10547
|
||||
got request {"kinds":[1],"authors":["79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"],"since":1724082362}
|
||||
got event {"kind":1,"id":"e3c6bf630d6deea74c0ee2f7f7ba6da55a627498a32f1e72029229bb1810bce3","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1724082366,"tags":[],"content":"two","sig":"34261cf226c3fee2df24e55a89f43f5349c98a64bce46bdc46807b0329f334cea93e9e8bc285c1259a5684cf23f5e507c8e6dad47a31a6615d706b1130d09e69"}
|
||||
got event {"kind":1,"id":"0bbb397c8f87ae557650b9d6ee847292df8e530c458ffea1b24bdcb7bed0ec5e","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1724082369,"tags":[],"content":"three","sig":"aa1cb7d5f0f03f358fc4c0a4351a4f1c66e3a7627021b618601c56ba598b825b6d95d9c8720a4c60666a7eb21e17018cf326222f9f574a9396f2f2da7f007546"}
|
||||
• events stored: 2, subscriptions opened: 1
|
||||
got event {"kind":1,"id":"029ebff759dd54dbd01b929f879fea5802de297e1c3768ca16d9b97cc8bca38f","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1724082371,"tags":[],"content":"four","sig":"9816de517d87d4c3ede57c1c50e3c237486794241afadcd891e1acbba2c5e672286090e6ad3402b047d69bae8095bc4e20e57ac70d92386dfa26db216379330f"}
|
||||
got event {"kind":1,"id":"fe6489fa6fbb925be839377b9b7049d73be755dc2bdad97ff6dd9eecbf8b3a32","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1724082383,"tags":[],"content":"five","sig":"865ce5e32eead5bdb950ac1fbc55bc92dde26818ee3136634538ec42914de179a51e672c2d4269d4362176e5e8cd5e08e69b35b91c6c2af867e129b93d607635"}
|
||||
got request {"kinds":[30818]}
|
||||
• 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"}
|
||||
```
|
||||
|
||||
### make a nostr event signed with a key given as an environment variable
|
||||
|
||||
```shell
|
||||
~> export NOSTR_SECRET_KEY=ncryptsec1qggyy9vw0nclmw8ly9caz6aa7f85a4ufhsct64uva337pulsdw00n6twa2lzhzk2znzsyu60urx9s08lx00ke6ual3lszyn5an9zarm6s70lw5lj6dv3mj3f9p4tvp0we6qyz4gp420mapfmvqheuttv
|
||||
~> nak event -c 'it supports keys as hex, nsec or ncryptsec'
|
||||
type the password to decrypt your secret key: ********
|
||||
{"kind":1,"id":"5cbf3feb9a7d99c3ee2a88693a591caca1a8348fea427b3652c27f7a8a76af48","pubkey":"b00bcab55375d8c7b731dd9841f6d805ff1cf6fdc945e7326786deb5ddac6ce4","created_at":1724247924,"tags":[],"content":"it supports keys as hex, nsec or ncryptsec","sig":"fb3fd170bc10e5042322c7a05dd4bbd8ac9947b39026b8a7afd1ee02524e8e3aa1d9554e9c7b6181ca1b45cab01cd06643bdffa5ce678b475e6b185e1c14b085"}
|
||||
```
|
||||
|
||||
### download some helpful `jq` functions for dealing with nostr events
|
||||
```shell
|
||||
~> nak req -i 412f2d3e73acc312942c055ac2a695dc60bf58ff97e06689a8a79e97796c4cdb relay.westernbtc.com | jq -r .content > ~/.jq
|
||||
```
|
||||
|
||||
### watch a NIP-53 livestream (zap.stream etc)
|
||||
```shell
|
||||
~> # this requires the jq utils from the step above
|
||||
~> mpv $(nak fetch naddr1qqjxvvm9xscnsdtx95cxvcfk956rsvtx943rje3k95mx2dp389jnwwrp8ymxgqg4waehxw309aex2mrp0yhxgctdw4eju6t09upzpn6956apxcad0mfp8grcuugdysg44eepex68h50t73zcathmfs49qvzqqqrkvu7ed38k | jq -r 'tag_value("streaming")')
|
||||
~>
|
||||
~> # or without the utils
|
||||
~> mpv $(nak fetch naddr1qqjxvvm9xscnsdtx95cxvcfk956rsvtx943rje3k95mx2dp389jnwwrp8ymxgqg4waehxw309aex2mrp0yhxgctdw4eju6t09upzpn6956apxcad0mfp8grcuugdysg44eepex68h50t73zcathmfs49qvzqqqrkvu7ed38k | jq -r '.tags | map(select(.[0] == "streaming") | .[1])[0]')
|
||||
```
|
||||
|
||||
### download a NIP-35 torrent from an `nevent`
|
||||
```shell
|
||||
~> # this requires the jq utils from two steps above
|
||||
~> aria2c $(nak fetch nevent1qqsdsg6x7uujekac4ga7k7qa9q9sx8gqj7xzjf5w9us0dm0ghvf4ugspp4mhxue69uhkummn9ekx7mq6dw9y4 | jq -r '"magnet:?xt=urn:btih:\(tag_value("x"))&dn=\(tag_value("title"))&tr=http%3A%2F%2Ftracker.loadpeers.org%3A8080%2FxvRKfvAlnfuf5EfxTT5T0KIVPtbqAHnX%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A6969%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.torrent.eu.org%3A451%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=\(tags("tracker") | map(.[1] | @uri) | join("&tr="))"')
|
||||
```
|
||||
|
||||
## contributing to this repository
|
||||
|
||||
Use NIP-34 to send your patches to `naddr1qqpkucttqy28wumn8ghj7un9d3shjtnwdaehgu3wvfnsz9nhwden5te0wfjkccte9ehx7um5wghxyctwvsq3gamnwvaz7tmjv4kxz7fwv3sk6atn9e5k7q3q80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsxpqqqpmej2wctpn`.
|
||||
|
||||
25
bunker.go
25
bunker.go
@@ -11,24 +11,24 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/nip46"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var bunker = &cli.Command{
|
||||
Name: "bunker",
|
||||
Usage: "starts a NIP-46 signer daemon with the given --sec key",
|
||||
ArgsUsage: "[relay...]",
|
||||
Description: ``,
|
||||
Name: "bunker",
|
||||
Usage: "starts a NIP-46 signer daemon with the given --sec key",
|
||||
ArgsUsage: "[relay...]",
|
||||
Description: ``,
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "sec",
|
||||
Usage: "secret key to sign the event, as hex or nsec",
|
||||
DefaultText: "the key '1'",
|
||||
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "prompt-sec",
|
||||
@@ -50,7 +50,7 @@ var bunker = &cli.Command{
|
||||
qs := url.Values{}
|
||||
relayURLs := make([]string, 0, c.Args().Len())
|
||||
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
||||
_, relays := connectToAllRelays(ctx, relayUrls, false)
|
||||
relays := connectToAllRelays(ctx, relayUrls, false)
|
||||
if len(relays) == 0 {
|
||||
log("failed to connect to any of the given relays.\n")
|
||||
os.Exit(3)
|
||||
@@ -143,9 +143,8 @@ var bunker = &cli.Command{
|
||||
printBunkerInfo()
|
||||
|
||||
// subscribe to relays
|
||||
pool := nostr.NewSimplePool(ctx)
|
||||
now := nostr.Now()
|
||||
events := pool.SubMany(ctx, relayURLs, nostr.Filters{
|
||||
events := sys.Pool.SubMany(ctx, relayURLs, nostr.Filters{
|
||||
{
|
||||
Kinds: []int{nostr.KindNostrConnect},
|
||||
Tags: nostr.TagMap{"p": []string{pubkey}},
|
||||
@@ -177,14 +176,14 @@ var bunker = &cli.Command{
|
||||
}()
|
||||
}
|
||||
|
||||
return harmless || slices.Contains(authorizedKeys, from) || slices.Contains(authorizedSecrets, secret)
|
||||
return slices.Contains(authorizedKeys, from) || slices.Contains(authorizedSecrets, secret)
|
||||
}
|
||||
|
||||
for ie := range events {
|
||||
cancelPreviousBunkerInfoPrint() // this prevents us from printing a million bunker info blocks
|
||||
|
||||
// handle the NIP-46 request event
|
||||
req, resp, eventResponse, err := signer.HandleRequest(ie.Event)
|
||||
req, resp, eventResponse, err := signer.HandleRequest(ctx, ie.Event)
|
||||
if err != nil {
|
||||
log("< failed to handle request from %s: %s\n", ie.Event.PubKey, err.Error())
|
||||
continue
|
||||
@@ -198,7 +197,7 @@ var bunker = &cli.Command{
|
||||
handlerWg.Add(len(relayURLs))
|
||||
for _, relayURL := range relayURLs {
|
||||
go func(relayURL string) {
|
||||
if relay, _ := pool.EnsureRelay(relayURL); relay != nil {
|
||||
if relay, _ := sys.Pool.EnsureRelay(relayURL); relay != nil {
|
||||
err := relay.Publish(ctx, eventResponse)
|
||||
printLock.Lock()
|
||||
if err == nil {
|
||||
@@ -223,7 +222,7 @@ var bunker = &cli.Command{
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(time.Minute * 5):
|
||||
fmt.Fprintf(os.Stderr, "\n")
|
||||
log("\n")
|
||||
printBunkerInfo()
|
||||
}
|
||||
}()
|
||||
|
||||
9
count.go
9
count.go
@@ -7,14 +7,15 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
var count = &cli.Command{
|
||||
Name: "count",
|
||||
Usage: "generates encoded COUNT messages and optionally use them to talk to relays",
|
||||
Description: `outputs a NIP-45 request (the flags are mostly the same as 'nak req').`,
|
||||
Name: "count",
|
||||
Usage: "generates encoded COUNT messages and optionally use them to talk to relays",
|
||||
Description: `outputs a NIP-45 request (the flags are mostly the same as 'nak req').`,
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "author",
|
||||
|
||||
12
decode.go
12
decode.go
@@ -6,10 +6,10 @@ import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
sdk "github.com/nbd-wtf/nostr-sdk"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr/sdk"
|
||||
)
|
||||
|
||||
var decode = &cli.Command{
|
||||
@@ -20,6 +20,7 @@ var decode = &cli.Command{
|
||||
nak decode nevent1qqs29yet5tp0qq5xu5qgkeehkzqh5qu46739axzezcxpj4tjlkx9j7gpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5sh59ud
|
||||
nak decode nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpz4mhxue69uhk2er9dchxummnw3ezumrpdejqz8thwden5te0dehhxarj94c82c3wwajkcmr0wfjx2u3wdejhgqgcwaehxw309aex2mrp0yhxummnw3exzarf9e3k7mgnp0sh5
|
||||
nak decode nsec1jrmyhtjhgd9yqalps8hf9mayvd58852gtz66m7tqpacjedkp6kxq4dyxsr`,
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "id",
|
||||
@@ -58,8 +59,11 @@ var decode = &cli.Command{
|
||||
} else if pp := sdk.InputToProfile(ctx, input); pp != nil {
|
||||
decodeResult = DecodeResult{ProfilePointer: pp}
|
||||
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "naddr" {
|
||||
ep := value.(nostr.EntityPointer)
|
||||
decodeResult = DecodeResult{EntityPointer: &ep}
|
||||
if ep, ok := value.(nostr.EntityPointer); ok {
|
||||
decodeResult = DecodeResult{EntityPointer: &ep}
|
||||
} else {
|
||||
ctx = lineProcessingError(ctx, "couldn't decode naddr: %s", err)
|
||||
}
|
||||
} else if prefix, value, err := nip19.Decode(input); err == nil && prefix == "nsec" {
|
||||
decodeResult.PrivateKey.PrivateKey = value.(string)
|
||||
decodeResult.PrivateKey.PublicKey, _ = nostr.GetPublicKey(value.(string))
|
||||
|
||||
21
encode.go
21
encode.go
@@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
)
|
||||
|
||||
var encode = &cli.Command{
|
||||
@@ -25,10 +25,12 @@ var encode = &cli.Command{
|
||||
}
|
||||
return nil
|
||||
},
|
||||
DisableSliceFlagSeparator: true,
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "npub",
|
||||
Usage: "encode a hex public key into bech32 'npub' format",
|
||||
Name: "npub",
|
||||
Usage: "encode a hex public key into bech32 'npub' format",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValidPublicKey(target); !ok {
|
||||
@@ -48,8 +50,9 @@ var encode = &cli.Command{
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "nsec",
|
||||
Usage: "encode a hex private key into bech32 'nsec' format",
|
||||
Name: "nsec",
|
||||
Usage: "encode a hex private key into bech32 'nsec' format",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||
@@ -78,6 +81,7 @@ var encode = &cli.Command{
|
||||
Usage: "attach relay hints to nprofile code",
|
||||
},
|
||||
},
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||
@@ -116,6 +120,7 @@ var encode = &cli.Command{
|
||||
Usage: "attach an author pubkey as a hint to the nevent code",
|
||||
},
|
||||
},
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||
@@ -174,6 +179,7 @@ var encode = &cli.Command{
|
||||
Usage: "attach relay hints to naddr code",
|
||||
},
|
||||
},
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for d := range getStdinLinesOrBlank() {
|
||||
pubkey := c.String("pubkey")
|
||||
@@ -211,8 +217,9 @@ var encode = &cli.Command{
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "note",
|
||||
Usage: "generate note1 event codes (not recommended)",
|
||||
Name: "note",
|
||||
Usage: "generate note1 event codes (not recommended)",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for target := range getStdinLinesOrArguments(c.Args()) {
|
||||
if ok := nostr.IsValid32ByteHex(target); !ok {
|
||||
|
||||
140
encrypt_decrypt.go
Normal file
140
encrypt_decrypt.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip04"
|
||||
)
|
||||
|
||||
var encrypt = &cli.Command{
|
||||
Name: "encrypt",
|
||||
Usage: "encrypts a string with nip44 (or nip04 if specified using a flag) and returns the resulting ciphertext as base64",
|
||||
ArgsUsage: "[plaintext string]",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: append(
|
||||
defaultKeyFlags,
|
||||
&cli.StringFlag{
|
||||
Name: "recipient-pubkey",
|
||||
Aliases: []string{"p", "tgt", "target", "pubkey"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "nip04",
|
||||
Usage: "use nip04 encryption instead of nip44",
|
||||
},
|
||||
),
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
target := c.String("recipient-pubkey")
|
||||
if !nostr.IsValidPublicKey(target) {
|
||||
return fmt.Errorf("target %s is not a valid public key", target)
|
||||
}
|
||||
|
||||
plaintext := c.Args().First()
|
||||
|
||||
if c.Bool("nip04") {
|
||||
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bunker != nil {
|
||||
ciphertext, err := bunker.NIP04Encrypt(ctx, target, plaintext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdout(ciphertext)
|
||||
} else {
|
||||
ss, err := nip04.ComputeSharedSecret(target, sec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compute nip04 shared secret: %w", err)
|
||||
}
|
||||
ciphertext, err := nip04.Encrypt(plaintext, ss)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encrypt as nip04: %w", err)
|
||||
}
|
||||
stdout(ciphertext)
|
||||
}
|
||||
} else {
|
||||
kr, err := gatherKeyerFromArguments(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := kr.Encrypt(ctx, plaintext, target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encrypt: %w", err)
|
||||
}
|
||||
stdout(res)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var decrypt = &cli.Command{
|
||||
Name: "decrypt",
|
||||
Usage: "decrypts a base64 nip44 ciphertext (or nip04 if specified using a flag) and returns the resulting plaintext",
|
||||
ArgsUsage: "[ciphertext base64]",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: append(
|
||||
defaultKeyFlags,
|
||||
&cli.StringFlag{
|
||||
Name: "sender-pubkey",
|
||||
Aliases: []string{"p", "src", "source", "pubkey"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "nip04",
|
||||
Usage: "use nip04 encryption instead of nip44",
|
||||
},
|
||||
),
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
source := c.String("sender-pubkey")
|
||||
if !nostr.IsValidPublicKey(source) {
|
||||
return fmt.Errorf("source %s is not a valid public key", source)
|
||||
}
|
||||
|
||||
ciphertext := c.Args().First()
|
||||
|
||||
if c.Bool("nip04") {
|
||||
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if bunker != nil {
|
||||
plaintext, err := bunker.NIP04Decrypt(ctx, source, ciphertext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stdout(plaintext)
|
||||
} else {
|
||||
ss, err := nip04.ComputeSharedSecret(source, sec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compute nip04 shared secret: %w", err)
|
||||
}
|
||||
plaintext, err := nip04.Decrypt(ciphertext, ss)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encrypt as nip04: %w", err)
|
||||
}
|
||||
stdout(plaintext)
|
||||
}
|
||||
} else {
|
||||
kr, err := gatherKeyerFromArguments(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := kr.Decrypt(ctx, ciphertext, source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encrypt: %w", err)
|
||||
}
|
||||
stdout(res)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
124
event.go
124
event.go
@@ -8,14 +8,19 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip13"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"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{
|
||||
Name: "event",
|
||||
@@ -31,26 +36,8 @@ if an event -- or a partial event -- is given on stdin, the flags can be used to
|
||||
example:
|
||||
echo '{"id":"a889df6a387419ff204305f4c2d296ee328c3cd4f8b62f205648a541b4554dfb","pubkey":"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5","created_at":1698623783,"kind":1,"tags":[],"content":"hello from the nostr army knife","sig":"84876e1ee3e726da84e5d195eb79358b2b3eaa4d9bd38456fde3e8a2af3f1cd4cda23f23fda454869975b3688797d4c66e12f4c51c1b43c6d2997c5e61865661"}' | nak event wss://offchain.pub
|
||||
echo '{"tags": [["t", "spam"]]}' | nak event -c 'this is spam'`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "sec",
|
||||
Usage: "secret key to sign the event, as nsec, ncryptsec or hex",
|
||||
DefaultText: "the key '1'",
|
||||
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "prompt-sec",
|
||||
Usage: "prompt the user to paste a hex or nsec with which to sign the event",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "connect",
|
||||
Usage: "sign event using NIP-46, expects a bunker://... URL",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "connect-as",
|
||||
Usage: "private key to when communicating with the bunker given on --connect",
|
||||
DefaultText: "a random key",
|
||||
},
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: append(defaultKeyFlags,
|
||||
// ~ these args are only for the convoluted musig2 signing process
|
||||
// they will be generally copy-shared-pasted across some manual coordination method between participants
|
||||
&cli.UintFlag{
|
||||
@@ -58,6 +45,7 @@ example:
|
||||
Usage: "number of signers to use for musig2",
|
||||
Value: 1,
|
||||
DefaultText: "1 -- i.e. do not use musig2 at all",
|
||||
Category: CATEGORY_SIGNER,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "musig-pubkey",
|
||||
@@ -76,17 +64,25 @@ example:
|
||||
Hidden: true,
|
||||
},
|
||||
// ~~~
|
||||
&cli.BoolFlag{
|
||||
Name: "envelope",
|
||||
Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay",
|
||||
&cli.UintFlag{
|
||||
Name: "pow",
|
||||
Usage: "NIP-13 difficulty to target when doing hash work on the event id",
|
||||
Category: CATEGORY_EXTRAS,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "auth",
|
||||
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
||||
Name: "envelope",
|
||||
Usage: "print the event enveloped in a [\"EVENT\", ...] message ready to be sent to a relay",
|
||||
Category: CATEGORY_EXTRAS,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "nevent",
|
||||
Usage: "print the nevent code (to stderr) after the event is published",
|
||||
Name: "auth",
|
||||
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
||||
Category: CATEGORY_EXTRAS,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "nevent",
|
||||
Usage: "print the nevent code (to stderr) after the event is published",
|
||||
Category: CATEGORY_EXTRAS,
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "kind",
|
||||
@@ -107,7 +103,7 @@ example:
|
||||
&cli.StringSliceFlag{
|
||||
Name: "tag",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "sets a tag field on the event, takes a value like -t e=<id>",
|
||||
Usage: "sets a tag field on the event, takes a value like -t e=<id> or -t sometag=\"value one;value two;value three\"",
|
||||
Category: CATEGORY_EVENT_FIELDS,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
@@ -133,13 +129,13 @@ example:
|
||||
Value: nostr.Now(),
|
||||
Category: CATEGORY_EVENT_FIELDS,
|
||||
},
|
||||
},
|
||||
),
|
||||
ArgsUsage: "[relay...]",
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
// try to connect to the relays here
|
||||
var relays []*nostr.Relay
|
||||
if relayUrls := c.Args().Slice(); len(relayUrls) > 0 {
|
||||
_, relays = connectToAllRelays(ctx, relayUrls, false)
|
||||
relays = connectToAllRelays(ctx, relayUrls, false)
|
||||
if len(relays) == 0 {
|
||||
log("failed to connect to any of the given relays.\n")
|
||||
os.Exit(3)
|
||||
@@ -152,10 +148,11 @@ example:
|
||||
}
|
||||
}()
|
||||
|
||||
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||
kr, err := gatherKeyerFromArguments(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sec, _, _ := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||
|
||||
doAuth := c.Bool("auth")
|
||||
|
||||
@@ -184,16 +181,17 @@ example:
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
|
||||
if content := c.String("content"); content != "" {
|
||||
evt.Content = content
|
||||
if c.IsSet("content") {
|
||||
evt.Content = c.String("content")
|
||||
mustRehashAndResign = true
|
||||
} else if evt.Content == "" && evt.Kind == 1 {
|
||||
evt.Content = "hello from the nostr army knife"
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
|
||||
tags := make(nostr.Tags, 0, 5)
|
||||
for _, tagFlag := range c.StringSlice("tag") {
|
||||
tagFlags := c.StringSlice("tag")
|
||||
tags := make(nostr.Tags, 0, len(tagFlags)+2)
|
||||
for _, tagFlag := range tagFlags {
|
||||
// tags are in the format key=value
|
||||
tagName, tagValue, found := strings.Cut(tagFlag, "=")
|
||||
tag := []string{tagName}
|
||||
@@ -202,20 +200,17 @@ example:
|
||||
tagValues := strings.Split(tagValue, ";")
|
||||
tag = append(tag, tagValues...)
|
||||
}
|
||||
tags = tags.AppendUnique(tag)
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
for _, etag := range c.StringSlice("e") {
|
||||
tags = tags.AppendUnique([]string{"e", etag})
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
for _, ptag := range c.StringSlice("p") {
|
||||
tags = tags.AppendUnique([]string{"p", ptag})
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
for _, dtag := range c.StringSlice("d") {
|
||||
tags = tags.AppendUnique([]string{"d", dtag})
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
for _, tag := range tags {
|
||||
@@ -232,12 +227,30 @@ example:
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
|
||||
if evt.Sig == "" || mustRehashAndResign {
|
||||
if bunker != nil {
|
||||
if err := bunker.SignEvent(ctx, &evt); err != nil {
|
||||
return fmt.Errorf("failed to sign with bunker: %w", err)
|
||||
if difficulty := c.Uint("pow"); difficulty > 0 {
|
||||
// before doing pow we need the pubkey
|
||||
if numSigners := c.Uint("musig"); numSigners > 1 {
|
||||
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")
|
||||
}
|
||||
} else if numSigners := c.Uint("musig"); numSigners > 1 && sec != "" {
|
||||
evt.PubKey, err = getMusigAggregatedKey(ctx, pubkeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
evt.PubKey, _ = kr.GetPublicKey(ctx)
|
||||
}
|
||||
|
||||
// try to generate work with this difficulty -- runs forever
|
||||
nonceTag, _ := nip13.DoWork(ctx, evt, int(difficulty))
|
||||
evt.Tags = append(evt.Tags, nonceTag)
|
||||
|
||||
mustRehashAndResign = true
|
||||
}
|
||||
|
||||
if evt.Sig == "" || mustRehashAndResign {
|
||||
if numSigners := c.Uint("musig"); numSigners > 1 && sec != "" {
|
||||
pubkeys := c.StringSlice("musig-pubkey")
|
||||
secNonce := c.String("musig-nonce-secret")
|
||||
pubNonces := c.StringSlice("musig-nonce")
|
||||
@@ -252,7 +265,7 @@ example:
|
||||
// instructions for what to do should have been printed by the performMusig() function
|
||||
return nil
|
||||
}
|
||||
} else if err := evt.Sign(sec); err != nil {
|
||||
} else if err := kr.SignEvent(ctx, &evt); err != nil {
|
||||
return fmt.Errorf("error signing with provided key: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -289,21 +302,10 @@ example:
|
||||
// error publishing
|
||||
if strings.HasPrefix(err.Error(), "msg: auth-required:") && (sec != "" || bunker != nil) && doAuth {
|
||||
// if the relay is requesting auth and we can auth, let's do it
|
||||
var pk string
|
||||
if bunker != nil {
|
||||
pk, err = bunker.GetPublicKey(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get public key from bunker: %w", err)
|
||||
}
|
||||
} else {
|
||||
pk, _ = nostr.GetPublicKey(sec)
|
||||
}
|
||||
pk, _ := kr.GetPublicKey(ctx)
|
||||
log("performing auth as %s... ", pk)
|
||||
if err := relay.Auth(ctx, func(evt *nostr.Event) error {
|
||||
if bunker != nil {
|
||||
return bunker.SignEvent(ctx, evt)
|
||||
}
|
||||
return evt.Sign(sec)
|
||||
if err := relay.Auth(ctx, func(authEvent *nostr.Event) error {
|
||||
return kr.SignEvent(ctx, authEvent)
|
||||
}); err == nil {
|
||||
// try to publish again, but this time don't try to auth again
|
||||
doAuth = false
|
||||
|
||||
@@ -116,3 +116,9 @@ func ExampleReqWithFlagsAfter3() {
|
||||
// Output:
|
||||
// {"kind":1,"id":"101572c80ebdc963dab8440f6307387a3023b6d90f7e495d6c5ee1ef77045a67","pubkey":"3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24","created_at":1720987305,"tags":[["e","ceacdc29fa7a0b51640b30d2424e188215460617db5ba5bb52d3fbf0094eebb3","","root"],["e","9f3c1121c96edf17d84b9194f74d66d012b28c4e25b3ef190582c76b8546a188","","reply"],["p","3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24"],["p","6b96c3eb36c6cd457d906bbaafe7b36cacfb8bcc4ab235be6eab3b71c6669251"]],"content":"Nope. I grew up playing in the woods. Never once saw a bear in the woods. If I did, I'd probably shiy my pants, then scream at it like I was a crazy person with my arms above my head to make me seem huge.","sig":"b098820b4a5635865cada9f9a5813be2bc6dd7180e16e590cf30e07916d8ed6ed98ab38b64f3bfba12d88d37335f229f7ef8c084bc48132e936c664a54d3e650"}
|
||||
}
|
||||
|
||||
func ExampleNaturalTimestamps() {
|
||||
app.Run(ctx, []string{"nak", "event", "-t", "plu=pla", "-e", "3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24", "--ts", "May 19 2018 03:37:19", "-c", "nn"})
|
||||
// Output:
|
||||
// {"kind":0,"id":"b10da0095f96aa2accd99fa3d93bf29a76f51d2594cf5a0a52f8e961aecd0b67","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1526711839,"tags":[["plu","pla"],["e","3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24"]],"content":"nn","sig":"988442c97064a041ba5e2bfbd64e84d3f819b2169e865511d9d53e74667949ff165325942acaa2ca233c8b529adedf12cf44088cf04081b56d098c5f4d52dd8f"}
|
||||
}
|
||||
|
||||
125
fetch.go
125
fetch.go
@@ -2,32 +2,32 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
sdk "github.com/nbd-wtf/nostr-sdk"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip05"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
)
|
||||
|
||||
var fetch = &cli.Command{
|
||||
Name: "fetch",
|
||||
Usage: "fetches events related to the given nip19 code from the included relay hints or the author's NIP-65 relays.",
|
||||
Usage: "fetches events related to the given nip19 or nip05 code from the included relay hints or the author's NIP-65 relays.",
|
||||
Description: `example usage:
|
||||
nak fetch nevent1qqsxrwm0hd3s3fddh4jc2574z3xzufq6qwuyz2rvv3n087zvym3dpaqprpmhxue69uhhqatzd35kxtnjv4kxz7tfdenju6t0xpnej4
|
||||
echo npub1h8spmtw9m2huyv6v2j2qd5zv956z2zdugl6mgx02f2upffwpm3nqv0j4ps | nak fetch --relay wss://relay.nostr.band`,
|
||||
Flags: []cli.Flag{
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: append(reqFilterFlags,
|
||||
&cli.StringSliceFlag{
|
||||
Name: "relay",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "also use these relays to fetch from",
|
||||
},
|
||||
},
|
||||
ArgsUsage: "[nip19code]",
|
||||
),
|
||||
ArgsUsage: "[nip05_or_nip19_code]",
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
pool := nostr.NewSimplePool(ctx)
|
||||
|
||||
defer func() {
|
||||
pool.Relays.Range(func(_ string, relay *nostr.Relay) bool {
|
||||
sys.Pool.Relays.Range(func(_ string, relay *nostr.Relay) bool {
|
||||
relay.Close()
|
||||
return true
|
||||
})
|
||||
@@ -35,64 +35,81 @@ var fetch = &cli.Command{
|
||||
|
||||
for code := range getStdinLinesOrArguments(c.Args()) {
|
||||
filter := nostr.Filter{}
|
||||
|
||||
prefix, value, err := nip19.Decode(code)
|
||||
if err != nil {
|
||||
ctx = lineProcessingError(ctx, "failed to decode: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
relays := c.StringSlice("relay")
|
||||
if err := normalizeAndValidateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
var authorHint string
|
||||
relays := c.StringSlice("relay")
|
||||
|
||||
switch prefix {
|
||||
case "nevent":
|
||||
v := value.(nostr.EventPointer)
|
||||
filter.IDs = append(filter.IDs, v.ID)
|
||||
if v.Author != "" {
|
||||
authorHint = v.Author
|
||||
if nip05.IsValidIdentifier(code) {
|
||||
pp, err := nip05.QueryIdentifier(ctx, code)
|
||||
if err != nil {
|
||||
ctx = lineProcessingError(ctx, "failed to fetch nip05: %s", err)
|
||||
continue
|
||||
}
|
||||
authorHint = pp.PublicKey
|
||||
relays = append(relays, pp.Relays...)
|
||||
filter.Authors = append(filter.Authors, pp.PublicKey)
|
||||
} else {
|
||||
prefix, value, err := nip19.Decode(code)
|
||||
if err != nil {
|
||||
ctx = lineProcessingError(ctx, "failed to decode: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := normalizeAndValidateRelayURLs(relays); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch prefix {
|
||||
case "nevent":
|
||||
v := value.(nostr.EventPointer)
|
||||
filter.IDs = append(filter.IDs, v.ID)
|
||||
if v.Author != "" {
|
||||
authorHint = v.Author
|
||||
}
|
||||
relays = append(relays, v.Relays...)
|
||||
case "note":
|
||||
filter.IDs = append(filter.IDs, value.(string))
|
||||
case "naddr":
|
||||
v := value.(nostr.EntityPointer)
|
||||
filter.Kinds = []int{v.Kind}
|
||||
filter.Tags = nostr.TagMap{"d": []string{v.Identifier}}
|
||||
filter.Authors = append(filter.Authors, v.PublicKey)
|
||||
authorHint = v.PublicKey
|
||||
relays = append(relays, v.Relays...)
|
||||
case "nprofile":
|
||||
v := value.(nostr.ProfilePointer)
|
||||
filter.Authors = append(filter.Authors, v.PublicKey)
|
||||
authorHint = v.PublicKey
|
||||
relays = append(relays, v.Relays...)
|
||||
case "npub":
|
||||
v := value.(string)
|
||||
filter.Authors = append(filter.Authors, v)
|
||||
authorHint = v
|
||||
default:
|
||||
return fmt.Errorf("unexpected prefix %s", prefix)
|
||||
}
|
||||
relays = append(relays, v.Relays...)
|
||||
case "naddr":
|
||||
v := value.(nostr.EntityPointer)
|
||||
filter.Tags = nostr.TagMap{"d": []string{v.Identifier}}
|
||||
filter.Kinds = append(filter.Kinds, v.Kind)
|
||||
filter.Authors = append(filter.Authors, v.PublicKey)
|
||||
authorHint = v.PublicKey
|
||||
relays = append(relays, v.Relays...)
|
||||
case "nprofile":
|
||||
v := value.(nostr.ProfilePointer)
|
||||
filter.Authors = append(filter.Authors, v.PublicKey)
|
||||
filter.Kinds = append(filter.Kinds, 0)
|
||||
authorHint = v.PublicKey
|
||||
relays = append(relays, v.Relays...)
|
||||
case "npub":
|
||||
v := value.(string)
|
||||
filter.Authors = append(filter.Authors, v)
|
||||
filter.Kinds = append(filter.Kinds, 0)
|
||||
authorHint = v
|
||||
}
|
||||
|
||||
if authorHint != "" {
|
||||
relayList := sdk.FetchRelaysForPubkey(ctx, pool, authorHint,
|
||||
"wss://purplepag.es", "wss://relay.damus.io", "wss://relay.noswhere.com",
|
||||
"wss://nos.lol", "wss://public.relaying.io", "wss://relay.nostr.band")
|
||||
for _, relayListItem := range relayList {
|
||||
if relayListItem.Outbox {
|
||||
relays = append(relays, relayListItem.URL)
|
||||
}
|
||||
relays := sys.FetchOutboxRelays(ctx, authorHint, 3)
|
||||
for _, url := range relays {
|
||||
relays = append(relays, url)
|
||||
}
|
||||
}
|
||||
|
||||
if len(filter.Authors) > 0 && len(filter.Kinds) == 0 {
|
||||
filter.Kinds = append(filter.Kinds, 0)
|
||||
}
|
||||
|
||||
if err := applyFlagsToFilter(c, &filter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(relays) == 0 {
|
||||
ctx = lineProcessingError(ctx, "no relay hints found")
|
||||
continue
|
||||
}
|
||||
|
||||
for ie := range pool.SubManyEose(ctx, relays, nostr.Filters{filter}) {
|
||||
for ie := range sys.Pool.SubManyEose(ctx, relays, nostr.Filters{filter}) {
|
||||
stdout(ie.Event)
|
||||
}
|
||||
}
|
||||
|
||||
2
flags.go
2
flags.go
@@ -6,9 +6,9 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/markusmobius/go-dateparser"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
)
|
||||
|
||||
type NaturalTimeFlag = cli.FlagBase[nostr.Timestamp, struct{}, naturalTimeValue]
|
||||
|
||||
44
go.mod
44
go.mod
@@ -1,47 +1,59 @@
|
||||
module github.com/fiatjaf/nak
|
||||
|
||||
go 1.22
|
||||
|
||||
toolchain go1.22.4
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3
|
||||
github.com/bep/debounce v1.2.1
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae
|
||||
github.com/fiatjaf/eventstore v0.9.0
|
||||
github.com/fiatjaf/khatru v0.7.5
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
github.com/markusmobius/go-dateparser v1.2.3
|
||||
github.com/nbd-wtf/go-nostr v0.34.2
|
||||
github.com/nbd-wtf/nostr-sdk v0.0.5
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37
|
||||
github.com/nbd-wtf/go-nostr v0.40.1
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chzyer/logex v1.1.10 // indirect
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
|
||||
github.com/dgraph-io/ristretto v1.0.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/elliotchance/pie/v2 v2.7.0 // indirect
|
||||
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae // indirect
|
||||
github.com/fiatjaf/eventstore v0.2.16 // indirect
|
||||
github.com/fasthttp/websocket v1.5.7 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/gobwas/ws v1.4.0 // indirect
|
||||
github.com/graph-gophers/dataloader/v7 v7.1.0 // indirect
|
||||
github.com/hablullah/go-hijri v1.0.2 // indirect
|
||||
github.com/hablullah/go-juliandays v1.0.0 // indirect
|
||||
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.17.8 // indirect
|
||||
github.com/magefile/mage v1.14.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.2.1 // indirect
|
||||
github.com/tidwall/gjson v1.17.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect
|
||||
github.com/rs/cors v1.7.0 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||
github.com/tetratelabs/wazero v1.8.0 // indirect
|
||||
github.com/tidwall/gjson v1.17.3 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/wasilibs/go-re2 v1.3.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
)
|
||||
|
||||
78
go.sum
78
go.sum
@@ -1,11 +1,15 @@
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
||||
github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3 h1:6+iXlDKE8RMtKsvK0gshlXIuPbyWM/h84Ensb7o3sC0=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ=
|
||||
@@ -23,6 +27,8 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
|
||||
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
@@ -34,22 +40,30 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||
github.com/dgraph-io/ristretto v1.0.0 h1:SYG07bONKMlFDUYu5pEu3DGAh8c2OFNzKm6G9J4Si84=
|
||||
github.com/dgraph-io/ristretto v1.0.0/go.mod h1:jTi2FiYEhQ1NsMmA7DeBykizjOuY88NhKBkepyu1jPc=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/elliotchance/pie/v2 v2.7.0 h1:FqoIKg4uj0G/CrLGuMS9ejnFKa92lxE1dEgBD3pShXg=
|
||||
github.com/elliotchance/pie/v2 v2.7.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og=
|
||||
github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KHau4=
|
||||
github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fiatjaf/cli/v3 v3.0.0-20240714232133-bb036558919f h1:SQ5W4q4HfpAPA8e8uPADqs4dhI6VvVwUq00DtlpHIt0=
|
||||
github.com/fiatjaf/cli/v3 v3.0.0-20240714232133-bb036558919f/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI=
|
||||
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae h1:0B/1dU3YECIbPoBIRTQ4c0scZCNz9TVHtQpiODGrTTo=
|
||||
github.com/fiatjaf/cli/v3 v3.0.0-20240723181502-e7dd498b16ae/go.mod h1:aAWPO4bixZZxPtOnH6K3q4GbQ0jftUNDW9Oa861IRew=
|
||||
github.com/fiatjaf/eventstore v0.2.16 h1:NR64mnyUT5nJR8Sj2AwJTd1Hqs5kKJcCFO21ggUkvWg=
|
||||
github.com/fiatjaf/eventstore v0.2.16/go.mod h1:rUc1KhVufVmC+HUOiuPweGAcvG6lEOQCkRCn2Xn5VRA=
|
||||
github.com/fiatjaf/eventstore v0.9.0 h1:WsGDVAaRaVaV/J8PdrQDGfzChrL13q+lTO4C44rhu3E=
|
||||
github.com/fiatjaf/eventstore v0.9.0/go.mod h1:JrAce5h0wi79+Sw4gsEq5kz0NtUxbVkOZ7lAo7ay6R8=
|
||||
github.com/fiatjaf/khatru v0.7.5 h1:UFo+cdbqHDn1W4Q4h03y3vzh1BiU+6fLYK48oWU2K34=
|
||||
github.com/fiatjaf/khatru v0.7.5/go.mod h1:WVqij7X9Vr9UAMIwafQbKVFKxc42Np37vyficwUr/nQ=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
@@ -69,6 +83,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/graph-gophers/dataloader/v7 v7.1.0 h1:Wn8HGF/q7MNXcvfaBnLEPEFJttVHR8zuEqP1obys/oc=
|
||||
github.com/graph-gophers/dataloader/v7 v7.1.0/go.mod h1:1bKE0Dm6OUcTB/OAuYVOZctgIz7Q3d0XrYtlIzTgg6Q=
|
||||
github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k=
|
||||
github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
|
||||
github.com/hablullah/go-juliandays v1.0.0 h1:A8YM7wIj16SzlKT0SRJc9CD29iiaUzpBLzh5hr0/5p0=
|
||||
@@ -82,6 +98,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
@@ -93,10 +111,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/nbd-wtf/go-nostr v0.34.2 h1:9b4qZ29DhQf9xEWN8/7zfDD868r1jFbpjrR3c+BHc+E=
|
||||
github.com/nbd-wtf/go-nostr v0.34.2/go.mod h1:NZQkxl96ggbO8rvDpVjcsojJqKTPwqhP4i82O7K5DJs=
|
||||
github.com/nbd-wtf/nostr-sdk v0.0.5 h1:rec+FcDizDVO0W25PX0lgYMXvP7zNNOgI3Fu9UCm4BY=
|
||||
github.com/nbd-wtf/nostr-sdk v0.0.5/go.mod h1:iJJsikesCGLNFZ9dLqhLPDzdt924EagUmdQxT3w2Lmk=
|
||||
github.com/nbd-wtf/go-nostr v0.40.1 h1:+ogxn+CeRwjQSMSU161fOxKWtVWTEz/p++X4O8VKhMw=
|
||||
github.com/nbd-wtf/go-nostr v0.40.1/go.mod h1:FBa4FBJO7NuANvkeKSlrf0BIyxGufmrUbuelr6Q4Ick=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -106,24 +122,34 @@ github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/tetratelabs/wazero v1.2.1 h1:J4X2hrGzJvt+wqltuvcSjHQ7ujQxA9gb6PeMs4qlUWs=
|
||||
github.com/tetratelabs/wazero v1.2.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
|
||||
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
|
||||
github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94=
|
||||
github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/wasilibs/go-re2 v1.3.0 h1:LFhBNzoStM3wMie6rN2slD1cuYH2CGiHpvNL3UtcsMw=
|
||||
github.com/wasilibs/go-re2 v1.3.0/go.mod h1:AafrCXVvGRJJOImMajgJ2M7rVmWyisVK7sFshbxnVrg=
|
||||
github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ=
|
||||
@@ -131,10 +157,10 @@ github.com/wasilibs/nottinygc v0.4.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -154,13 +180,13 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
137
helpers.go
137
helpers.go
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
@@ -11,15 +10,20 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/fatih/color"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/nip46"
|
||||
"github.com/nbd-wtf/go-nostr/nip49"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/sdk"
|
||||
)
|
||||
|
||||
var sys = sdk.NewSystem()
|
||||
|
||||
func init() {
|
||||
sys.Pool = nostr.NewSimplePool(context.Background(),
|
||||
nostr.WithUserAgent("nak/b"),
|
||||
)
|
||||
}
|
||||
|
||||
const (
|
||||
LINE_PROCESSING_ERROR = iota
|
||||
)
|
||||
@@ -120,13 +124,20 @@ func connectToAllRelays(
|
||||
relayUrls []string,
|
||||
forcePreAuth bool,
|
||||
opts ...nostr.PoolOption,
|
||||
) (*nostr.SimplePool, []*nostr.Relay) {
|
||||
) []*nostr.Relay {
|
||||
sys.Pool = nostr.NewSimplePool(context.Background(),
|
||||
append(opts,
|
||||
nostr.WithEventMiddleware(sys.TrackEventHints),
|
||||
nostr.WithPenaltyBox(),
|
||||
nostr.WithUserAgent("nak/s"),
|
||||
)...,
|
||||
)
|
||||
|
||||
relays := make([]*nostr.Relay, 0, len(relayUrls))
|
||||
pool := nostr.NewSimplePool(ctx, opts...)
|
||||
relayLoop:
|
||||
for _, url := range relayUrls {
|
||||
log("connecting to %s... ", url)
|
||||
if relay, err := pool.EnsureRelay(url); err == nil {
|
||||
if relay, err := sys.Pool.EnsureRelay(url); err == nil {
|
||||
if forcePreAuth {
|
||||
log("waiting for auth challenge... ")
|
||||
signer := opts[0].(nostr.WithAuthHandler)
|
||||
@@ -140,7 +151,7 @@ relayLoop:
|
||||
if (*challengeTag)[1] == "" {
|
||||
return fmt.Errorf("auth not received yet *****")
|
||||
}
|
||||
return signer(authEvent)
|
||||
return signer(ctx, nostr.RelayEvent{Event: authEvent, Relay: relay})
|
||||
}); err == nil {
|
||||
// auth succeeded
|
||||
break challengeWaitLoop
|
||||
@@ -166,7 +177,7 @@ relayLoop:
|
||||
log(err.Error() + "\n")
|
||||
}
|
||||
}
|
||||
return pool, relays
|
||||
return relays
|
||||
}
|
||||
|
||||
func lineProcessingError(ctx context.Context, msg string, args ...any) context.Context {
|
||||
@@ -180,110 +191,6 @@ func exitIfLineProcessingError(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (string, *nip46.BunkerClient, error) {
|
||||
var err error
|
||||
|
||||
if bunkerURL := c.String("connect"); bunkerURL != "" {
|
||||
clientKey := c.String("connect-as")
|
||||
if clientKey != "" {
|
||||
clientKey = strings.Repeat("0", 64-len(clientKey)) + clientKey
|
||||
} else {
|
||||
clientKey = nostr.GeneratePrivateKey()
|
||||
}
|
||||
bunker, err := nip46.ConnectBunker(ctx, clientKey, bunkerURL, nil, func(s string) {
|
||||
fmt.Fprintf(color.Error, color.CyanString("[nip46]: open the following URL: %s"), s)
|
||||
})
|
||||
return "", bunker, err
|
||||
}
|
||||
|
||||
sec := c.String("sec")
|
||||
|
||||
// check in the environment for the secret key
|
||||
if sec == "" {
|
||||
if key, ok := os.LookupEnv("NOSTR_PRIVATE_KEY"); ok {
|
||||
sec = key
|
||||
}
|
||||
}
|
||||
|
||||
if c.Bool("prompt-sec") {
|
||||
if isPiped() {
|
||||
return "", nil, fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec")
|
||||
}
|
||||
sec, err = askPassword("type your secret key as ncryptsec, nsec or hex: ", nil)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to get secret key: %w", err)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(sec, "ncryptsec1") {
|
||||
sec, err = promptDecrypt(sec)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to decrypt: %w", err)
|
||||
}
|
||||
} else if bsec, err := hex.DecodeString(leftPadKey(sec)); err == nil {
|
||||
sec = hex.EncodeToString(bsec)
|
||||
} else if prefix, hexvalue, err := nip19.Decode(sec); err != nil {
|
||||
return "", nil, fmt.Errorf("invalid nsec: %w", err)
|
||||
} else if prefix == "nsec" {
|
||||
sec = hexvalue.(string)
|
||||
}
|
||||
|
||||
if ok := nostr.IsValid32ByteHex(sec); !ok {
|
||||
return "", nil, fmt.Errorf("invalid secret key")
|
||||
}
|
||||
return sec, nil, nil
|
||||
}
|
||||
|
||||
func promptDecrypt(ncryptsec string) (string, error) {
|
||||
for i := 1; i < 4; i++ {
|
||||
var attemptStr string
|
||||
if i > 1 {
|
||||
attemptStr = fmt.Sprintf(" [%d/3]", i)
|
||||
}
|
||||
password, err := askPassword("type the password to decrypt your secret key"+attemptStr+": ", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sec, err := nip49.Decrypt(ncryptsec, password)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return sec, nil
|
||||
}
|
||||
return "", fmt.Errorf("couldn't decrypt private key")
|
||||
}
|
||||
|
||||
func askPassword(msg string, shouldAskAgain func(answer string) bool) (string, error) {
|
||||
config := &readline.Config{
|
||||
Stdout: color.Error,
|
||||
Prompt: color.YellowString(msg),
|
||||
InterruptPrompt: "^C",
|
||||
DisableAutoSaveHistory: true,
|
||||
EnableMask: true,
|
||||
MaskRune: '*',
|
||||
}
|
||||
return _ask(config, msg, "", shouldAskAgain)
|
||||
}
|
||||
|
||||
func _ask(config *readline.Config, msg string, defaultValue string, shouldAskAgain func(answer string) bool) (string, error) {
|
||||
rl, err := readline.NewEx(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rl.WriteStdin([]byte(defaultValue))
|
||||
for {
|
||||
answer, err := rl.Readline()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
answer = strings.TrimSpace(strings.ToLower(answer))
|
||||
if shouldAskAgain != nil && shouldAskAgain(answer) {
|
||||
continue
|
||||
}
|
||||
return answer, err
|
||||
}
|
||||
}
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
func randString(n int) string {
|
||||
|
||||
160
helpers_key.go
Normal file
160
helpers_key.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
"github.com/fatih/color"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/keyer"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/nip46"
|
||||
"github.com/nbd-wtf/go-nostr/nip49"
|
||||
)
|
||||
|
||||
var defaultKeyFlags = []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "sec",
|
||||
Usage: "secret key to sign the event, as nsec, ncryptsec or hex, or a bunker URL",
|
||||
DefaultText: "the key '1'",
|
||||
Aliases: []string{"connect"},
|
||||
Category: CATEGORY_SIGNER,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "prompt-sec",
|
||||
Usage: "prompt the user to paste a hex or nsec with which to sign the event",
|
||||
Category: CATEGORY_SIGNER,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "connect-as",
|
||||
Usage: "private key to when communicating with the bunker given on --connect",
|
||||
DefaultText: "a random key",
|
||||
Category: CATEGORY_SIGNER,
|
||||
},
|
||||
}
|
||||
|
||||
func gatherKeyerFromArguments(ctx context.Context, c *cli.Command) (keyer.Keyer, error) {
|
||||
key, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kr keyer.Keyer
|
||||
if bunker != nil {
|
||||
kr = keyer.NewBunkerSignerFromBunkerClient(bunker)
|
||||
} else {
|
||||
kr, err = keyer.NewPlainKeySigner(key)
|
||||
}
|
||||
|
||||
return kr, err
|
||||
}
|
||||
|
||||
func gatherSecretKeyOrBunkerFromArguments(ctx context.Context, c *cli.Command) (string, *nip46.BunkerClient, error) {
|
||||
var err error
|
||||
|
||||
sec := c.String("sec")
|
||||
if strings.HasPrefix(sec, "bunker://") {
|
||||
// it's a bunker
|
||||
bunkerURL := sec
|
||||
clientKey := c.String("connect-as")
|
||||
if clientKey != "" {
|
||||
clientKey = strings.Repeat("0", 64-len(clientKey)) + clientKey
|
||||
} else {
|
||||
clientKey = nostr.GeneratePrivateKey()
|
||||
}
|
||||
bunker, err := nip46.ConnectBunker(ctx, clientKey, bunkerURL, nil, func(s string) {
|
||||
log(color.CyanString("[nip46]: open the following URL: %s"), s)
|
||||
})
|
||||
return "", bunker, err
|
||||
}
|
||||
|
||||
// take private from flags, environment variable or default to 1
|
||||
if sec == "" {
|
||||
if key, ok := os.LookupEnv("NOSTR_SECRET_KEY"); ok {
|
||||
sec = key
|
||||
} else {
|
||||
sec = "0000000000000000000000000000000000000000000000000000000000000001"
|
||||
}
|
||||
}
|
||||
|
||||
if c.Bool("prompt-sec") {
|
||||
if isPiped() {
|
||||
return "", nil, fmt.Errorf("can't prompt for a secret key when processing data from a pipe, try again without --prompt-sec")
|
||||
}
|
||||
sec, err = askPassword("type your secret key as ncryptsec, nsec or hex: ", nil)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to get secret key: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(sec, "ncryptsec1") {
|
||||
sec, err = promptDecrypt(sec)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("failed to decrypt: %w", err)
|
||||
}
|
||||
} else if bsec, err := hex.DecodeString(leftPadKey(sec)); err == nil {
|
||||
sec = hex.EncodeToString(bsec)
|
||||
} else if prefix, hexvalue, err := nip19.Decode(sec); err != nil {
|
||||
return "", nil, fmt.Errorf("invalid nsec: %w", err)
|
||||
} else if prefix == "nsec" {
|
||||
sec = hexvalue.(string)
|
||||
}
|
||||
|
||||
if ok := nostr.IsValid32ByteHex(sec); !ok {
|
||||
return "", nil, fmt.Errorf("invalid secret key")
|
||||
}
|
||||
|
||||
return sec, nil, nil
|
||||
}
|
||||
|
||||
func promptDecrypt(ncryptsec string) (string, error) {
|
||||
for i := 1; i < 4; i++ {
|
||||
var attemptStr string
|
||||
if i > 1 {
|
||||
attemptStr = fmt.Sprintf(" [%d/3]", i)
|
||||
}
|
||||
password, err := askPassword("type the password to decrypt your secret key"+attemptStr+": ", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sec, err := nip49.Decrypt(ncryptsec, password)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return sec, nil
|
||||
}
|
||||
return "", fmt.Errorf("couldn't decrypt private key")
|
||||
}
|
||||
|
||||
func askPassword(msg string, shouldAskAgain func(answer string) bool) (string, error) {
|
||||
config := &readline.Config{
|
||||
Stdout: color.Error,
|
||||
Prompt: color.YellowString(msg),
|
||||
InterruptPrompt: "^C",
|
||||
DisableAutoSaveHistory: true,
|
||||
EnableMask: true,
|
||||
MaskRune: '*',
|
||||
}
|
||||
|
||||
rl, err := readline.NewEx(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for {
|
||||
answer, err := rl.Readline()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
answer = strings.TrimSpace(answer)
|
||||
if shouldAskAgain != nil && shouldAskAgain(answer) {
|
||||
continue
|
||||
}
|
||||
return answer, err
|
||||
}
|
||||
}
|
||||
81
key.go
81
key.go
@@ -5,35 +5,36 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/nip49"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
)
|
||||
|
||||
var key = &cli.Command{
|
||||
Name: "key",
|
||||
Usage: "operations on secret keys: generate, derive, encrypt, decrypt.",
|
||||
Description: ``,
|
||||
Name: "key",
|
||||
Usage: "operations on secret keys: generate, derive, encrypt, decrypt.",
|
||||
Description: ``,
|
||||
DisableSliceFlagSeparator: true,
|
||||
Commands: []*cli.Command{
|
||||
generate,
|
||||
public,
|
||||
encrypt,
|
||||
decrypt,
|
||||
encryptKey,
|
||||
decryptKey,
|
||||
combine,
|
||||
},
|
||||
}
|
||||
|
||||
var generate = &cli.Command{
|
||||
Name: "generate",
|
||||
Usage: "generates a secret key",
|
||||
Description: ``,
|
||||
Name: "generate",
|
||||
Usage: "generates a secret key",
|
||||
Description: ``,
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
sec := nostr.GeneratePrivateKey()
|
||||
stdout(sec)
|
||||
@@ -42,28 +43,38 @@ var generate = &cli.Command{
|
||||
}
|
||||
|
||||
var public = &cli.Command{
|
||||
Name: "public",
|
||||
Usage: "computes a public key from a secret key",
|
||||
Description: ``,
|
||||
ArgsUsage: "[secret]",
|
||||
Name: "public",
|
||||
Usage: "computes a public key from a secret key",
|
||||
Description: ``,
|
||||
ArgsUsage: "[secret]",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "with-parity",
|
||||
Usage: "output 33 bytes instead of 32, the first one being either '02' or '03', a prefix indicating whether this pubkey is even or odd.",
|
||||
},
|
||||
},
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for sec := range getSecretKeysFromStdinLinesOrSlice(ctx, c, c.Args().Slice()) {
|
||||
pubkey, err := nostr.GetPublicKey(sec)
|
||||
if err != nil {
|
||||
ctx = lineProcessingError(ctx, "failed to derive public key: %s", err)
|
||||
continue
|
||||
b, _ := hex.DecodeString(sec)
|
||||
_, pk := btcec.PrivKeyFromBytes(b)
|
||||
|
||||
if c.Bool("with-parity") {
|
||||
stdout(hex.EncodeToString(pk.SerializeCompressed()))
|
||||
} else {
|
||||
stdout(hex.EncodeToString(pk.SerializeCompressed()[1:]))
|
||||
}
|
||||
stdout(pubkey)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var encrypt = &cli.Command{
|
||||
Name: "encrypt",
|
||||
Usage: "encrypts a secret key and prints an ncryptsec code",
|
||||
Description: `uses the NIP-49 standard.`,
|
||||
ArgsUsage: "<secret> <password>",
|
||||
var encryptKey = &cli.Command{
|
||||
Name: "encrypt",
|
||||
Usage: "encrypts a secret key and prints an ncryptsec code",
|
||||
Description: `uses the NIP-49 standard.`,
|
||||
ArgsUsage: "<secret> <password>",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
&cli.IntFlag{
|
||||
Name: "logn",
|
||||
@@ -97,11 +108,12 @@ var encrypt = &cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var decrypt = &cli.Command{
|
||||
Name: "decrypt",
|
||||
Usage: "takes an ncrypsec and a password and decrypts it into an nsec",
|
||||
Description: `uses the NIP-49 standard.`,
|
||||
ArgsUsage: "<ncryptsec-code> <password>",
|
||||
var decryptKey = &cli.Command{
|
||||
Name: "decrypt",
|
||||
Usage: "takes an ncrypsec and a password and decrypts it into an nsec",
|
||||
Description: `uses the NIP-49 standard.`,
|
||||
ArgsUsage: "<ncryptsec-code> <password>",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
var ncryptsec string
|
||||
var password string
|
||||
@@ -151,7 +163,8 @@ var combine = &cli.Command{
|
||||
Description: `The public keys must have 33 bytes (66 characters hex), with the 02 or 03 prefix. It is common in Nostr to drop that first byte, so you'll have to derive the public keys again from the private keys in order to get it back.
|
||||
|
||||
However, if the intent is to check if two existing Nostr pubkeys match a given combined pubkey, then it might be sufficient to calculate the combined key for all the possible combinations of pubkeys in the input.`,
|
||||
ArgsUsage: "[pubkey...]",
|
||||
ArgsUsage: "[pubkey...]",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
type Combination struct {
|
||||
Variants []string `json:"input_variants"`
|
||||
@@ -182,7 +195,7 @@ However, if the intent is to check if two existing Nostr pubkeys match a given c
|
||||
for i, prefix := range []byte{0x02, 0x03} {
|
||||
pubk, err := btcec.ParsePubKey(append([]byte{prefix}, keyb...))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error parsing key %s: %s", keyhex, err)
|
||||
log("error parsing key %s: %s", keyhex, err)
|
||||
continue
|
||||
}
|
||||
group[i] = pubk
|
||||
@@ -223,7 +236,7 @@ However, if the intent is to check if two existing Nostr pubkeys match a given c
|
||||
|
||||
agg, _, _, err := musig2.AggregateKeys(combining, true)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error aggregating: %s", err)
|
||||
log("error aggregating: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -246,13 +259,13 @@ However, if the intent is to check if two existing Nostr pubkeys match a given c
|
||||
}
|
||||
|
||||
res, _ := json.MarshalIndent(result, "", " ")
|
||||
fmt.Println(string(res))
|
||||
stdout(string(res))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func getSecretKeysFromStdinLinesOrSlice(ctx context.Context, c *cli.Command, keys []string) chan string {
|
||||
func getSecretKeysFromStdinLinesOrSlice(ctx context.Context, _ *cli.Command, keys []string) chan string {
|
||||
ch := make(chan string)
|
||||
go func() {
|
||||
for sec := range getStdinLinesOrArgumentsFromSlice(keys) {
|
||||
|
||||
19
main.go
19
main.go
@@ -7,12 +7,15 @@ import (
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
)
|
||||
|
||||
var version string = "debug"
|
||||
|
||||
var app = &cli.Command{
|
||||
Name: "nak",
|
||||
Suggest: true,
|
||||
UseShortOptionHandling: true,
|
||||
AllowFlagsAfterArguments: true,
|
||||
Usage: "the nostr army knife command-line tool",
|
||||
Name: "nak",
|
||||
Suggest: true,
|
||||
UseShortOptionHandling: true,
|
||||
AllowFlagsAfterArguments: true,
|
||||
Usage: "the nostr army knife command-line tool",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Commands: []*cli.Command{
|
||||
req,
|
||||
count,
|
||||
@@ -24,7 +27,11 @@ var app = &cli.Command{
|
||||
verify,
|
||||
relay,
|
||||
bunker,
|
||||
serve,
|
||||
encrypt,
|
||||
decrypt,
|
||||
},
|
||||
Version: version,
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "quiet",
|
||||
@@ -36,7 +43,7 @@ var app = &cli.Command{
|
||||
if q >= 1 {
|
||||
log = func(msg string, args ...any) {}
|
||||
if q >= 2 {
|
||||
stdout = func(a ...any) (int, error) { return 0, nil }
|
||||
stdout = func(_ ...any) (int, error) { return 0, nil }
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
36
musig2.go
36
musig2.go
@@ -6,7 +6,6 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -15,8 +14,33 @@ import (
|
||||
"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(
|
||||
ctx context.Context,
|
||||
_ context.Context,
|
||||
sec string,
|
||||
evt *nostr.Event,
|
||||
numSigners int,
|
||||
@@ -110,8 +134,8 @@ func performMusig(
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "the following code should be saved secretly until the next step an included with --musig-nonce-secret:\n")
|
||||
fmt.Fprintf(os.Stderr, "%s\n\n", base64.StdEncoding.EncodeToString(nonce.SecNonce[:]))
|
||||
log("the following code should be saved secretly until the next step an included with --musig-nonce-secret:\n")
|
||||
log("%s\n\n", base64.StdEncoding.EncodeToString(nonce.SecNonce[:]))
|
||||
|
||||
knownNonces = append(knownNonces, nonce.PubNonce)
|
||||
printPublicCommandForNextPeer(evt, numSigners, knownSigners, knownNonces, nil, false)
|
||||
@@ -124,7 +148,7 @@ func performMusig(
|
||||
} else {
|
||||
evt.PubKey = hex.EncodeToString(comb.SerializeCompressed()[1:])
|
||||
evt.ID = evt.GetID()
|
||||
fmt.Fprintf(os.Stderr, "combined key: %x\n\n", comb.SerializeCompressed())
|
||||
log("combined key: %x\n\n", comb.SerializeCompressed())
|
||||
}
|
||||
|
||||
// we have all the signers, which means we must also have all the nonces
|
||||
@@ -219,7 +243,7 @@ func printPublicCommandForNextPeer(
|
||||
maybeNonceSecret = " --musig-nonce-secret '<insert-nonce-secret>'"
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "the next signer and they should call this on their side:\nnak event --sec <insert-secret-key> --musig %d %s%s%s%s%s\n",
|
||||
log("the next signer and they should call this on their side:\nnak event --sec <insert-secret-key> --musig %d %s%s%s%s%s\n",
|
||||
numSigners,
|
||||
eventToCliArgs(evt),
|
||||
signersToCliArgs(knownSigners),
|
||||
|
||||
76
paginate.go
Normal file
76
paginate.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
func paginateWithParams(
|
||||
interval time.Duration,
|
||||
globalLimit uint64,
|
||||
) func(ctx context.Context, urls []string, filters nostr.Filters, opts ...nostr.SubscriptionOption) chan nostr.RelayEvent {
|
||||
return func(ctx context.Context, urls []string, filters nostr.Filters, opts ...nostr.SubscriptionOption) chan nostr.RelayEvent {
|
||||
// filters will always be just one
|
||||
filter := filters[0]
|
||||
|
||||
nextUntil := nostr.Now()
|
||||
if filter.Until != nil {
|
||||
nextUntil = *filter.Until
|
||||
}
|
||||
|
||||
if globalLimit == 0 {
|
||||
globalLimit = uint64(filter.Limit)
|
||||
if globalLimit == 0 && !filter.LimitZero {
|
||||
globalLimit = math.MaxUint64
|
||||
}
|
||||
}
|
||||
var globalCount uint64 = 0
|
||||
globalCh := make(chan nostr.RelayEvent)
|
||||
|
||||
repeatedCache := make([]string, 0, 300)
|
||||
nextRepeatedCache := make([]string, 0, 300)
|
||||
|
||||
go func() {
|
||||
defer close(globalCh)
|
||||
|
||||
for {
|
||||
filter.Until = &nextUntil
|
||||
time.Sleep(interval)
|
||||
|
||||
keepGoing := false
|
||||
for evt := range sys.Pool.SubManyEose(ctx, urls, nostr.Filters{filter}, opts...) {
|
||||
if slices.Contains(repeatedCache, evt.ID) {
|
||||
continue
|
||||
}
|
||||
|
||||
keepGoing = true // if we get one that isn't repeated, then keep trying to get more
|
||||
nextRepeatedCache = append(nextRepeatedCache, evt.ID)
|
||||
|
||||
globalCh <- evt
|
||||
|
||||
globalCount++
|
||||
if globalCount >= globalLimit {
|
||||
return
|
||||
}
|
||||
|
||||
if evt.CreatedAt < *filter.Until {
|
||||
nextUntil = evt.CreatedAt
|
||||
}
|
||||
}
|
||||
|
||||
if !keepGoing {
|
||||
return
|
||||
}
|
||||
|
||||
repeatedCache = nextRepeatedCache
|
||||
nextRepeatedCache = nextRepeatedCache[:0]
|
||||
}
|
||||
}()
|
||||
|
||||
return globalCh
|
||||
}
|
||||
}
|
||||
41
relay.go
41
relay.go
@@ -11,10 +11,10 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip11"
|
||||
"github.com/nbd-wtf/go-nostr/nip86"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
)
|
||||
|
||||
var relay = &cli.Command{
|
||||
@@ -22,7 +22,8 @@ var relay = &cli.Command{
|
||||
Usage: "gets the relay information document for the given relay, as JSON",
|
||||
Description: `example:
|
||||
nak relay nostr.wine`,
|
||||
ArgsUsage: "<relay-url>",
|
||||
ArgsUsage: "<relay-url>",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for url := range getStdinLinesOrArguments(c.Args()) {
|
||||
if url == "" {
|
||||
@@ -73,27 +74,7 @@ var relay = &cli.Command{
|
||||
flags[i] = declareFlag(argName)
|
||||
}
|
||||
|
||||
flags = append(flags,
|
||||
&cli.StringFlag{
|
||||
Name: "sec",
|
||||
Usage: "secret key to sign the event, as nsec, ncryptsec or hex",
|
||||
DefaultText: "the key '1'",
|
||||
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "prompt-sec",
|
||||
Usage: "prompt the user to paste a hex or nsec with which to sign the event",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "connect",
|
||||
Usage: "sign event using NIP-46, expects a bunker://... URL",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "connect-as",
|
||||
Usage: "private key to when communicating with the bunker given on --connect",
|
||||
DefaultText: "a random key",
|
||||
},
|
||||
)
|
||||
flags = append(flags, defaultKeyFlags...)
|
||||
|
||||
cmd := &cli.Command{
|
||||
Name: def.method,
|
||||
@@ -114,7 +95,7 @@ var relay = &cli.Command{
|
||||
return nil
|
||||
}
|
||||
|
||||
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||
kr, err := gatherKeyerFromArguments(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -131,7 +112,7 @@ var relay = &cli.Command{
|
||||
|
||||
// Authorization
|
||||
payloadHash := sha256.Sum256(reqj)
|
||||
authEvent := nostr.Event{
|
||||
tokenEvent := nostr.Event{
|
||||
Kind: 27235,
|
||||
CreatedAt: nostr.Now(),
|
||||
Tags: nostr.Tags{
|
||||
@@ -140,14 +121,10 @@ var relay = &cli.Command{
|
||||
{"payload", hex.EncodeToString(payloadHash[:])},
|
||||
},
|
||||
}
|
||||
if bunker != nil {
|
||||
if err := bunker.SignEvent(ctx, &authEvent); err != nil {
|
||||
return fmt.Errorf("failed to sign with bunker: %w", err)
|
||||
}
|
||||
} else if err := authEvent.Sign(sec); err != nil {
|
||||
return fmt.Errorf("error signing with provided key: %w", err)
|
||||
if err := kr.SignEvent(ctx, &tokenEvent); err != nil {
|
||||
return fmt.Errorf("failed to sign token event: %w", err)
|
||||
}
|
||||
evtj, _ := json.Marshal(authEvent)
|
||||
evtj, _ := json.Marshal(tokenEvent)
|
||||
req.Header.Set("Authorization", "Nostr "+base64.StdEncoding.EncodeToString(evtj))
|
||||
|
||||
// Content-Type
|
||||
|
||||
384
req.go
384
req.go
@@ -7,12 +7,15 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
)
|
||||
|
||||
const CATEGORY_FILTER_ATTRIBUTES = "FILTER ATTRIBUTES"
|
||||
const (
|
||||
CATEGORY_FILTER_ATTRIBUTES = "FILTER ATTRIBUTES"
|
||||
// CATEGORY_SIGNER = "SIGNER OPTIONS" -- defined at event.go as the same (yes, I know)
|
||||
)
|
||||
|
||||
var req = &cli.Command{
|
||||
Name: "req",
|
||||
@@ -27,140 +30,68 @@ it can also take a filter from stdin, optionally modify it with flags and send i
|
||||
|
||||
example:
|
||||
echo '{"kinds": [1], "#t": ["test"]}' | nak req -l 5 -k 4549 --tag t=spam wss://nostr-pub.wellorder.net`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "author",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "only accept events from these authors (pubkey as hex)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "id",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "only accept events with these ids (hex)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.IntSliceFlag{
|
||||
Name: "kind",
|
||||
Aliases: []string{"k"},
|
||||
Usage: "only accept events with these kind numbers",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "tag",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "takes a tag like -t e=<id>, only accept events with these tags",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "e",
|
||||
Usage: "shortcut for --tag e=<value>",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "p",
|
||||
Usage: "shortcut for --tag p=<value>",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "d",
|
||||
Usage: "shortcut for --tag d=<value>",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&NaturalTimeFlag{
|
||||
Name: "since",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "only accept events newer than this (unix timestamp)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&NaturalTimeFlag{
|
||||
Name: "until",
|
||||
Aliases: []string{"u"},
|
||||
Usage: "only accept events older than this (unix timestamp)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "limit",
|
||||
Aliases: []string{"l"},
|
||||
Usage: "only accept up to this number of events",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "search",
|
||||
Usage: "a NIP-50 search query, use it only with relays that explicitly support it",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "stream",
|
||||
Usage: "keep the subscription open, printing all events as they are returned",
|
||||
DefaultText: "false, will close on EOSE",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "bare",
|
||||
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "auth",
|
||||
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "force-pre-auth",
|
||||
Aliases: []string{"fpa"},
|
||||
Usage: "after connecting, for a NIP-42 \"AUTH\" message to be received, act on it and only then send the \"REQ\"",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "sec",
|
||||
Usage: "secret key to sign the AUTH challenge, as hex or nsec",
|
||||
DefaultText: "the key '1'",
|
||||
Value: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "prompt-sec",
|
||||
Usage: "prompt the user to paste a hex or nsec with which to sign the AUTH challenge",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "connect",
|
||||
Usage: "sign AUTH using NIP-46, expects a bunker://... URL",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "connect-as",
|
||||
Usage: "private key to when communicating with the bunker given on --connect",
|
||||
DefaultText: "a random key",
|
||||
},
|
||||
},
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: append(defaultKeyFlags,
|
||||
append(reqFilterFlags,
|
||||
&cli.BoolFlag{
|
||||
Name: "stream",
|
||||
Usage: "keep the subscription open, printing all events as they are returned",
|
||||
DefaultText: "false, will close on EOSE",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "paginate",
|
||||
Usage: "make multiple REQs to the relay decreasing the value of 'until' until 'limit' or 'since' conditions are met",
|
||||
DefaultText: "false",
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Name: "paginate-interval",
|
||||
Usage: "time between queries when using --paginate",
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "paginate-global-limit",
|
||||
Usage: "global limit at which --paginate should stop",
|
||||
DefaultText: "uses the value given by --limit/-l or infinite",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "bare",
|
||||
Usage: "when printing the filter, print just the filter, not enveloped in a [\"REQ\", ...] array",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "auth",
|
||||
Usage: "always perform NIP-42 \"AUTH\" when facing an \"auth-required: \" rejection and try again",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "force-pre-auth",
|
||||
Aliases: []string{"fpa"},
|
||||
Usage: "after connecting, for a NIP-42 \"AUTH\" message to be received, act on it and only then send the \"REQ\"",
|
||||
Category: CATEGORY_SIGNER,
|
||||
},
|
||||
)...,
|
||||
),
|
||||
ArgsUsage: "[relay...]",
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
var pool *nostr.SimplePool
|
||||
|
||||
relayUrls := c.Args().Slice()
|
||||
if len(relayUrls) > 0 {
|
||||
var relays []*nostr.Relay
|
||||
pool, relays = connectToAllRelays(ctx, relayUrls, c.Bool("force-pre-auth"), nostr.WithAuthHandler(func(evt *nostr.Event) error {
|
||||
if !c.Bool("auth") && !c.Bool("force-pre-auth") {
|
||||
return fmt.Errorf("auth not authorized")
|
||||
}
|
||||
sec, bunker, err := gatherSecretKeyOrBunkerFromArguments(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relays := connectToAllRelays(ctx,
|
||||
relayUrls,
|
||||
c.Bool("force-pre-auth"),
|
||||
nostr.WithAuthHandler(
|
||||
func(ctx context.Context, authEvent nostr.RelayEvent) error {
|
||||
if !c.Bool("auth") && !c.Bool("force-pre-auth") {
|
||||
return fmt.Errorf("auth not authorized")
|
||||
}
|
||||
kr, err := gatherKeyerFromArguments(ctx, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pk string
|
||||
if bunker != nil {
|
||||
pk, err = bunker.GetPublicKey(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get public key from bunker: %w", err)
|
||||
}
|
||||
} else {
|
||||
pk, _ = nostr.GetPublicKey(sec)
|
||||
}
|
||||
log("performing auth as %s... ", pk)
|
||||
pk, _ := kr.GetPublicKey(ctx)
|
||||
log("performing auth as %s... ", pk)
|
||||
|
||||
if bunker != nil {
|
||||
return bunker.SignEvent(ctx, evt)
|
||||
} else {
|
||||
return evt.Sign(sec)
|
||||
}
|
||||
}))
|
||||
return kr.SignEvent(ctx, authEvent.Event)
|
||||
},
|
||||
),
|
||||
)
|
||||
if len(relays) == 0 {
|
||||
log("failed to connect to any of the given relays.\n")
|
||||
os.Exit(3)
|
||||
@@ -186,68 +117,16 @@ example:
|
||||
}
|
||||
}
|
||||
|
||||
if authors := c.StringSlice("author"); len(authors) > 0 {
|
||||
filter.Authors = append(filter.Authors, authors...)
|
||||
}
|
||||
if ids := c.StringSlice("id"); len(ids) > 0 {
|
||||
filter.IDs = append(filter.IDs, ids...)
|
||||
}
|
||||
for _, kind64 := range c.IntSlice("kind") {
|
||||
filter.Kinds = append(filter.Kinds, int(kind64))
|
||||
}
|
||||
if search := c.String("search"); search != "" {
|
||||
filter.Search = search
|
||||
}
|
||||
tags := make([][]string, 0, 5)
|
||||
for _, tagFlag := range c.StringSlice("tag") {
|
||||
spl := strings.Split(tagFlag, "=")
|
||||
if len(spl) == 2 && len(spl[0]) == 1 {
|
||||
tags = append(tags, spl)
|
||||
} else {
|
||||
return fmt.Errorf("invalid --tag '%s'", tagFlag)
|
||||
}
|
||||
}
|
||||
for _, etag := range c.StringSlice("e") {
|
||||
tags = append(tags, []string{"e", etag})
|
||||
}
|
||||
for _, ptag := range c.StringSlice("p") {
|
||||
tags = append(tags, []string{"p", ptag})
|
||||
}
|
||||
for _, dtag := range c.StringSlice("d") {
|
||||
tags = append(tags, []string{"d", dtag})
|
||||
}
|
||||
|
||||
if len(tags) > 0 && filter.Tags == nil {
|
||||
filter.Tags = make(nostr.TagMap)
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if _, ok := filter.Tags[tag[0]]; !ok {
|
||||
filter.Tags[tag[0]] = make([]string, 0, 3)
|
||||
}
|
||||
filter.Tags[tag[0]] = append(filter.Tags[tag[0]], tag[1])
|
||||
}
|
||||
|
||||
if c.IsSet("since") {
|
||||
nts := getNaturalDate(c, "since")
|
||||
filter.Since = &nts
|
||||
}
|
||||
|
||||
if c.IsSet("until") {
|
||||
nts := getNaturalDate(c, "until")
|
||||
filter.Until = &nts
|
||||
}
|
||||
|
||||
if limit := c.Uint("limit"); limit != 0 {
|
||||
filter.Limit = int(limit)
|
||||
} else if c.IsSet("limit") || c.Bool("stream") {
|
||||
filter.LimitZero = true
|
||||
if err := applyFlagsToFilter(c, &filter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(relayUrls) > 0 {
|
||||
fn := pool.SubManyEose
|
||||
if c.Bool("stream") {
|
||||
fn = pool.SubMany
|
||||
fn := sys.Pool.SubManyEose
|
||||
if c.Bool("paginate") {
|
||||
fn = paginateWithParams(c.Duration("paginate-interval"), c.Uint("paginate-global-limit"))
|
||||
} else if c.Bool("stream") {
|
||||
fn = sys.Pool.SubMany
|
||||
}
|
||||
|
||||
for ie := range fn(ctx, relayUrls, nostr.Filters{filter}) {
|
||||
@@ -271,3 +150,130 @@ example:
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var reqFilterFlags = []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "author",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "only accept events from these authors (pubkey as hex)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "id",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "only accept events with these ids (hex)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.IntSliceFlag{
|
||||
Name: "kind",
|
||||
Aliases: []string{"k"},
|
||||
Usage: "only accept events with these kind numbers",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "tag",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "takes a tag like -t e=<id>, only accept events with these tags",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "e",
|
||||
Usage: "shortcut for --tag e=<value>",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "p",
|
||||
Usage: "shortcut for --tag p=<value>",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "d",
|
||||
Usage: "shortcut for --tag d=<value>",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&NaturalTimeFlag{
|
||||
Name: "since",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "only accept events newer than this (unix timestamp)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&NaturalTimeFlag{
|
||||
Name: "until",
|
||||
Aliases: []string{"u"},
|
||||
Usage: "only accept events older than this (unix timestamp)",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "limit",
|
||||
Aliases: []string{"l"},
|
||||
Usage: "only accept up to this number of events",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "search",
|
||||
Usage: "a NIP-50 search query, use it only with relays that explicitly support it",
|
||||
Category: CATEGORY_FILTER_ATTRIBUTES,
|
||||
},
|
||||
}
|
||||
|
||||
func applyFlagsToFilter(c *cli.Command, filter *nostr.Filter) error {
|
||||
if authors := c.StringSlice("author"); len(authors) > 0 {
|
||||
filter.Authors = append(filter.Authors, authors...)
|
||||
}
|
||||
if ids := c.StringSlice("id"); len(ids) > 0 {
|
||||
filter.IDs = append(filter.IDs, ids...)
|
||||
}
|
||||
for _, kind64 := range c.IntSlice("kind") {
|
||||
filter.Kinds = append(filter.Kinds, int(kind64))
|
||||
}
|
||||
if search := c.String("search"); search != "" {
|
||||
filter.Search = search
|
||||
}
|
||||
tags := make([][]string, 0, 5)
|
||||
for _, tagFlag := range c.StringSlice("tag") {
|
||||
spl := strings.Split(tagFlag, "=")
|
||||
if len(spl) == 2 && len(spl[0]) == 1 {
|
||||
tags = append(tags, spl)
|
||||
} else {
|
||||
return fmt.Errorf("invalid --tag '%s'", tagFlag)
|
||||
}
|
||||
}
|
||||
for _, etag := range c.StringSlice("e") {
|
||||
tags = append(tags, []string{"e", etag})
|
||||
}
|
||||
for _, ptag := range c.StringSlice("p") {
|
||||
tags = append(tags, []string{"p", ptag})
|
||||
}
|
||||
for _, dtag := range c.StringSlice("d") {
|
||||
tags = append(tags, []string{"d", dtag})
|
||||
}
|
||||
|
||||
if len(tags) > 0 && filter.Tags == nil {
|
||||
filter.Tags = make(nostr.TagMap)
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if _, ok := filter.Tags[tag[0]]; !ok {
|
||||
filter.Tags[tag[0]] = make([]string, 0, 3)
|
||||
}
|
||||
filter.Tags[tag[0]] = append(filter.Tags[tag[0]], tag[1])
|
||||
}
|
||||
|
||||
if c.IsSet("since") {
|
||||
nts := getNaturalDate(c, "since")
|
||||
filter.Since = &nts
|
||||
}
|
||||
|
||||
if c.IsSet("until") {
|
||||
nts := getNaturalDate(c, "until")
|
||||
filter.Until = &nts
|
||||
}
|
||||
|
||||
if limit := c.Uint("limit"); limit != 0 {
|
||||
filter.Limit = int(limit)
|
||||
} else if c.IsSet("limit") {
|
||||
filter.LimitZero = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
126
serve.go
Normal file
126
serve.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/bep/debounce"
|
||||
"github.com/fatih/color"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/fiatjaf/eventstore/slicestore"
|
||||
"github.com/fiatjaf/khatru"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
var serve = &cli.Command{
|
||||
Name: "serve",
|
||||
Usage: "starts an in-memory relay for testing purposes",
|
||||
DisableSliceFlagSeparator: true,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "hostname",
|
||||
Usage: "hostname where to listen for connections",
|
||||
Value: "localhost",
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "port",
|
||||
Usage: "port where to listen for connections",
|
||||
Value: 10547,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "events",
|
||||
Usage: "file containing the initial batch of events that will be served by the relay as newline-separated JSON (jsonl)",
|
||||
DefaultText: "the relay will start empty",
|
||||
},
|
||||
},
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
db := slicestore.SliceStore{MaxLimit: math.MaxInt}
|
||||
|
||||
var scanner *bufio.Scanner
|
||||
if path := c.String("events"); path != "" {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to file at '%s': %w", path, err)
|
||||
}
|
||||
scanner = bufio.NewScanner(f)
|
||||
} else if isPiped() {
|
||||
scanner = bufio.NewScanner(os.Stdin)
|
||||
}
|
||||
|
||||
if scanner != nil {
|
||||
scanner.Buffer(make([]byte, 16*1024*1024), 256*1024*1024)
|
||||
i := 0
|
||||
for scanner.Scan() {
|
||||
var evt nostr.Event
|
||||
if err := json.Unmarshal(scanner.Bytes(), &evt); err != nil {
|
||||
return fmt.Errorf("invalid event received at line %d: %s (`%s`)", i, err, scanner.Text())
|
||||
}
|
||||
db.SaveEvent(ctx, &evt)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
rl := khatru.NewRelay()
|
||||
rl.QueryEvents = append(rl.QueryEvents, db.QueryEvents)
|
||||
rl.CountEvents = append(rl.CountEvents, db.CountEvents)
|
||||
rl.DeleteEvent = append(rl.DeleteEvent, db.DeleteEvent)
|
||||
rl.StoreEvent = append(rl.StoreEvent, db.SaveEvent)
|
||||
|
||||
started := make(chan bool)
|
||||
exited := make(chan error)
|
||||
|
||||
hostname := c.String("hostname")
|
||||
port := int(c.Uint("port"))
|
||||
|
||||
go func() {
|
||||
err := rl.Start(hostname, port, started)
|
||||
exited <- err
|
||||
}()
|
||||
|
||||
bold := color.New(color.Bold).Sprintf
|
||||
italic := color.New(color.Italic).Sprint
|
||||
|
||||
var printStatus func()
|
||||
|
||||
// relay logging
|
||||
rl.RejectFilter = append(rl.RejectFilter, func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||
log(" got %s %v\n", color.HiYellowString("request"), italic(filter))
|
||||
printStatus()
|
||||
return false, ""
|
||||
})
|
||||
rl.RejectCountFilter = append(rl.RejectCountFilter, func(ctx context.Context, filter nostr.Filter) (reject bool, msg string) {
|
||||
log(" got %s %v\n", color.HiCyanString("count request"), italic(filter))
|
||||
printStatus()
|
||||
return false, ""
|
||||
})
|
||||
rl.RejectEvent = append(rl.RejectEvent, func(ctx context.Context, event *nostr.Event) (reject bool, msg string) {
|
||||
log(" got %s %v\n", color.BlueString("event"), italic(event))
|
||||
printStatus()
|
||||
return false, ""
|
||||
})
|
||||
|
||||
d := debounce.New(time.Second * 2)
|
||||
printStatus = func() {
|
||||
d(func() {
|
||||
totalEvents := 0
|
||||
ch, _ := db.QueryEvents(ctx, nostr.Filter{})
|
||||
for range ch {
|
||||
totalEvents++
|
||||
}
|
||||
subs := rl.GetListeningFilters()
|
||||
|
||||
log(" %s events stored: %s, subscriptions opened: %s\n", color.HiMagentaString("•"), color.HiMagentaString("%d", totalEvents), color.HiMagentaString("%d", len(subs)))
|
||||
})
|
||||
}
|
||||
|
||||
<-started
|
||||
log("%s relay running at %s\n", color.HiRedString(">"), bold("ws://%s:%d", hostname, port))
|
||||
|
||||
return <-exited
|
||||
},
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/fiatjaf/cli/v3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
var verify = &cli.Command{
|
||||
@@ -15,6 +15,7 @@ var verify = &cli.Command{
|
||||
echo '{"id":"a889df6a387419ff204305f4c2d296ee328c3cd4f8b62f205648a541b4554dfb","pubkey":"c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5","created_at":1698623783,"kind":1,"tags":[],"content":"hello from the nostr army knife","sig":"84876e1ee3e726da84e5d195eb79358b2b3eaa4d9bd38456fde3e8a2af3f1cd4cda23f23fda454869975b3688797d4c66e12f4c51c1b43c6d2997c5e61865661"}' | nak verify
|
||||
|
||||
it outputs nothing if the verification is successful.`,
|
||||
DisableSliceFlagSeparator: true,
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
for stdinEvent := range getStdinLinesOrArguments(c.Args()) {
|
||||
evt := nostr.Event{}
|
||||
@@ -31,7 +32,7 @@ it outputs nothing if the verification is successful.`,
|
||||
}
|
||||
|
||||
if ok, err := evt.CheckSignature(); !ok {
|
||||
ctx = lineProcessingError(ctx, "invalid signature: %s", err)
|
||||
ctx = lineProcessingError(ctx, "invalid signature: %v", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user