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