CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-sodium-native

Low level bindings for libsodium cryptographic library

Pending
Overview
Eval results
Files

shorthash.mddocs/

Short Hash Functions

Fast, non-cryptographic hash function (SipHash-2-4) optimized for hash tables, data structures, and applications requiring short, uniform hash values.

Capabilities

SipHash-2-4

Compute SipHash-2-4, a fast and secure short hash function suitable for hash tables and data structure applications.

/**
 * Compute SipHash-2-4 short hash
 * @param out - Output buffer for hash (must be BYTES long)
 * @param input - Input buffer to hash
 * @param k - Key buffer (must be KEYBYTES long)
 * @throws Error if buffer sizes are incorrect or hashing fails
 */
function crypto_shorthash(out: Buffer, input: Buffer, k: Buffer): void;

Usage Example:

const sodium = require('sodium-native');

// Generate key for SipHash
const key = Buffer.alloc(sodium.crypto_shorthash_KEYBYTES);
sodium.randombytes_buf(key);

// Compute short hash
const input = Buffer.from('Hello, World!');
const hash = Buffer.alloc(sodium.crypto_shorthash_BYTES);

sodium.crypto_shorthash(hash, input, key);

console.log('Short hash:', hash.toString('hex'));
console.log('Hash length:', hash.length, 'bytes');

Constants

// Output hash size in bytes (8 bytes = 64 bits)
const crypto_shorthash_BYTES: number;

// Key size in bytes (16 bytes = 128 bits)
const crypto_shorthash_KEYBYTES: number;

Use Cases

SipHash is designed for scenarios where you need:

  • Hash Tables: Fast, collision-resistant hashing for hash table keys
  • Data Structures: Checksums for data structure integrity
  • Bloom Filters: Multiple hash functions with different keys
  • Load Balancing: Consistent hashing for distributed systems
  • Deduplication: Fast content fingerprinting
  • DoS Protection: Hash table collision resistance

Security Considerations

  • Not Cryptographically Secure: SipHash is designed for speed, not cryptographic security
  • Key Secrecy: Keep hash keys secret to prevent hash collision attacks
  • Collision Resistance: Provides good collision resistance when key is unknown to attacker
  • Performance: Much faster than cryptographic hashes like SHA-256

Common Patterns

Hash Table Implementation

const sodium = require('sodium-native');

class SecureHashTable {
  constructor(initialSize = 1000) {
    this.buckets = new Array(initialSize).fill(null).map(() => []);
    this.size = initialSize;
    this.count = 0;
    
    // Generate secret key for hash function
    this.hashKey = Buffer.alloc(sodium.crypto_shorthash_KEYBYTES);
    sodium.randombytes_buf(this.hashKey);
  }
  
  hash(key) {
    const keyBuffer = Buffer.from(key.toString());
    const hashOutput = Buffer.alloc(sodium.crypto_shorthash_BYTES);
    
    sodium.crypto_shorthash(hashOutput, keyBuffer, this.hashKey);
    
    // Convert to array index
    return hashOutput.readBigUInt64LE(0) % BigInt(this.size);
  }
  
  set(key, value) {
    const index = Number(this.hash(key));
    const bucket = this.buckets[index];
    
    // Check if key already exists
    for (let i = 0; i < bucket.length; i++) {
      if (bucket[i][0] === key) {
        bucket[i][1] = value;
        return;
      }
    }
    
    // Add new key-value pair
    bucket.push([key, value]);
    this.count++;
    
    // Resize if load factor too high
    if (this.count > this.size * 0.75) {
      this.resize();
    }
  }
  
  get(key) {
    const index = Number(this.hash(key));
    const bucket = this.buckets[index];
    
    for (const [k, v] of bucket) {
      if (k === key) {
        return v;
      }
    }
    
    return undefined;
  }
  
  delete(key) {
    const index = Number(this.hash(key));
    const bucket = this.buckets[index];
    
    for (let i = 0; i < bucket.length; i++) {
      if (bucket[i][0] === key) {
        bucket.splice(i, 1);
        this.count--;
        return true;
      }
    }
    
    return false;
  }
  
  resize() {
    const oldBuckets = this.buckets;
    this.size *= 2;
    this.buckets = new Array(this.size).fill(null).map(() => []);
    this.count = 0;
    
    // Rehash all existing entries
    for (const bucket of oldBuckets) {
      for (const [key, value] of bucket) {
        this.set(key, value);
      }
    }
  }
}

// Usage
const hashTable = new SecureHashTable();

