Skip to content

feat: add optional hashFn to generateKey for wider key entropy#1267

Open
juliusmarminge wants to merge 3 commits intomainfrom
feat/custom-hash-fn
Open

feat: add optional hashFn to generateKey for wider key entropy#1267
juliusmarminge wants to merge 3 commits intomainfrom
feat/custom-hash-fn

Conversation

@juliusmarminge
Copy link
Member

@juliusmarminge juliusmarminge commented Feb 7, 2026

Summary

  • Adds a HashFn type and optional hashFn field to RouteOptions in @uploadthing/shared
  • generateKey now accepts an optional hashFn parameter that replaces the default Hash.string (32-bit) for hashing the file seed portion of the key
  • The function can return a single number or an array of numbers — arrays allow wider entropy (e.g. 4x 32-bit chunks from SHA-256 = 128 bits) via multiple SQIds inputs
  • The encodedAppId prefix remains unchanged (still uses Hash.string) so verifyKey continues to work

Motivation

Hash.string is a 32-bit hash, so even if users pass 128 bits of randomness via getFileHashParts, it all gets crushed through a ~31-bit funnel. This creates an unnecessary collision bottleneck. With hashFn, users can bypass this limitation for the file seed portion while keeping backward compatibility.

Test plan

  • Custom hashFn returning a single number generates a valid key (passes verifyKey)
  • Custom hashFn returning an array of numbers generates a valid key (passes verifyKey)
  • Different hashFn outputs produce different keys
  • All existing tests continue to pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added optional custom hash function support for file key generation, letting routes supply alternative hashing for greater entropy and variability in upload keys.
  • Tests

    • Added tests validating custom hash function behaviors (single number and array outputs) and that different hash outputs produce distinct keys.
  • Chores

    • Added a changeset documenting the update and version bump.

The `generateKey` function uses Effect's `Hash.string` (32-bit) to hash
the file seed, creating a collision bottleneck even when users provide
high-entropy inputs via `getFileHashParts`. This adds an optional
`hashFn` parameter to `RouteOptions` that lets users supply their own
hash function, returning either a single number or an array of numbers
for wider SQIds encoding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@changeset-bot
Copy link

changeset-bot bot commented Feb 7, 2026

🦋 Changeset detected

Latest commit: 0e818d4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 8 packages
Name Type
@uploadthing/shared Patch
uploadthing Patch
@uploadthing/expo Patch
@uploadthing/react Patch
@uploadthing/solid Patch
@uploadthing/svelte Patch
@uploadthing/vue Patch
@uploadthing/nuxt Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Feb 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs-uploadthing Ready Ready Preview, Comment Feb 7, 2026 7:41pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
legacy-docs-uploadthing Ignored Ignored Feb 7, 2026 7:41pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 7, 2026

Walkthrough

The PR adds support for a custom hash function: a new HashFn type, an optional hashFn field on RouteOptions, generateKey now accepts and uses hashFn when provided, and the upload handler forwards routeOptions.hashFn. Tests and a changeset were added.

Changes

