identity.go raw
1 package relay
2
3 import (
4 "crypto/rand"
5 "encoding/hex"
6 "errors"
7 "os"
8 "path/filepath"
9 "strings"
10
11 "git.smesh.lol/torly/pkg/config"
12 "git.smesh.lol/torly/pkg/crypto/secp256k1"
13 "git.smesh.lol/torly/pkg/nostr"
14 )
15
16 // loadRelayKey resolves the key torly uses to sign its own events (today the
17 // _graph result). The key comes from TORLY_SECRET_KEY when set, otherwise from
18 // DATA_DIR/relay.key, which is created on first start. A relay identity must
19 // survive restarts, so a key file is preferred over an ephemeral key.
20 func loadRelayKey(cfg *config.Config) ([32]byte, error) {
21 var sk [32]byte
22 if raw := strings.TrimSpace(cfg.SecretKey); raw != "" {
23 if err := nostr.Decode32(raw, sk[:]); err != nil {
24 return sk, errors.New("secret key must be 64 lowercase hex characters")
25 }
26 } else {
27 path := filepath.Join(cfg.DataDir, "relay.key")
28 body, err := os.ReadFile(path)
29 switch {
30 case err == nil:
31 if err := decodeKeyFile(body, sk[:]); err != nil {
32 return sk, err
33 }
34 case os.IsNotExist(err):
35 if err := createRelayKey(path, &sk); err != nil {
36 return sk, err
37 }
38 default:
39 return sk, err
40 }
41 }
42 pub, ok := secp256k1.PubKey(sk)
43 if !ok {
44 return sk, errors.New("invalid relay secret key")
45 }
46 cfg.RelayPubKey = nostr.Hex(pub[:])
47 return sk, nil
48 }
49
50 // decodeKeyFile accepts either the 32 raw bytes or the 64-hex text form, so a
51 // key generated by nak or another tool can be dropped in unchanged.
52 func decodeKeyFile(body, dst []byte) error {
53 if len(body) == 32 {
54 copy(dst, body)
55 return nil
56 }
57 text := strings.TrimSpace(string(body))
58 if err := nostr.Decode32(text, dst); err != nil {
59 return errors.New("relay key file must contain 32 raw bytes or 64 hex characters")
60 }
61 return nil
62 }
63
64 func createRelayKey(path string, dst *[32]byte) error {
65 for {
66 if _, err := rand.Read(dst[:]); err != nil {
67 return err
68 }
69 if _, ok := secp256k1.PubKey(*dst); ok {
70 break
71 }
72 }
73 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
74 return err
75 }
76 tmp := path + ".tmp"
77 text := hex.EncodeToString(dst[:]) + "\n"
78 if err := os.WriteFile(tmp, []byte(text), 0o600); err != nil {
79 return err
80 }
81 return os.Rename(tmp, path)
82 }
83