or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

aead.mdauth.mdbox.mded25519.mdhash.mdindex.mdkdf.mdkx.mdmemory.mdpwhash.mdrandom.mdsecretbox.mdsecretstream.mdshorthash.mdsign.mdstream.md

shorthash.mddocs/

0

# Short Hash Functions

1

2

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

3

4

## Capabilities

5

6

### SipHash-2-4

7

8

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

9

10

```javascript { .api }

11

/**

12

* Compute SipHash-2-4 short hash

13

* @param out - Output buffer for hash (must be BYTES long)

14

* @param input - Input buffer to hash

15

* @param k - Key buffer (must be KEYBYTES long)

16

* @throws Error if buffer sizes are incorrect or hashing fails

17

*/

18

function crypto_shorthash(out: Buffer, input: Buffer, k: Buffer): void;

19

```

20

21

**Usage Example:**

22

23

```javascript

24

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

25

26

// Generate key for SipHash

27

const key = Buffer.alloc(sodium.crypto_shorthash_KEYBYTES);

28

sodium.randombytes_buf(key);

29

30

// Compute short hash

31

const input = Buffer.from('Hello, World!');

32

const hash = Buffer.alloc(sodium.crypto_shorthash_BYTES);

33

34

sodium.crypto_shorthash(hash, input, key);

35

36

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

37

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

38

```

39

40

## Constants

41

42

```javascript { .api }

43

// Output hash size in bytes (8 bytes = 64 bits)

44

const crypto_shorthash_BYTES: number;

45

46

// Key size in bytes (16 bytes = 128 bits)

47

const crypto_shorthash_KEYBYTES: number;

48

```

49

50

## Use Cases

51

52

SipHash is designed for scenarios where you need:

53

54

- **Hash Tables**: Fast, collision-resistant hashing for hash table keys

55

- **Data Structures**: Checksums for data structure integrity

56

- **Bloom Filters**: Multiple hash functions with different keys

57

- **Load Balancing**: Consistent hashing for distributed systems

58

- **Deduplication**: Fast content fingerprinting

59

- **DoS Protection**: Hash table collision resistance

60

61

## Security Considerations

62

63

- **Not Cryptographically Secure**: SipHash is designed for speed, not cryptographic security

64

- **Key Secrecy**: Keep hash keys secret to prevent hash collision attacks

65

- **Collision Resistance**: Provides good collision resistance when key is unknown to attacker

66

- **Performance**: Much faster than cryptographic hashes like SHA-256

67

68

## Common Patterns

69

70

### Hash Table Implementation

71

72

```javascript

73

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

74

75

class SecureHashTable {

76

constructor(initialSize = 1000) {

77

this.buckets = new Array(initialSize).fill(null).map(() => []);

78

this.size = initialSize;

79

this.count = 0;

80

81

// Generate secret key for hash function

82

this.hashKey = Buffer.alloc(sodium.crypto_shorthash_KEYBYTES);

83

sodium.randombytes_buf(this.hashKey);

84

}

85

86

hash(key) {

87

const keyBuffer = Buffer.from(key.toString());

88

const hashOutput = Buffer.alloc(sodium.crypto_shorthash_BYTES);

89

90

sodium.crypto_shorthash(hashOutput, keyBuffer, this.hashKey);

91

92

// Convert to array index

93

return hashOutput.readBigUInt64LE(0) % BigInt(this.size);

94

}

95

96

set(key, value) {

97

const index = Number(this.hash(key));

98

const bucket = this.buckets[index];

99

100

// Check if key already exists

101

for (let i = 0; i < bucket.length; i++) {

102

if (bucket[i][0] === key) {

103

bucket[i][1] = value;

104

return;

105

}

106

}

107

108

// Add new key-value pair

109

bucket.push([key, value]);

110

this.count++;

111

112

// Resize if load factor too high

113

if (this.count > this.size * 0.75) {

114

this.resize();

115

}

116

}

117

118

get(key) {

119

const index = Number(this.hash(key));

120

const bucket = this.buckets[index];

121

122

for (const [k, v] of bucket) {

123

if (k === key) {

124

return v;

125

}

126

}

127

128

return undefined;

129

}

130

131

delete(key) {

132

const index = Number(this.hash(key));

133

const bucket = this.buckets[index];

134

135

for (let i = 0; i < bucket.length; i++) {

136

if (bucket[i][0] === key) {

137

bucket.splice(i, 1);

138

this.count--;

139

return true;

140

}

141

}

142

143

return false;

144

}

145

146

resize() {

147

const oldBuckets = this.buckets;

148

this.size *= 2;

149

this.buckets = new Array(this.size).fill(null).map(() => []);

150

this.count = 0;

151

152

// Rehash all existing entries

153

for (const bucket of oldBuckets) {

154

for (const [key, value] of bucket) {

155

this.set(key, value);

156

}

157

}

158

}

159

}

160

161

// Usage

162

const hashTable = new SecureHashTable();

163

164

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

165

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

166

167

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

168

```

169

170

### Bloom Filter Implementation

171

172

```javascript

173

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

174

175

class BloomFilter {

176

constructor(expectedElements, falsePositiveRate = 0.01) {

177

// Calculate optimal bit array size and hash function count

178

this.bitCount = Math.ceil(-(expectedElements * Math.log(falsePositiveRate)) / (Math.log(2) ** 2));

179

this.hashCount = Math.ceil((this.bitCount / expectedElements) * Math.log(2));

180

181

// Initialize bit array

182

this.bits = new Uint8Array(Math.ceil(this.bitCount / 8));

183

184

// Generate multiple hash keys

185

this.hashKeys = [];

186

for (let i = 0; i < this.hashCount; i++) {

187

const key = Buffer.alloc(sodium.crypto_shorthash_KEYBYTES);

188

sodium.randombytes_buf(key);

189

this.hashKeys.push(key);

190

}

191

}

192

193

hash(input, keyIndex) {

194

const inputBuffer = Buffer.from(input.toString());

195

const hashOutput = Buffer.alloc(sodium.crypto_shorthash_BYTES);

196

197

sodium.crypto_shorthash(hashOutput, inputBuffer, this.hashKeys[keyIndex]);

198

199

return Number(hashOutput.readBigUInt64LE(0) % BigInt(this.bitCount));

200

}

201

202

add(element) {

203

for (let i = 0; i < this.hashCount; i++) {

204

const bitIndex = this.hash(element, i);

205

const byteIndex = Math.floor(bitIndex / 8);

206

const bitOffset = bitIndex % 8;

207

208

this.bits[byteIndex] |= 1 << bitOffset;

209

}

210

}

211

212

contains(element) {

213

for (let i = 0; i < this.hashCount; i++) {

214

const bitIndex = this.hash(element, i);

215

const byteIndex = Math.floor(bitIndex / 8);

216

const bitOffset = bitIndex % 8;

217

218

if ((this.bits[byteIndex] & (1 << bitOffset)) === 0) {

219

return false; // Definitely not in set

220

}

221

}

222

223

return true; // Probably in set

224

}

225

226

getInfo() {

227

return {

228

bitCount: this.bitCount,

229

hashCount: this.hashCount,

230

expectedElements: Math.ceil(this.bitCount / (-Math.log(0.01) / Math.log(2))),

231

memoryUsage: this.bits.length

232

};

233

}

234

}

235

236

// Usage

237

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

238

239

// Add elements

240

bloom.add('user:12345');

241

bloom.add('user:67890');

242

bloom.add('user:11111');

243

244

// Test membership

245

console.log('Contains user:12345:', bloom.contains('user:12345')); // true

246

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

247

248

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

249

```

250

251

### Consistent Hashing for Load Balancing

252

253