Cohort / File(s) Summary
Type Definitions
packages/shared/src/types.ts
Added exported `HashFn = (input: string) => number
Crypto Implementation
packages/shared/src/crypto.ts
generateKey gains an optional hashFn parameter; when provided it is invoked to produce one or more numeric hash parts (normalized to an array) and those values are encoded; falls back to existing Hash.string() when absent.
Handler Integration
packages/uploadthing/src/_internal/handler.ts
Updated generateKey(...) call to pass routeOptions.hashFn through to the shared crypto module.
Test Coverage
packages/shared/test/crypto.test.ts
Added tests verifying generateKey behavior with a custom hashFn returning a single number, an array of numbers, and differing outputs producing different keys.
Changelog
.changeset/three-bees-melt.md
Added changeset noting the feature and package patch bumps.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an optional hashFn parameter to generateKey for improving key entropy through wider hash values.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/custom-hash-fn

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
packages/shared/test/crypto.test.ts (1)

202-265: Consider adding edge case tests for hashFn.

The current tests cover the main paths well. For completeness, consider adding tests for:

  • Negative numbers (handled by Math.abs() in the implementation)
  • hashFn returning [0] or very large numbers

These are optional improvements that could be deferred.


Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 7, 2026

More templates

npm i https://pkg.pr.new/pingdotgg/uploadthing/@uploadthing/shared@1267
npm i https://pkg.pr.new/pingdotgg/uploadthing@1267

commit: 0e818d4

@github-actions
Copy link
Contributor

github-actions bot commented Feb 7, 2026

📦 Bundle size comparison

Bundle Size (gzip) Visualization
Main 31.17KB See Treemap 📊
PR (67f6b17) 31.17KB See Treemap 📊
Diff No change

@greptile-apps
Copy link

greptile-apps bot commented Feb 7, 2026

Greptile Overview

Greptile Summary

This PR adds an optional hashFn parameter to address a collision bottleneck in the key generation system. Previously, all file hash parts were processed through Effect's Hash.string (32-bit), which limited entropy even when users provided 128 bits of randomness via getFileHashParts.

Key changes:

  • Added HashFn type that accepts string input and returns number | number[]
  • Modified generateKey in crypto.ts:712-715 to accept optional hashFn parameter and handle both single numbers and arrays via Array.isArray check
  • Arrays are mapped through Math.abs before being passed to SQIds encoder for multi-chunk entropy
  • The encodedAppId prefix continues using Hash.string to maintain backward compatibility with verifyKey
  • Integration point in handler.ts:584 passes routeOptions.hashFn to generateKey

Test coverage:

  • Single number return values (test line 203-222)
  • Array return values for wider entropy (test line 224-243)
  • Different hash outputs produce different keys (test line 245-261)
  • All tests verify keys pass verifyKey validation

The implementation is clean, well-tested, and maintains backward compatibility since hashFn is optional.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk - the changes are well-isolated, fully backward compatible, and thoroughly tested.
  • Score of 5 reflects: (1) Optional parameter maintains full backward compatibility, (2) Comprehensive test coverage validates all use cases including edge cases, (3) Simple, focused implementation with clear documentation, (4) No breaking changes to existing functionality, (5) The Math.abs handling prevents potential issues with negative numbers in SQIds encoding.
  • No files require special attention

Important Files Changed

Filename Overview
packages/shared/src/crypto.ts Added optional hashFn parameter to generateKey function that accepts custom hash functions returning number or number[] for wider entropy, with proper handling via Math.abs and array normalization.
packages/shared/src/types.ts Added HashFn type definition and hashFn field to RouteOptions with clear documentation explaining the default 32-bit limitation and how to increase entropy.
packages/shared/test/crypto.test.ts Added comprehensive tests covering single number return, array return, key verification, and different outputs producing different keys - all test cases pass validation.
packages/uploadthing/src/_internal/handler.ts Integrated hashFn parameter by passing routeOptions.hashFn to generateKey call, maintaining consistency with other route options like getFileHashParts.

Sequence Diagram

sequenceDiagram
    participant Handler
    participant GenerateKey
    participant CustomHashFn
    participant SQIds

    Handler->>GenerateKey: Call generateKey with file and options
    
    GenerateKey->>GenerateKey: Extract and stringify file properties
    
    alt Custom hashFn provided
        GenerateKey->>CustomHashFn: Invoke custom function
        CustomHashFn-->>GenerateKey: Return number or array
    else Default behavior
        GenerateKey->>GenerateKey: Use built-in hash function
    end
    
    GenerateKey->>GenerateKey: Convert to array and normalize values
    GenerateKey->>SQIds: Encode file seed portion
    SQIds-->>GenerateKey: Return encoded file seed
    
    GenerateKey->>SQIds: Encode appId portion
    SQIds-->>GenerateKey: Return encoded appId
    
    GenerateKey-->>Handler: Concatenate and return final key
Loading

juliusmarminge and others added 2 commits February 7, 2026 11:38
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add optional hash function to generateKey for improved key entropy.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/shared/src/crypto.ts (1)

89-116: ⚠️ Potential issue | 🟠 Major

Guard against empty hash arrays before SQIds encoding.

If hashFn returns [] or produces no valid numbers, SQIds.encode([]) returns an empty string, collapsing the file seed and risking key collisions across different files. Additionally, if hashFn returns NaN or non-integers, the current code doesn't validate these invalid inputs before encoding. Add validation to ensure the hash array contains at least one finite, non-negative integer before encoding.

🔧 Proposed fix
-    const rawHash = hashFn ? hashFn(hashParts) : Hash.string(hashParts);
-    const hashArray = Array.isArray(rawHash) ? rawHash : [rawHash];
-    const encodedFileSeed = new SQIds({ alphabet, minLength: 36 }).encode(
-      hashArray.map((n) => Math.abs(n)),
-    );
+    const rawHash = hashFn ? hashFn(hashParts) : Hash.string(hashParts);
+    const hashArray = Array.isArray(rawHash) ? rawHash : [rawHash];
+    const normalized = hashArray
+      .map((n) => (Number.isFinite(n) ? Math.abs(Math.trunc(n)) : NaN))
+      .filter((n) => Number.isFinite(n));
+    if (normalized.length === 0) {
+      throw new UploadThingError({
+        code: "BAD_REQUEST",
+        message: "hashFn must return at least one finite number",
+      });
+    }
+    const encodedFileSeed = new SQIds({ alphabet, minLength: 36 }).encode(
+      normalized,
+    );

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant