Low level bindings for libsodium cryptographic library
—
Fast, non-cryptographic hash function (SipHash-2-4) optimized for hash tables, data structures, and applications requiring short, uniform hash values.
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');// 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;SipHash is designed for scenarios where you need:
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'));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());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)}%)`);
}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