hashTable.set('user:123', { name: 'Alice', email: 'alice@example.com' });
hashTable.set('user:456', { name: 'Bob', email: 'bob@example.com' });

console.log(hashTable.get('user:123'));

Bloom Filter Implementation

const sodium = require('sodium-native');

class BloomFilter {
  constructor(expectedElements, falsePositiveRate = 0.01) {
    // Calculate optimal bit array size and hash function count
    this.bitCount = Math.ceil(-(expectedElements * Math.log(falsePositiveRate)) / (Math.log(2) ** 2));
    this.hashCount = Math.ceil((this.bitCount / expectedElements) * Math.log(2));
    
    // Initialize bit array
    this.bits = new Uint8Array(Math.ceil(this.bitCount / 8));
    
    // Generate multiple hash keys
    this.hashKeys = [];
    for (let i = 0; i < this.hashCount; i++) {
      const key = Buffer.alloc(sodium.crypto_shorthash_KEYBYTES);
      sodium.randombytes_buf(key);
      this.hashKeys.push(key);
    }
  }
  
  hash(input, keyIndex) {
    const inputBuffer = Buffer.from(input.toString());
    const hashOutput = Buffer.alloc(sodium.crypto_shorthash_BYTES);
    
    sodium.crypto_shorthash(hashOutput, inputBuffer, this.hashKeys[keyIndex]);
    
    return Number(hashOutput.readBigUInt64LE(0) % BigInt(this.bitCount));
  }
  
  add(element) {
    for (let i = 0; i < this.hashCount; i++) {
      const bitIndex = this.hash(element, i);
      const byteIndex = Math.floor(bitIndex / 8);
      const bitOffset = bitIndex % 8;
      
      this.bits[byteIndex] |= 1 << bitOffset;
    }
  }
  
  contains(element) {
    for (let i = 0; i < this.hashCount; i++) {
      const bitIndex = this.hash(element, i);
      const byteIndex = Math.floor(bitIndex / 8);
      const bitOffset = bitIndex % 8;
      
      if ((this.bits[byteIndex] & (1 << bitOffset)) === 0) {
        return false; // Definitely not in set
      }
    }
    
    return true; // Probably in set
  }
  
  getInfo() {
    return {
      bitCount: this.bitCount,
      hashCount: this.hashCount,
      expectedElements: Math.ceil(this.bitCount / (-Math.log(0.01) / Math.log(2))),
      memoryUsage: this.bits.length
    };
  }
}

// Usage
const bloom = new BloomFilter(10000, 0.01); // 10k elements, 1% false positive rate

// Add elements
bloom.add('user:12345');
bloom.add('user:67890');
bloom.add('user:11111');

// Test membership
console.log('Contains user:12345:', bloom.contains('user:12345')); // true
console.log('Contains user:99999:', bloom.contains('user:99999')); // probably false

console.log('Bloom filter info:', bloom.getInfo());

Consistent Hashing for Load Balancing

const sodium = require('sodium-native');

class ConsistentHashRing {
  constructor(virtualNodes = 150) {
    this.virtualNodes = virtualNodes;
    this.ring = new Map(); // hash -> server
    this.servers = new Set();
    
    // Single key for all hash operations
    this.hashKey = Buffer.alloc(sodium.crypto_shorthash_KEYBYTES);
    sodium.randombytes_buf(this.hashKey);
  }
  
  hash(input) {
    const inputBuffer = Buffer.from(input.toString());
    const hashOutput = Buffer.alloc(sodium.crypto_shorthash_BYTES);
    
    sodium.crypto_shorthash(hashOutput, inputBuffer, this.hashKey);
    
    return hashOutput.readBigUInt64LE(0);
  }
  
  addServer(server) {
    this.servers.add(server);
    
    // Add virtual nodes for this server
    for (let i = 0; i < this.virtualNodes; i++) {
      const virtualKey = `${server}:${i}`;
      const hash = this.hash(virtualKey);
      this.ring.set(hash, server);
    }
    
    // Sort ring by hash values
    this.sortedHashes = Array.from(this.ring.keys()).sort((a, b) => {
      if (a < b) return -1;
      if (a > b) return 1;
      return 0;
    });
  }
  
  removeServer(server) {
    this.servers.delete(server);
    
    // Remove virtual nodes for this server
    for (let i = 0; i < this.virtualNodes; i++) {
      const virtualKey = `${server}:${i}`;
      const hash = this.hash(virtualKey);
      this.ring.delete(hash);
    }
    
    // Update sorted hash list
    this.sortedHashes = Array.from(this.ring.keys()).sort((a, b) => {
      if (a < b) return -1;
      if (a > b) return 1;
      return 0;
    });
  }
  