```javascript

254

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

255

256

class ConsistentHashRing {

257

constructor(virtualNodes = 150) {

258

this.virtualNodes = virtualNodes;

259

this.ring = new Map(); // hash -> server

260

this.servers = new Set();

261

262

// Single key for all hash operations

263

this.hashKey = Buffer.alloc(sodium.crypto_shorthash_KEYBYTES);

264

sodium.randombytes_buf(this.hashKey);

265

}

266

267

hash(input) {

268

const inputBuffer = Buffer.from(input.toString());

269

const hashOutput = Buffer.alloc(sodium.crypto_shorthash_BYTES);

270

271

sodium.crypto_shorthash(hashOutput, inputBuffer, this.hashKey);

272

273

return hashOutput.readBigUInt64LE(0);

274

}

275

276

addServer(server) {

277

this.servers.add(server);

278

279

// Add virtual nodes for this server

280

for (let i = 0; i < this.virtualNodes; i++) {

281

const virtualKey = `${server}:${i}`;

282

const hash = this.hash(virtualKey);

283

this.ring.set(hash, server);

284

}

285

286

// Sort ring by hash values

287

this.sortedHashes = Array.from(this.ring.keys()).sort((a, b) => {

288

if (a < b) return -1;

289

if (a > b) return 1;

290

return 0;

291

});

292

}

293

294

removeServer(server) {

295

this.servers.delete(server);

296

297

// Remove virtual nodes for this server

298

for (let i = 0; i < this.virtualNodes; i++) {

299

const virtualKey = `${server}:${i}`;

300

const hash = this.hash(virtualKey);

301

this.ring.delete(hash);

302

}

303

304

// Update sorted hash list

305

this.sortedHashes = Array.from(this.ring.keys()).sort((a, b) => {

306

if (a < b) return -1;

307

if (a > b) return 1;

308

return 0;

309

});

310

}

311

312

getServer(key) {

313

if (this.servers.size === 0) {

314

throw new Error('No servers available');

315

}

316

317

const keyHash = this.hash(key);

318

319

// Find first server hash >= key hash

320

for (const serverHash of this.sortedHashes) {

321

if (serverHash >= keyHash) {

322

return this.ring.get(serverHash);

323

}

324

}

325

326

// Wrap around to first server

327

return this.ring.get(this.sortedHashes[0]);

328

}

329

330

getServerDistribution(keys) {

331

const distribution = new Map();

332

333

for (const server of this.servers) {

334

distribution.set(server, 0);

335

}

336

337

for (const key of keys) {

338

const server = this.getServer(key);

339

distribution.set(server, distribution.get(server) + 1);

340

}

341

342

return distribution;

343

}

344

}

345

346

// Usage

347

const hashRing = new ConsistentHashRing();

348

349

// Add servers

350

hashRing.addServer('server1:8080');

351

hashRing.addServer('server2:8080');

352

hashRing.addServer('server3:8080');

353

354

// Route requests

355

const testKeys = Array.from({length: 1000}, (_, i) => `request:${i}`);

356

const distribution = hashRing.getServerDistribution(testKeys);

357

358

console.log('Load distribution:');

359

for (const [server, count] of distribution) {

360

console.log(`${server}: ${count} requests (${(count/testKeys.length*100).toFixed(1)}%)`);

361

}

362

363

// Add new server and see redistribution

364

hashRing.addServer('server4:8080');

365

const newDistribution = hashRing.getServerDistribution(testKeys);

366

367

console.log('\nAfter adding server4:');

368

for (const [server, count] of newDistribution) {

369

console.log(`${server}: ${count} requests (${(count/testKeys.length*100).toFixed(1)}%)`);

370

}

371

```

372

373

### Fast Content Deduplication

374

375

```javascript

376

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

377

378

class ContentDeduplicator {

379

constructor() {

380

this.hashKey = Buffer.alloc(sodium.crypto_shorthash_KEYBYTES);

381

sodium.randombytes_buf(this.hashKey);

382

383

this.seenHashes = new Set();

384

this.contentMap = new Map(); // hash -> content reference

385

}

386

387

fingerprint(content) {

388

const contentBuffer = Buffer.isBuffer(content) ? content : Buffer.from(content);

389

const hash = Buffer.alloc(sodium.crypto_shorthash_BYTES);

390

391

sodium.crypto_shorthash(hash, contentBuffer, this.hashKey);

392

393

return hash.toString('hex');

394

}

395

396

isDuplicate(content) {

397

const fingerprint = this.fingerprint(content);

398

return this.seenHashes.has(fingerprint);

399

}

400

401

addContent(content, metadata = null) {

402

const fingerprint = this.fingerprint(content);

403

404

if (this.seenHashes.has(fingerprint)) {

405

return { isDuplicate: true, fingerprint };

406

}

407

408

this.seenHashes.add(fingerprint);

409

this.contentMap.set(fingerprint, {

410

content: content,

411

metadata: metadata,

412

firstSeen: new Date(),

413

refCount: 1

414

});

415

416

return { isDuplicate: false, fingerprint };

417

}

418

419

getContent(fingerprint) {

420

return this.contentMap.get(fingerprint);

421

}

422

423

incrementReference(fingerprint) {

424

const entry = this.contentMap.get(fingerprint);

425

if (entry) {

426

entry.refCount++;

427

}

428

}

429

430

getStats() {

431

return {

432

uniqueContent: this.seenHashes.size,

433

totalReferences: Array.from(this.contentMap.values())

434

.reduce((sum, entry) => sum + entry.refCount, 0),

435

memoryUsage: this.contentMap.size

436

};

437

}

438

}

439

440

// Usage

441

const deduplicator = new ContentDeduplicator();

442

443

// Add some content

444

const content1 = 'This is some content';

445

const content2 = 'This is different content';

446

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

447

448

const result1 = deduplicator.addContent(content1, { source: 'file1.txt' });

449

const result2 = deduplicator.addContent(content2, { source: 'file2.txt' });

450

const result3 = deduplicator.addContent(content3, { source: 'file3.txt' });

451

452

console.log('Content1 result:', result1);

453

console.log('Content2 result:', result2);

454

console.log('Content3 result (duplicate):', result3);

455

456

if (result3.isDuplicate) {

457

deduplicator.incrementReference(result3.fingerprint);

458

}

459

460

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

461

```