| 1 | package syncprotocol |
| 2 | |
| 3 | import ( |
| 4 | "crypto/sha256" |
| 5 | "encoding/hex" |
| 6 | "fmt" |
| 7 | "strconv" |
| 8 | "strings" |
| 9 | |
| 10 | "github.com/FelineStateMachine/tinyrelay/internal/event" |
| 11 | ) |
| 12 | |
| 13 | const hllRegisters = 256 |
| 14 | |
| 15 | // HLL is the NIP-45 256-register sketch used to merge counts from relays. |
| 16 | type HLL struct { |
| 17 | offset int |
| 18 | regs [hllRegisters]uint8 |
| 19 | } |
| 20 | |
| 21 | func NewHLL(offset int) *HLL { |
| 22 | if offset < 0 || offset >= 32 { |
| 23 | return &HLL{} |
| 24 | } |
| 25 | return &HLL{offset: offset} |
| 26 | } |
| 27 | |
| 28 | // HLLFilterOffset derives the register byte offset from the single tag value |
| 29 | // used by the relay's count filter. Hashed values and address-style values are |
| 30 | // both accepted. |
| 31 | func HLLFilterOffset(f event.Filter) (int, bool) { |
| 32 | if len(f.Tags) != 1 { |
| 33 | return 0, false |
| 34 | } |
| 35 | for _, values := range f.Tags { |
| 36 | if len(values) == 0 { |
| 37 | return 0, false |
| 38 | } |
| 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, false |
| 51 | } |
| 52 | return int(digit) + 8, true |
| 53 | } |
| 54 | return 0, false |
| 55 | } |
| 56 | |
| 57 | // Add incorporates one 32-byte hexadecimal public key into the sketch. |
| 58 | func (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 := 0 |
| 68 | for i := h.offset + 1; i < len(key); i++ { |
| 69 | if key[i] == 0 { |
| 70 | zeros += 8 |
| 71 | continue |
| 72 | } |
| 73 | zeros += leadingZeros8(key[i]) |
| 74 | break |
| 75 | } |
| 76 | if value := uint8(zeros + 1); value > h.regs[register] { |
| 77 | h.regs[register] = value |
| 78 | } |
| 79 | return nil |
| 80 | } |
| 81 | |
| 82 | // Hex returns the 256 registers in their wire hexadecimal form. |
| 83 | func (h *HLL) Hex() string { return hex.EncodeToString(h.regs[:]) } |
| 84 | |
| 85 | // MergeHex unions another 256-register sketch into h. |
| 86 | func (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] = value |
| 97 | } |
| 98 | } |
| 99 | return nil |
| 100 | } |
| 101 | |
| 102 | func leadingZeros8(value byte) int { |
| 103 | zeros := 0 |
| 104 | for bit := byte(0x80); value&bit == 0; bit >>= 1 { |
| 105 | zeros++ |
| 106 | } |
| 107 | return zeros |
| 108 | } |
| 109 | |
| 110 | func isLowerHex(value string) bool { |
| 111 | for _, char := range value { |
| 112 | if !(char >= '0' && char <= '9' || char >= 'a' && char <= 'f' ) { |
| 113 | return false |
| 114 | } |
| 115 | } |
| 116 | return true |
| 117 | } |
| 118 | |
| 119 | func 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 false |
| 123 | } |
| 124 | } |
| 125 | return true |
| 126 | } |