  getServer(key) {
    if (this.servers.size === 0) {
      throw new Error('No servers available');
    }
    
    const keyHash = this.hash(key);
    
    // Find first server hash >= key hash
    for (const serverHash of this.sortedHashes) {
      if (serverHash >= keyHash) {
        return this.ring.get(serverHash);
      }
    }
    
    // Wrap around to first server
    return this.ring.get(this.sortedHashes[0]);
  }
  
  getServerDistribution(keys) {
    const distribution = new Map();
    
    for (const server of this.servers) {
      distribution.set(server, 0);
    }
    
    for (const key of keys) {
      const server = this.getServer(key);
      distribution.set(server, distribution.get(server) + 1);
    }
    
    return distribution;
  }
}

// Usage
const hashRing = new ConsistentHashRing();

// Add servers
hashRing.addServer('server1:8080');
hashRing.addServer('server2:8080');
hashRing.addServer('server3:8080');

// Route requests
const testKeys = Array.from({length: 1000}, (_, i) => `request:${i}`);
const distribution = hashRing.getServerDistribution(testKeys);

console.log('Load distribution:');
for (const [server, count] of distribution) {
  console.log(`${server}: ${count} requests (${(count/testKeys.length*100).toFixed(1)}%)`);
}

// Add new server and see redistribution
hashRing.addServer('server4:8080');
const newDistribution = hashRing.getServerDistribution(testKeys);

console.log('\nAfter adding server4:');
for (const [server, count] of newDistribution) {
  console.log(`${server}: ${count} requests (${(count/testKeys.length*100).toFixed(1)}%)`);
}

Fast Content Deduplication

const sodium = require('sodium-native');

class ContentDeduplicator {
  constructor() {
    this.hashKey = Buffer.alloc(sodium.crypto_shorthash_KEYBYTES);
    sodium.randombytes_buf(this.hashKey);
    
    this.seenHashes = new Set();
    this.contentMap = new Map(); // hash -> content reference
  }
  
  fingerprint(content) {
    const contentBuffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
    const hash = Buffer.alloc(sodium.crypto_shorthash_BYTES);
    
    sodium.crypto_shorthash(hash, contentBuffer, this.hashKey);
    
    return hash.toString('hex');
  }
  
  isDuplicate(content) {
    const fingerprint = this.fingerprint(content);
    return this.seenHashes.has(fingerprint);
  }
  
  addContent(content, metadata = null) {
    const fingerprint = this.fingerprint(content);
    
    if (this.seenHashes.has(fingerprint)) {
      return { isDuplicate: true, fingerprint };
    }
    
    this.seenHashes.add(fingerprint);
    this.contentMap.set(fingerprint, {
      content: content,
      metadata: metadata,
      firstSeen: new Date(),
      refCount: 1
    });
    
    return { isDuplicate: false, fingerprint };
  }
  
  getContent(fingerprint) {
    return this.contentMap.get(fingerprint);
  }
  
  incrementReference(fingerprint) {
    const entry = this.contentMap.get(fingerprint);
    if (entry) {
      entry.refCount++;
    }
  }
  
  getStats() {
    return {
      uniqueContent: this.seenHashes.size,
      totalReferences: Array.from(this.contentMap.values())
        .reduce((sum, entry) => sum + entry.refCount, 0),
      memoryUsage: this.contentMap.size
    };
  }
}

// Usage
const deduplicator = new ContentDeduplicator();

// Add some content
const content1 = 'This is some content';
const content2 = 'This is different content';
const content3 = 'This is some content'; // Duplicate of content1

const result1 = deduplicator.addContent(content1, { source: 'file1.txt' });
const result2 = deduplicator.addContent(content2, { source: 'file2.txt' });
const result3 = deduplicator.addContent(content3, { source: 'file3.txt' });

console.log('Content1 result:', result1);
console.log('Content2 result:', result2);
console.log('Content3 result (duplicate):', result3);

if (result3.isDuplicate) {
  deduplicator.incrementReference(result3.fingerprint);
}

console.log('Deduplication stats:', deduplicator.getStats());

Install with Tessl CLI

npx tessl i tessl/npm-sodium-native

docs

aead.md

auth.md

box.md

ed25519.md

hash.md

index.md

kdf.md

kx.md

memory.md

pwhash.md

random.md

secretbox.md

secretstream.md

shorthash.md

sign.md

stream.md

tile.json