Files
crypto/utils/bloom/filter.go
T
Hanzo Dev 0be2fe8f6c crypto: implement post-quantum primitives with circl
- Implement ML-DSA-65 (FIPS 204) using cloudflare/circl
  * Single implementation with automatic CGO optimization
  * Sign ~440μs, Verify ~130μs, KeyGen ~165μs on M1 Max
  * All 11 tests passing

- Simplify ML-KEM implementation
  * Remove redundant optimized versions
  * Use circl ML-KEM-768 directly

- Simplify SLH-DSA implementation
  * Remove premature optimizations
  * Clean stub for future circl support (FIPS 205)

- Add comprehensive cache package
  * LRU cache from luxfi/node
  * Metercacher for metrics integration
  * Test utilities

- Add crypto utils
  * Atomic operations
  * Bytes utilities
  * Complete utils package from luxfi/node

- Update secp256k1 and BLS
  * All BLS tests passing (23 tests)
  * secp256k1 fuzz test added

All post-quantum implementations now use cloudflare/circl as single source
of truth, following DRY principle and ensuring FIPS compliance.
2025-11-22 16:37:21 -08:00

148 lines
3.5 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package bloom
import (
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"math/bits"
"sync"
)
const (
minHashes = 1
maxHashes = 16 // Supports a false positive probability of 2^-16 when using optimal size values
minEntries = 1
bitsPerByte = 8
bytesPerUint64 = 8
hashRotation = 17
)
var (
errInvalidNumHashes = errors.New("invalid num hashes")
errTooFewHashes = errors.New("too few hashes")
errTooManyHashes = errors.New("too many hashes")
errTooFewEntries = errors.New("too few entries")
)
type Filter struct {
// numBits is always equal to [bitsPerByte * len(entries)]
numBits uint64
lock sync.RWMutex
hashSeeds []uint64
entries []byte
count int
}
// New creates a new Filter with the specified number of hashes and bytes for
// entries. The returned bloom filter is safe for concurrent usage.
func New(numHashes, numEntries int) (*Filter, error) {
if numEntries < minEntries {
return nil, errTooFewEntries
}
hashSeeds, err := newHashSeeds(numHashes)
if err != nil {
return nil, err
}
return &Filter{
numBits: uint64(numEntries * bitsPerByte),
hashSeeds: hashSeeds,
entries: make([]byte, numEntries),
count: 0,
}, nil
}
func (f *Filter) Add(hash uint64) {
f.lock.Lock()
defer f.lock.Unlock()
_ = 1 % f.numBits // hint to the compiler that numBits is not 0
for _, seed := range f.hashSeeds {
hash = bits.RotateLeft64(hash, hashRotation) ^ seed
index := hash % f.numBits
byteIndex := index / bitsPerByte
bitIndex := index % bitsPerByte
f.entries[byteIndex] |= 1 << bitIndex
}
f.count++
}
// Count returns the number of elements that have been added to the bloom
// filter.
func (f *Filter) Count() int {
f.lock.RLock()
defer f.lock.RUnlock()
return f.count
}
func (f *Filter) Contains(hash uint64) bool {
f.lock.RLock()
defer f.lock.RUnlock()
return contains(f.hashSeeds, f.entries, hash)
}
func (f *Filter) Marshal() []byte {
f.lock.RLock()
defer f.lock.RUnlock()
return marshal(f.hashSeeds, f.entries)
}
func newHashSeeds(count int) ([]uint64, error) {
switch {
case count < minHashes:
return nil, fmt.Errorf("%w: %d < %d", errTooFewHashes, count, minHashes)
case count > maxHashes:
return nil, fmt.Errorf("%w: %d > %d", errTooManyHashes, count, maxHashes)
}
bytes := make([]byte, count*bytesPerUint64)
if _, err := rand.Reader.Read(bytes); err != nil {
return nil, err
}
seeds := make([]uint64, count)
for i := range seeds {
seeds[i] = binary.BigEndian.Uint64(bytes[i*bytesPerUint64:])
}
return seeds, nil
}
func contains(hashSeeds []uint64, entries []byte, hash uint64) bool {
var (
numBits = bitsPerByte * uint64(len(entries))
_ = 1 % numBits // hint to the compiler that numBits is not 0
accumulator byte = 1
)
for seedIndex := 0; seedIndex < len(hashSeeds) && accumulator != 0; seedIndex++ {
hash = bits.RotateLeft64(hash, hashRotation) ^ hashSeeds[seedIndex]
index := hash % numBits
byteIndex := index / bitsPerByte
bitIndex := index % bitsPerByte
accumulator &= entries[byteIndex] >> bitIndex
}
return accumulator != 0
}
func marshal(hashSeeds []uint64, entries []byte) []byte {
numHashes := len(hashSeeds)
entriesOffset := 1 + numHashes*bytesPerUint64
bytes := make([]byte, entriesOffset+len(entries))
bytes[0] = byte(numHashes)
for i, seed := range hashSeeds {
binary.BigEndian.PutUint64(bytes[1+i*bytesPerUint64:], seed)
}
copy(bytes[entriesOffset:], entries)
return bytes
}