helpers.go raw

   1  package directory
   2  
   3  import (
   4  	"github.com/minio/sha256-simd"
   5  	"encoding/hex"
   6  	"fmt"
   7  	"strconv"
   8  	"strings"
   9  	"time"
  10  
  11  	"next.orly.dev/pkg/lol/chk"
  12  	"next.orly.dev/pkg/lol/errorf"
  13  	"next.orly.dev/pkg/nostr/crypto/ec/schnorr"
  14  	"next.orly.dev/pkg/nostr/crypto/ec/secp256k1"
  15  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  16  	"next.orly.dev/pkg/nostr/encoders/bech32encoding"
  17  	"next.orly.dev/pkg/nostr/encoders/event"
  18  )
  19  
  20  // IdentityTagBuilder helps construct identity tags with proper signatures.
  21  type IdentityTagBuilder struct {
  22  	identityPrivkey []byte
  23  	identityPubkey  []byte
  24  	npubIdentity    string
  25  }
  26  
  27  // NewIdentityTagBuilder creates a new identity tag builder with the given
  28  // identity private key.
  29  func NewIdentityTagBuilder(identityPrivkey []byte) (builder *IdentityTagBuilder, err error) {
  30  	if len(identityPrivkey) != 32 {
  31  		return nil, errorf.E("identity private key must be 32 bytes")
  32  	}
  33  
  34  	// Derive public key from secret key using p8k signer
  35  	var signer *p8k.Signer
  36  	if signer, err = p8k.New(); chk.E(err) {
  37  		return nil, errorf.E("failed to create signer: %w", err)
  38  	}
  39  	if err = signer.InitSec(identityPrivkey); chk.E(err) {
  40  		return nil, errorf.E("failed to initialize signer: %w", err)
  41  	}
  42  	identityPubkeyBytes := signer.Pub()
  43  
  44  	// Parse public key for npub encoding
  45  	var identityPubkey *secp256k1.PublicKey
  46  	if identityPubkey, err = schnorr.ParsePubKey(identityPubkeyBytes); chk.E(err) {
  47  		return nil, errorf.E("failed to parse public key: %w", err)
  48  	}
  49  
  50  	// Encode as npub
  51  	var npubIdentity []byte
  52  	if npubIdentity, err = bech32encoding.PublicKeyToNpub(identityPubkey); chk.E(err) {
  53  		return nil, errorf.E("failed to encode npub: %w", err)
  54  	}
  55  
  56  	return &IdentityTagBuilder{
  57  		identityPrivkey: identityPrivkey,
  58  		identityPubkey:  identityPubkeyBytes,
  59  		npubIdentity:    string(npubIdentity),
  60  	}, nil
  61  }
  62  
  63  // CreateIdentityTag creates a signed identity tag for the given delegate pubkey.
  64  func (builder *IdentityTagBuilder) CreateIdentityTag(delegatePubkey []byte) (identityTag *IdentityTag, err error) {
  65  	if len(delegatePubkey) != 32 {
  66  		return nil, errorf.E("delegate pubkey must be 32 bytes")
  67  	}
  68  
  69  	// Generate nonce
  70  	var nonceHex string
  71  	if nonceHex, err = GenerateNonceHex(16); chk.E(err) {
  72  		return nil, errorf.E("failed to generate nonce: %w", err)
  73  	}
  74  
  75  	// Create message: nonce + delegate_pubkey_hex + identity_pubkey_hex
  76  	delegatePubkeyHex := hex.EncodeToString(delegatePubkey)
  77  	identityPubkeyHex := hex.EncodeToString(builder.identityPubkey)
  78  	message := nonceHex + delegatePubkeyHex + identityPubkeyHex
  79  
  80  	// Hash and sign using p8k signer
  81  	hash := sha256.Sum256([]byte(message))
  82  	var signer *p8k.Signer
  83  	if signer, err = p8k.New(); chk.E(err) {
  84  		return nil, errorf.E("failed to create signer: %w", err)
  85  	}
  86  	if err = signer.InitSec(builder.identityPrivkey); chk.E(err) {
  87  		return nil, errorf.E("failed to initialize signer: %w", err)
  88  	}
  89  	var signature []byte
  90  	if signature, err = signer.Sign(hash[:]); chk.E(err) {
  91  		return nil, errorf.E("failed to sign identity tag: %w", err)
  92  	}
  93  
  94  	identityTag = &IdentityTag{
  95  		NPubIdentity: builder.npubIdentity,
  96  		Nonce:        nonceHex,
  97  		Signature:    hex.EncodeToString(signature),
  98  	}
  99  
 100  	return
 101  }
 102  
 103  // GetNPubIdentity returns the npub-encoded identity.
 104  func (builder *IdentityTagBuilder) GetNPubIdentity() string {
 105  	return builder.npubIdentity
 106  }
 107  
 108  // GetIdentityPubkey returns the raw identity public key.
 109  func (builder *IdentityTagBuilder) GetIdentityPubkey() []byte {
 110  	return builder.identityPubkey
 111  }
 112  
 113  // KeyPoolManager helps manage HD key derivation and advertisement.
 114  type KeyPoolManager struct {
 115  	masterSeed     []byte
 116  	identityIndex  uint32
 117  	currentIndices map[KeyPurpose]int
 118  }
 119  
 120  // NewKeyPoolManager creates a new key pool manager with the given master seed.
 121  func NewKeyPoolManager(masterSeed []byte, identityIndex uint32) *KeyPoolManager {
 122  	return &KeyPoolManager{
 123  		masterSeed:     masterSeed,
 124  		identityIndex:  identityIndex,
 125  		currentIndices: make(map[KeyPurpose]int),
 126  	}
 127  }
 128  
 129  // GenerateDerivationPath creates a BIP32 derivation path for the given purpose and index.
 130  func (kpm *KeyPoolManager) GenerateDerivationPath(purpose KeyPurpose, index int) string {
 131  	var usageIndex int
 132  	switch purpose {
 133  	case KeyPurposeSigning:
 134  		usageIndex = 0
 135  	case KeyPurposeEncryption:
 136  		usageIndex = 1
 137  	case KeyPurposeDelegation:
 138  		usageIndex = 2
 139  	default:
 140  		usageIndex = 0
 141  	}
 142  
 143  	return fmt.Sprintf("m/39103'/1237'/%d'/%d/%d", kpm.identityIndex, usageIndex, index)
 144  }
 145  
 146  // GetNextKeyIndex returns the next available key index for the given purpose.
 147  func (kpm *KeyPoolManager) GetNextKeyIndex(purpose KeyPurpose) int {
 148  	current := kpm.currentIndices[purpose]
 149  	kpm.currentIndices[purpose] = current + 1
 150  	return current
 151  }
 152  
 153  // SetKeyIndex sets the current key index for the given purpose.
 154  func (kpm *KeyPoolManager) SetKeyIndex(purpose KeyPurpose, index int) {
 155  	kpm.currentIndices[purpose] = index
 156  }
 157  
 158  // GetCurrentKeyIndex returns the current key index for the given purpose.
 159  func (kpm *KeyPoolManager) GetCurrentKeyIndex(purpose KeyPurpose) int {
 160  	return kpm.currentIndices[purpose]
 161  }
 162  
 163  // TrustCalculator helps calculate trust scores and inheritance.
 164  type TrustCalculator struct {
 165  	acts map[string]*TrustAct
 166  }
 167  
 168  // NewTrustCalculator creates a new trust calculator.
 169  func NewTrustCalculator() *TrustCalculator {
 170  	return &TrustCalculator{
 171  		acts: make(map[string]*TrustAct),
 172  	}
 173  }
 174  
 175  // AddAct adds a trust act to the calculator.
 176  func (tc *TrustCalculator) AddAct(act *TrustAct) {
 177  	key := act.GetTargetPubkey()
 178  	tc.acts[key] = act
 179  }
 180  
 181  // GetTrustLevel returns the trust level for a given pubkey.
 182  func (tc *TrustCalculator) GetTrustLevel(pubkey string) TrustLevel {
 183  	if act, exists := tc.acts[pubkey]; exists {
 184  		if !act.IsExpired() {
 185  			return act.GetTrustLevel()
 186  		}
 187  	}
 188  	return TrustLevelNone // Return 0 for no trust
 189  }
 190  
 191  // CalculateInheritedTrust calculates inherited trust through the web of trust.
 192  // With numeric trust levels, inherited trust is calculated by multiplying
 193  // the trust percentages at each hop, reducing trust over distance.
 194  func (tc *TrustCalculator) CalculateInheritedTrust(
 195  	fromPubkey, toPubkey string,
 196  ) TrustLevel {
 197  	// Direct trust
 198  	if directTrust := tc.GetTrustLevel(toPubkey); directTrust > 0 {
 199  		return directTrust
 200  	}
 201  
 202  	// Look for inherited trust through intermediate nodes
 203  	var maxInheritedTrust TrustLevel = 0
 204  	for intermediatePubkey, act := range tc.acts {
 205  		if act.IsExpired() {
 206  			continue
 207  		}
 208  
 209  		// Check if we trust the intermediate node
 210  		intermediateLevel := tc.GetTrustLevel(intermediatePubkey)
 211  		if intermediateLevel == 0 {
 212  			continue
 213  		}
 214  
 215  		// Check if intermediate node trusts the target
 216  		targetLevel := tc.GetTrustLevel(toPubkey)
 217  		if targetLevel == 0 {
 218  			continue
 219  		}
 220  
 221  		// Calculate inherited trust level (multiply percentages)
 222  		inheritedLevel := tc.combinesTrustLevels(intermediateLevel, targetLevel)
 223  		if inheritedLevel > maxInheritedTrust {
 224  			maxInheritedTrust = inheritedLevel
 225  		}
 226  	}
 227  
 228  	return maxInheritedTrust
 229  }
 230  
 231  // combinesTrustLevels combines two trust levels to calculate inherited trust.
 232  // With numeric trust levels (0-100), inherited trust is calculated by
 233  // multiplying the two percentages: (level1 * level2) / 100
 234  // This naturally reduces trust over distance.
 235  func (tc *TrustCalculator) combinesTrustLevels(level1, level2 TrustLevel) TrustLevel {
 236  	// Multiply percentages: (level1% * level2%) = (level1 * level2) / 100
 237  	// Example: 75% trust * 50% trust = 37.5% inherited trust
 238  	combined := (uint16(level1) * uint16(level2)) / 100
 239  	return TrustLevel(combined)
 240  }
 241  
 242  // ReplicationFilter helps determine which events should be replicated.
 243  type ReplicationFilter struct {
 244  	trustCalculator *TrustCalculator
 245  	acts            map[string]*TrustAct
 246  }
 247  
 248  // NewReplicationFilter creates a new replication filter.
 249  func NewReplicationFilter(trustCalculator *TrustCalculator) *ReplicationFilter {
 250  	return &ReplicationFilter{
 251  		trustCalculator: trustCalculator,
 252  		acts:            make(map[string]*TrustAct),
 253  	}
 254  }
 255  
 256  // AddTrustAct adds a trust act to the filter.
 257  func (rf *ReplicationFilter) AddTrustAct(act *TrustAct) {
 258  	rf.acts[act.GetTargetPubkey()] = act
 259  }
 260  
 261  // ShouldReplicate determines if an event should be replicated to a target relay.
 262  func (rf *ReplicationFilter) ShouldReplicate(ev *event.E, targetPubkey string) bool {
 263  	act, exists := rf.acts[targetPubkey]
 264  	if !exists || act.IsExpired() {
 265  		return false
 266  	}
 267  
 268  	return act.ShouldReplicate(ev.Kind)
 269  }
 270  
 271  // GetReplicationTargets returns all target relays that should receive an event.
 272  func (rf *ReplicationFilter) GetReplicationTargets(ev *event.E) []string {
 273  	var targets []string
 274  
 275  	for pubkey, act := range rf.acts {
 276  		if !act.IsExpired() && act.ShouldReplicate(ev.Kind) {
 277  			targets = append(targets, pubkey)
 278  		}
 279  	}
 280  
 281  	return targets
 282  }
 283  
 284  // EventBatcher helps batch events for efficient replication.
 285  type EventBatcher struct {
 286  	maxBatchSize int
 287  	batches      map[string][]*event.E
 288  }
 289  
 290  // NewEventBatcher creates a new event batcher.
 291  func NewEventBatcher(maxBatchSize int) *EventBatcher {
 292  	if maxBatchSize <= 0 {
 293  		maxBatchSize = 100 // Default batch size
 294  	}
 295  
 296  	return &EventBatcher{
 297  		maxBatchSize: maxBatchSize,
 298  		batches:      make(map[string][]*event.E),
 299  	}
 300  }
 301  
 302  // AddEvent adds an event to the batch for a target relay.
 303  func (eb *EventBatcher) AddEvent(targetRelay string, ev *event.E) {
 304  	eb.batches[targetRelay] = append(eb.batches[targetRelay], ev)
 305  }
 306  
 307  // GetBatch returns the current batch for a target relay.
 308  func (eb *EventBatcher) GetBatch(targetRelay string) []*event.E {
 309  	return eb.batches[targetRelay]
 310  }
 311  
 312  // IsBatchFull returns true if the batch for a target relay is full.
 313  func (eb *EventBatcher) IsBatchFull(targetRelay string) bool {
 314  	return len(eb.batches[targetRelay]) >= eb.maxBatchSize
 315  }
 316  
 317  // FlushBatch returns and clears the batch for a target relay.
 318  func (eb *EventBatcher) FlushBatch(targetRelay string) []*event.E {
 319  	batch := eb.batches[targetRelay]
 320  	eb.batches[targetRelay] = nil
 321  	return batch
 322  }
 323  
 324  // GetAllBatches returns all current batches.
 325  func (eb *EventBatcher) GetAllBatches() map[string][]*event.E {
 326  	result := make(map[string][]*event.E)
 327  	for relay, batch := range eb.batches {
 328  		if len(batch) > 0 {
 329  			result[relay] = batch
 330  		}
 331  	}
 332  	return result
 333  }
 334  
 335  // FlushAllBatches returns and clears all batches.
 336  func (eb *EventBatcher) FlushAllBatches() map[string][]*event.E {
 337  	result := eb.GetAllBatches()
 338  	eb.batches = make(map[string][]*event.E)
 339  	return result
 340  }
 341  
 342  // Utility functions
 343  
 344  // ParseKindsList parses a comma-separated list of event kinds.
 345  func ParseKindsList(kindsStr string) (kinds []uint16, err error) {
 346  	if kindsStr == "" {
 347  		return nil, nil
 348  	}
 349  
 350  	kindStrings := strings.Split(kindsStr, ",")
 351  	for _, kindStr := range kindStrings {
 352  		kindStr = strings.TrimSpace(kindStr)
 353  		if kindStr == "" {
 354  			continue
 355  		}
 356  
 357  		var kind uint64
 358  		if kind, err = strconv.ParseUint(kindStr, 10, 16); chk.E(err) {
 359  			return nil, errorf.E("invalid kind: %s", kindStr)
 360  		}
 361  
 362  		kinds = append(kinds, uint16(kind))
 363  	}
 364  
 365  	return
 366  }
 367  
 368  // FormatKindsList formats a list of event kinds as a comma-separated string.
 369  func FormatKindsList(kinds []uint16) string {
 370  	if len(kinds) == 0 {
 371  		return ""
 372  	}
 373  
 374  	var kindStrings []string
 375  	for _, kind := range kinds {
 376  		kindStrings = append(kindStrings, strconv.FormatUint(uint64(kind), 10))
 377  	}
 378  
 379  	return strings.Join(kindStrings, ",")
 380  }
 381  
 382  // GenerateRequestID generates a unique request ID for replication requests.
 383  func GenerateRequestID() (requestID string, err error) {
 384  	// Use timestamp + random nonce for uniqueness
 385  	timestamp := time.Now().Unix()
 386  	var nonce string
 387  	if nonce, err = GenerateNonceHex(8); chk.E(err) {
 388  		return
 389  	}
 390  
 391  	requestID = fmt.Sprintf("%d-%s", timestamp, nonce)
 392  	return
 393  }
 394  
 395  // CreateSuccessResponse creates a successful replication response.
 396  func CreateSuccessResponse(
 397  	pubkey []byte,
 398  	requestID, sourceRelay string,
 399  	eventResults []*EventResult,
 400  ) (response *DirectoryEventReplicationResponse, err error) {
 401  	return NewDirectoryEventReplicationResponse(
 402  		pubkey,
 403  		requestID,
 404  		ReplicationStatusSuccess,
 405  		"",
 406  		sourceRelay,
 407  		eventResults,
 408  	)
 409  }
 410  
 411  // CreateErrorResponse creates an error replication response.
 412  func CreateErrorResponse(
 413  	pubkey []byte,
 414  	requestID, sourceRelay, errorMsg string,
 415  ) (response *DirectoryEventReplicationResponse, err error) {
 416  	return NewDirectoryEventReplicationResponse(
 417  		pubkey,
 418  		requestID,
 419  		ReplicationStatusError,
 420  		errorMsg,
 421  		sourceRelay,
 422  		nil,
 423  	)
 424  }
 425  
 426  // CreateEventResult creates an event result for a replication response.
 427  func CreateEventResult(eventID string, success bool, errorMsg string) *EventResult {
 428  	status := ReplicationStatusSuccess
 429  	if !success {
 430  		status = ReplicationStatusError
 431  	}
 432  
 433  	return &EventResult{
 434  		EventID: eventID,
 435  		Status:  status,
 436  		Error:   errorMsg,
 437  	}
 438  }
 439