Bug Description
Rolldown (1.0.0-rc.17 via Vite 8.0.10) splits @noble/hashes and @noble/curves into separate chunks that form a static circular dependency. This causes a runtime error because @noble/curves/secp256k1 reads sha256 from @noble/hashes at module evaluation time (top-level curve construction), but the live binding is still undefined due to the circular chunk initialisation order.
This works correctly with Vite 7 / Rollup.
Error
Error: param hash is invalid. Expected hash, got undefined
at r (utils-BeVtMw_R.js:1)
at k (utils-BeVtMw_R.js:1)
at we (secp256k1-DjV6mbXe.js:1)
at Te (secp256k1-DjV6mbXe.js:1)
at ke (secp256k1-DjV6mbXe.js:1)
at <anonymous> (secp256k1-DjV6mbXe.js:1)
What happens
Rolldown produces three relevant chunks:
| Chunk |
Contains |
index-*.js (entry) |
@noble/hashes/sha256, viem core, React, app code |
secp256k1-*.js |
@noble/curves/secp256k1 + abstract curve utilities (Field, weierstrass, hash-to-curve) |
| various lazy chunks |
EVMcrispr modules (dynamic imports) |
The dependency graph between them:
index-*.js ──static──▶ secp256k1-*.js ──static──▶ index-*.js
│ ▲
└──────────dynamic import()───────────────────────┘
index-*.js statically imports {a, i, o, r, t} from secp256k1-*.js (the abstract curve utilities: Field, weierstrass, hash-to-curve, mapToCurveSimpleSWU, and the secp256k1 curve)
secp256k1-*.js statically imports {$, U} from index-*.js (sha256 hash function and hmac)
index-*.js also dynamically import()s secp256k1-*.js (viem's lazy signature recovery)
When the browser evaluates index-*.js, it must first evaluate secp256k1-*.js (static dep). But secp256k1-*.js reads sha256 from index-*.js at module top-level — and index-*.js hasn't finished initializing yet. So sha256 is undefined, and the @noble/curves parameter validator throws.
Reproduction
git clone https://github.com/EVMcrispr/evmcrispr.git
cd evmcrispr
git checkout c67bc95c84cc7afbe7f8e383f3a3815c1bceec2a
bun install
bun run --filter='evmcrispr-terminal' build
Then verify the circular dependency in the output:
# index statically imports from secp256k1
grep -oP 'import\{[^}]*\}from"./secp256k1[^"]*"' apps/evmcrispr-terminal/dist/assets/index-*.js
# secp256k1 statically imports from index
grep -oP 'import\{[^}]*\}from"./index[^"]*"' apps/evmcrispr-terminal/dist/assets/secp256k1-*.js
Serve with npx vite preview inside apps/evmcrispr-terminal and open the browser console to see the error.
Expected behaviour
The bundler should either:
- Keep modules with top-level cross-package dependencies in the same chunk, or
- Ensure correct evaluation order so that statically imported bindings are initialised before dependent chunks execute
Workaround
Force both packages into the same chunk:
// vite.config.ts
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("@noble/hashes") || id.includes("@noble/curves")) {
return "noble-crypto";
}
},
},
},
},
Impact
This affects any project using viem, wagmi, or ethers with Vite 8 when the dependency graph is complex enough that Rolldown's chunk optimizer splits @noble/hashes and @noble/curves into separate chunks with a static circular dependency.
Environment
- Vite: 8.0.10
- Rolldown: 1.0.0-rc.17
@noble/curves: 1.8.2 (via viem 2.46.3)
@noble/hashes: 1.7.2 (via viem 2.46.3)
Related issues
Bug Description
Rolldown (1.0.0-rc.17 via Vite 8.0.10) splits
@noble/hashesand@noble/curvesinto separate chunks that form a static circular dependency. This causes a runtime error because@noble/curves/secp256k1readssha256from@noble/hashesat module evaluation time (top-level curve construction), but the live binding is stillundefineddue to the circular chunk initialisation order.This works correctly with Vite 7 / Rollup.
Error
What happens
Rolldown produces three relevant chunks:
index-*.js(entry)@noble/hashes/sha256, viem core, React, app codesecp256k1-*.js@noble/curves/secp256k1+ abstract curve utilities (Field, weierstrass, hash-to-curve)The dependency graph between them:
index-*.jsstatically imports{a, i, o, r, t}fromsecp256k1-*.js(the abstract curve utilities: Field, weierstrass, hash-to-curve, mapToCurveSimpleSWU, and the secp256k1 curve)secp256k1-*.jsstatically imports{$, U}fromindex-*.js(sha256 hash function and hmac)index-*.jsalso dynamicallyimport()ssecp256k1-*.js(viem's lazy signature recovery)When the browser evaluates
index-*.js, it must first evaluatesecp256k1-*.js(static dep). Butsecp256k1-*.jsreads sha256 fromindex-*.jsat module top-level — andindex-*.jshasn't finished initializing yet. So sha256 isundefined, and the@noble/curvesparameter validator throws.Reproduction
Then verify the circular dependency in the output:
Serve with
npx vite previewinsideapps/evmcrispr-terminaland open the browser console to see the error.Expected behaviour
The bundler should either:
Workaround
Force both packages into the same chunk:
Impact
This affects any project using viem, wagmi, or ethers with Vite 8 when the dependency graph is complex enough that Rolldown's chunk optimizer splits
@noble/hashesand@noble/curvesinto separate chunks with a static circular dependency.Environment
@noble/curves: 1.8.2 (via viem 2.46.3)@noble/hashes: 1.7.2 (via viem 2.46.3)Related issues
undefined#3650 — Circular deps caused by advancedChunks