clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/tinyagent
tinyrelay / internal / syncprotocol / hll.go
hll.go 2.9K
1package syncprotocol23import (4 "crypto/sha256"5 "encoding/hex"6 "fmt"7 "strconv"8 "strings"910 "github.com/FelineStateMachine/tinyrelay/internal/event"11)1213const hllRegisters = 2561415// HLL is the NIP-45 256-register sketch used to merge counts from relays.16type HLL struct {17 offset int18 regs [hllRegisters]uint819}2021func NewHLL(offset int) *HLL {22 if offset < 0 || offset >= 32 {23 return &HLL{}24 }25 return &HLL{offset: offset}26}2728// HLLFilterOffset derives the register byte offset from the single tag value29// used by the relay's count filter. Hashed values and address-style values are30// both accepted.31func HLLFilterOffset(f event.Filter) (int, bool) {32 if len(f.Tags) != 1 {33 return 0, false34 }35 for _, values := range f.Tags {36 if len(values) == 0 {37 return 0, false38 }39 value := values[0]40 parts := strings.Split(value, ":")41 if len(parts) == 3 && len(parts[1]) == 64 {42 value = parts[1]43 }44 if len(value) != 64 || !isLowerHex(value) {45 sum := sha256.Sum256([]byte(value))46 value = hex.EncodeToString(sum[:])47 }48 digit, err := strconv.ParseUint(string(value[32]), 16, 4)49 if err != nil {50 return 0, false51 }52 return int(digit) + 8, true53 }54 return 0, false55}5657// Add incorporates one 32-byte hexadecimal public key into the sketch.58func (h *HLL) Add(pubkeyHex string) error {59 if len(pubkeyHex) != 64 || !isHexCharacters(pubkeyHex) {60 return fmt.Errorf("invalid pubkey: expected 32-byte hex")61 }62 key, err := hex.DecodeString(pubkeyHex)63 if err != nil {64 return fmt.Errorf("decode pubkey: %w", err)65 }66 register := key[h.offset]67 zeros := 068 for i := h.offset + 1; i < len(key); i++ {69 if key[i] == 0 {70 zeros += 871 continue72 }73 zeros += leadingZeros8(key[i])74 break75 }76 if value := uint8(zeros + 1); value > h.regs[register] {77 h.regs[register] = value78 }79 return nil80}8182// Hex returns the 256 registers in their wire hexadecimal form.83func (h *HLL) Hex() string { return hex.EncodeToString(h.regs[:]) }8485// MergeHex unions another 256-register sketch into h.86func (h *HLL) MergeHex(encoded string) error {87 if len(encoded) != hllRegisters*2 {88 return fmt.Errorf("invalid HLL: expected %d hex characters", hllRegisters*2)89 }90 registers, err := hex.DecodeString(encoded)91 if err != nil || len(registers) != hllRegisters {92 return fmt.Errorf("invalid HLL registers")93 }94 for i, value := range registers {95 if value > h.regs[i] {96 h.regs[i] = value97 }98 }99 return nil100}101102func leadingZeros8(value byte) int {103 zeros := 0104 for bit := byte(0x80); value&bit == 0; bit >>= 1 {105 zeros++106 }107 return zeros108}109110func isLowerHex(value string) bool {111 for _, char := range value {112 if !(char >= '0' && char <= '9' || char >= 'a' && char <= 'f') {113 return false114 }115 }116 return true117}118119func isHexCharacters(value string) bool {120 for _, char := range value {121 if !(char >= '0' && char <= '9' || char >= 'a' && char <= 'f' || char >= 'A' && char <= 'F') {122 return false123 }124 }125 return true126}