nip43.go raw

   1  //go:build !(js && wasm)
   2  
   3  package database
   4  
   5  import (
   6  	"encoding/binary"
   7  	"fmt"
   8  	"time"
   9  
  10  	"github.com/dgraph-io/badger/v4"
  11  	"next.orly.dev/pkg/lol/chk"
  12  	"next.orly.dev/pkg/lol/log"
  13  	"next.orly.dev/pkg/nostr/encoders/hex"
  14  )
  15  
  16  // Database key prefixes for NIP-43
  17  const (
  18  	nip43MemberPrefix = "nip43:member:"
  19  	nip43InvitePrefix = "nip43:invite:"
  20  )
  21  
  22  // AddNIP43Member adds a member to the NIP-43 membership list
  23  func (d *D) AddNIP43Member(pubkey []byte, inviteCode string) error {
  24  	if len(pubkey) != 32 {
  25  		return fmt.Errorf("invalid pubkey length: %d", len(pubkey))
  26  	}
  27  
  28  	key := append([]byte(nip43MemberPrefix), pubkey...)
  29  
  30  	// Create membership record
  31  	membership := NIP43Membership{
  32  		Pubkey:     pubkey,
  33  		AddedAt:    time.Now(),
  34  		InviteCode: inviteCode,
  35  	}
  36  
  37  	// Serialize membership data
  38  	val := serializeNIP43Membership(membership)
  39  
  40  	return d.DB.Update(func(txn *badger.Txn) error {
  41  		return txn.Set(key, val)
  42  	})
  43  }
  44  
  45  // RemoveNIP43Member removes a member from the NIP-43 membership list
  46  func (d *D) RemoveNIP43Member(pubkey []byte) error {
  47  	if len(pubkey) != 32 {
  48  		return fmt.Errorf("invalid pubkey length: %d", len(pubkey))
  49  	}
  50  
  51  	key := append([]byte(nip43MemberPrefix), pubkey...)
  52  
  53  	return d.DB.Update(func(txn *badger.Txn) error {
  54  		return txn.Delete(key)
  55  	})
  56  }
  57  
  58  // IsNIP43Member checks if a pubkey is a NIP-43 member
  59  func (d *D) IsNIP43Member(pubkey []byte) (isMember bool, err error) {
  60  	if len(pubkey) != 32 {
  61  		return false, fmt.Errorf("invalid pubkey length: %d", len(pubkey))
  62  	}
  63  
  64  	key := append([]byte(nip43MemberPrefix), pubkey...)
  65  
  66  	err = d.DB.View(func(txn *badger.Txn) error {
  67  		_, err := txn.Get(key)
  68  		if err == badger.ErrKeyNotFound {
  69  			isMember = false
  70  			return nil
  71  		}
  72  		if err != nil {
  73  			return err
  74  		}
  75  		isMember = true
  76  		return nil
  77  	})
  78  
  79  	return isMember, err
  80  }
  81  
  82  // GetNIP43Membership retrieves membership details for a pubkey
  83  func (d *D) GetNIP43Membership(pubkey []byte) (*NIP43Membership, error) {
  84  	if len(pubkey) != 32 {
  85  		return nil, fmt.Errorf("invalid pubkey length: %d", len(pubkey))
  86  	}
  87  
  88  	key := append([]byte(nip43MemberPrefix), pubkey...)
  89  	var membership *NIP43Membership
  90  
  91  	err := d.DB.View(func(txn *badger.Txn) error {
  92  		item, err := txn.Get(key)
  93  		if err != nil {
  94  			return err
  95  		}
  96  
  97  		return item.Value(func(val []byte) error {
  98  			membership = deserializeNIP43Membership(val)
  99  			return nil
 100  		})
 101  	})
 102  
 103  	if err != nil {
 104  		return nil, err
 105  	}
 106  
 107  	return membership, nil
 108  }
 109  
 110  // GetAllNIP43Members returns all NIP-43 members
 111  func (d *D) GetAllNIP43Members() ([][]byte, error) {
 112  	var members [][]byte
 113  	prefix := []byte(nip43MemberPrefix)
 114  
 115  	err := d.DB.View(func(txn *badger.Txn) error {
 116  		opts := badger.DefaultIteratorOptions
 117  		opts.Prefix = prefix
 118  		opts.PrefetchValues = false // We only need keys
 119  
 120  		it := txn.NewIterator(opts)
 121  		defer it.Close()
 122  
 123  		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
 124  			item := it.Item()
 125  			key := item.Key()
 126  			// Extract pubkey from key (skip prefix)
 127  			pubkey := make([]byte, 32)
 128  			copy(pubkey, key[len(prefix):])
 129  			members = append(members, pubkey)
 130  		}
 131  
 132  		return nil
 133  	})
 134  
 135  	return members, err
 136  }
 137  
 138  // StoreInviteCode stores an invite code with expiry
 139  func (d *D) StoreInviteCode(code string, expiresAt time.Time) error {
 140  	key := append([]byte(nip43InvitePrefix), []byte(code)...)
 141  
 142  	// Serialize expiry time as unix timestamp
 143  	val := make([]byte, 8)
 144  	binary.BigEndian.PutUint64(val, uint64(expiresAt.Unix()))
 145  
 146  	return d.DB.Update(func(txn *badger.Txn) error {
 147  		entry := badger.NewEntry(key, val).WithTTL(time.Until(expiresAt))
 148  		return txn.SetEntry(entry)
 149  	})
 150  }
 151  
 152  // ValidateInviteCode checks if an invite code is valid and not expired
 153  func (d *D) ValidateInviteCode(code string) (valid bool, err error) {
 154  	key := append([]byte(nip43InvitePrefix), []byte(code)...)
 155  
 156  	err = d.DB.View(func(txn *badger.Txn) error {
 157  		item, err := txn.Get(key)
 158  		if err == badger.ErrKeyNotFound {
 159  			valid = false
 160  			return nil
 161  		}
 162  		if err != nil {
 163  			return err
 164  		}
 165  
 166  		return item.Value(func(val []byte) error {
 167  			if len(val) != 8 {
 168  				return fmt.Errorf("invalid invite code value")
 169  			}
 170  			expiresAt := int64(binary.BigEndian.Uint64(val))
 171  			valid = time.Now().Unix() < expiresAt
 172  			return nil
 173  		})
 174  	})
 175  
 176  	return valid, err
 177  }
 178  
 179  // DeleteInviteCode removes an invite code (after use)
 180  func (d *D) DeleteInviteCode(code string) error {
 181  	key := append([]byte(nip43InvitePrefix), []byte(code)...)
 182  
 183  	return d.DB.Update(func(txn *badger.Txn) error {
 184  		return txn.Delete(key)
 185  	})
 186  }
 187  
 188  // Helper functions for serialization
 189  
 190  func serializeNIP43Membership(m NIP43Membership) []byte {
 191  	// Format: [pubkey(32)] [timestamp(8)] [invite_code_len(2)] [invite_code]
 192  	codeBytes := []byte(m.InviteCode)
 193  	codeLen := len(codeBytes)
 194  
 195  	buf := make([]byte, 32+8+2+codeLen)
 196  
 197  	// Copy pubkey
 198  	copy(buf[0:32], m.Pubkey)
 199  
 200  	// Write timestamp
 201  	binary.BigEndian.PutUint64(buf[32:40], uint64(m.AddedAt.Unix()))
 202  
 203  	// Write invite code length
 204  	binary.BigEndian.PutUint16(buf[40:42], uint16(codeLen))
 205  
 206  	// Write invite code
 207  	copy(buf[42:], codeBytes)
 208  
 209  	return buf
 210  }
 211  
 212  func deserializeNIP43Membership(data []byte) *NIP43Membership {
 213  	if len(data) < 42 {
 214  		return nil
 215  	}
 216  
 217  	m := &NIP43Membership{}
 218  
 219  	// Read pubkey
 220  	m.Pubkey = make([]byte, 32)
 221  	copy(m.Pubkey, data[0:32])
 222  
 223  	// Read timestamp
 224  	timestamp := binary.BigEndian.Uint64(data[32:40])
 225  	m.AddedAt = time.Unix(int64(timestamp), 0)
 226  
 227  	// Read invite code
 228  	codeLen := binary.BigEndian.Uint16(data[40:42])
 229  	if len(data) >= 42+int(codeLen) {
 230  		m.InviteCode = string(data[42 : 42+codeLen])
 231  	}
 232  
 233  	return m
 234  }
 235  
 236  // PublishNIP43MembershipEvent publishes membership change events
 237  func (d *D) PublishNIP43MembershipEvent(kind int, pubkey []byte) error {
 238  	log.I.F("publishing NIP-43 event kind %d for pubkey %s", kind, hex.Enc(pubkey))
 239  
 240  	// Get relay identity
 241  	relaySecret, err := d.GetOrCreateRelayIdentitySecret()
 242  	if chk.E(err) {
 243  		return err
 244  	}
 245  
 246  	// This would integrate with the event publisher
 247  	// For now, just log it
 248  	log.D.F("would publish kind %d event for member %s", kind, hex.Enc(pubkey))
 249  
 250  	// The actual publishing will be done by the handler
 251  	_ = relaySecret
 252  
 253  	return nil
 254  }
 255