Skip to content

Fix dependency script encoding hashes#71

Merged
punk6529 merged 4 commits into
mainfrom
codex/dependency-script-safe-encoding
Jun 10, 2026
Merged

Fix dependency script encoding hashes#71
punk6529 merged 4 commits into
mainfrom
codex/dependency-script-safe-encoding

Conversation

@punk6529

@punk6529 punk6529 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds typed dependency script chunk and content hash views on DependencyRegistry and exposes the referenced content hash from StreamCore for minted tokens.
  • Replaces the remaining packed dynamic dependency-script rendering path with initialized string.concat while preserving the rendered generative script output.
  • Adds focused metadata encoding tests proving ambiguous chunk splits render the same JavaScript but produce distinct typed hashes, including deterministic zero-chunk dependency hashes.
  • Updates roadmap, Slither baseline, ADR 0006, status docs, test docs, and autonomous run state for issue [P0-META-001] Replace dependency-script packed concatenation with collision-safe encoding #9.

Scope boundary

This closes the P0 packed/dynamic encoding blocker. Full immutable dependency versioning, registry identity pinning, provenance, deprecation semantics, and freeze-manifest pinning remain in the later P1 metadata/freeze workstream.

CodeRabbit follow-up

CodeRabbit comment 4673171581 found the PR correct and well-scoped, with non-blocking observations only. The latest head adds NatSpec for the new public hash views and a zero-chunk dependency hash regression.

Validation

  • forge fmt --check smart-contracts\DependencyRegistry.sol smart-contracts\StreamCore.sol test\StreamMetadataEncoding.t.sol
  • forge test --match-contract StreamMetadataEncodingTest -vvv passed: 3 tests, 0 failed
  • make check passed: 174 tests, 0 failed
  • powershell -ExecutionPolicy Bypass -File scripts\check.ps1 passed: 174 tests, 0 failed
  • git diff --check
  • Markdown heading scan for touched docs
  • Traceability grep for P0-META-001, StreamMetadataEncoding, dependency hash views/typehashes, Slither detector rows, and PR Store raw randomness output hashes #70 merge state
  • Slither confirmation: slither_exit=-1, total=685, high=8, medium=28, low=63, informational=580, optimization=6, encode_packed_collision=0, uninitialized_local=10, calls_loop=8

Closes #9

Summary by CodeRabbit

  • Bug Fixes

    • Fixed ambiguous dependency-script retrieval and encoding issues that could cause hash collisions and uninitialized script data.
  • Improvements

    • Introduced typed, chunk-aware content hashing so dependency content and chunk boundaries are deterministic and collision-resistant.
  • Documentation

    • Updated status, roadmap, baseline, and process docs to reflect the new hashing model and readiness criteria.
  • Tests

    • Added tests validating chunk-boundary behavior, per-chunk hashing, and deterministic content hashes.
  • Chores

    • Advanced operational runlog and PR/roadmap metadata.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 28f92c33-c2e6-47b5-8d5d-0aec329a4456

📥 Commits

Reviewing files that changed from the base of the PR and between fd0b5b8 and 1668c6e.

📒 Files selected for processing (6)
  • ops/AUTONOMOUS_RUN.md
  • ops/ROADMAP.md
  • smart-contracts/DependencyRegistry.sol
  • smart-contracts/StreamCore.sol
  • test/README.md
  • test/StreamMetadataEncoding.t.sol
✅ Files skipped from review due to trivial changes (2)
  • ops/AUTONOMOUS_RUN.md
  • ops/ROADMAP.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • test/README.md
  • smart-contracts/StreamCore.sol
  • smart-contracts/DependencyRegistry.sol
  • test/StreamMetadataEncoding.t.sol

📝 Walkthrough

Walkthrough

Adds typed per-chunk and aggregated dependency-script hashes, extends the registry interface with chunk/content hash views, refactors StreamCore script retrieval for segment-safe concatenation, adds regression tests verifying hash distinctness and determinism, and updates docs/ops/Slither baselines to reflect fixes.

Changes

P0-META-001: Dependency Script Collision-Safe Encoding

Layer / File(s) Summary
Interface expansion and specification clarification
smart-contracts/IDependencyRegistry.sol, docs/adr/0006-metadata-freeze.md
IDependencyRegistry adds getDependencyScriptChunkHash and getDependencyScriptContentHash. ADR 0006 clarifies retrieveDependencyScript is segment-safe but not a freeze manifest and refines P0-META-001 to require typed per-chunk/content hashes including index/length and pairing to registry identity/version.
DependencyRegistry hashing implementation
smart-contracts/DependencyRegistry.sol
Adds DEPENDENCY_SCRIPT_CONTENT_TYPEHASH and DEPENDENCY_SCRIPT_CHUNK_TYPEHASH and implements getDependencyScriptChunkHash, getDependencyScriptContentHash, and a private chunk-hash helper using abi.encode with index and byte-length metadata.
StreamCore script retrieval refactoring
smart-contracts/StreamCore.sol
Caches collectionId and dependency identifier, builds scripttext via string.concat per chunk for segment-safe rendering, and reuses cached collectionId when invoking dependencyRegistry.getDependencyScriptContentHash.
Comprehensive tests and test docs
test/StreamMetadataEncoding.t.sol, test/README.md
Adds StreamMetadataEncodingTest with tests: distinct content hashes across ambiguous chunk boundaries, per-chunk hash includes index/length, and deterministic empty-content hash. Helpers mirror production hashing; test README documents P0-META-001 target state.
Known blockers and status documentation
docs/known-blockers.md, docs/status.md
Known-blockers updated to record dependency-script retrieval fixed via segment-safe typed chunking and content hashes; status adds dependency-script content hashing to Gate A baseline and reflows blocker text.
Project tracking and Slither baseline updates
ops/AUTONOMOUS_RUN.md, ops/ROADMAP.md, ops/SLITHER_BASELINE.md
AUTONOMOUS_RUN records PR/queue/worklog updates and Decision Log entries; ROADMAP narrows P0-META-001 and updates test baseline; SLITHER_BASELINE updated with new run totals and marks encode-packed-collision and uninitialized-local findings for StreamCore.retrieveDependencyScript as Fixed and test-linked.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #45: Implements and clarifies ADR-0006/P0-META-001 typed chunk/content dependency hashing and related API/tests.
  • #48: Adds per-chunk and content hashing functions and interface expansion that supply hashing infrastructure referenced by the issue.
  • #47: Provides typed per-chunk and overall dependency script hashing and view APIs needed for freeze-manifest / immutable dependency-state work.

"🐰
With typed hashes so clean and so keen,
I've nibbled collisions from every scene,
Each chunk notes index and length with care,
The script still renders, hashes now declare,
Hop—metadata's safe and sound! 🎉"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix dependency script encoding hashes' accurately summarizes the primary objective of addressing Slither's encode-packed-collision finding and implementing typed, segment-safe encoding for dependency scripts.
Linked Issues check ✅ Passed The PR successfully addresses all coding requirements from issue #9: replaces unsafe packed/dynamic composition with typed encoding (new typehashes and chunk/content hash functions), initializes locals explicitly (string.concat with initialized scripttext), and provides regression tests for segment-boundary collisions.
Out of Scope Changes check ✅ Passed All changes align with the PR scope: new DependencyRegistry hashing functions, refactored StreamCore retrieval, new tests, and documentation updates directly support fixing the P0 encoded-packed-collision blocker without introducing unrelated functionality.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/dependency-script-safe-encoding

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

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Please review the latest head for dependency-script encoding safety and Slither traceability. Areas to focus on:

  • Typed dependency chunk/content hash semantics in DependencyRegistry
  • StreamCore.retrieveDependencyScriptContentHash(uint256) ABI/view surface
  • Preservation of rendered generative script compatibility while eliminating ambiguous packed/dynamic encoding
  • test/StreamMetadataEncoding.t.sol coverage for ambiguous chunk boundaries and chunk index/length hashing
  • Slither baseline/roadmap updates showing encode-packed-collision=0 and uninitialized-local=10
  • ADR 0006 scope boundary: this fixes segment-safe encoding, while immutable dependency versions and freeze manifests remain P1 work

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== DependencyRegistry.sol ==="
cat smart-contracts/DependencyRegistry.sol

echo ""
echo "=== IDependencyRegistry.sol ==="
cat smart-contracts/IDependencyRegistry.sol

echo ""
echo "=== StreamCore.sol (relevant sections) ==="
cat smart-contracts/StreamCore.sol

echo ""
echo "=== StreamMetadataEncoding.t.sol ==="
cat test/StreamMetadataEncoding.t.sol

echo ""
echo "=== Slither baseline ==="
fd -e json slither 2>/dev/null | head -5
fd slither_baseline 2>/dev/null | head -5
fd -e json . docs/ 2>/dev/null | head -10

echo ""
echo "=== ADR 0006 ==="
fd "0006" docs/ 2>/dev/null
fd "ADR" docs/ 2>/dev/null | head -10
fd -e md . docs/adr/ 2>/dev/null 2>&1 || fd -e md . docs/ 2>/dev/null | head -20

echo ""
echo "=== Roadmap ==="
fd -e md ROADMAP 2>/dev/null
fd -e md roadmap 2>/dev/null

Length of output: 41663


🏁 Script executed:

#!/bin/bash
echo "=== SLITHER_BASELINE.md ==="
cat ops/SLITHER_BASELINE.md

echo ""
echo "=== ADR 0006 ==="
cat docs/adr/0006-metadata-freeze.md

echo ""
echo "=== ROADMAP.md ==="
cat ops/ROADMAP.md

echo ""
echo "=== StreamFixture.sol ==="
fd StreamFixture 2>/dev/null | head -5
cat test/helpers/StreamFixture.sol 2>/dev/null || fd -e sol StreamFixture 2>/dev/null | xargs cat

echo ""
echo "=== CharacterizationTestBase.sol ==="
cat test/helpers/CharacterizationTestBase.sol 2>/dev/null

echo ""
echo "=== Assertions.sol ==="
cat test/helpers/Assertions.sol 2>/dev/null

Length of output: 156312



@punk6529: I've reviewed the full diff and supporting files. Here is my analysis across each requested area.


Summary

This PR correctly eliminates the encode-packed-collision path in StreamCore.retrieveDependencyScript, introduces typed chunk and content hashes in DependencyRegistry, and adds focused tests proving ambiguous chunk layouts render the same JavaScript but produce distinct proof hashes. The Slither baseline, roadmap, and ADR 0006 are internally consistent with the code delta.


1. Typed Dependency Chunk/Content Hash Semantics

Typehash strings match usage exactly:

Typehash Fields encoded
DEPENDENCY_SCRIPT_CHUNK_TYPEHASH (TYPEHASH, index, keccak256(chunkBytes), chunkBytes.length)
DEPENDENCY_SCRIPT_CONTENT_TYPEHASH (TYPEHASH, dependencyNameAndVersion, chunkCount, chunksHash)

The rolling accumulator in getDependencyScriptContentHash:

chunksHash = keccak256(abi.encode(chunksHash, getDependencyScriptChunkHash(dependencyNameAndVersion, i)));

correctly makes chunk order significant (a reversed chunk array produces a different chunksHash) and the initial seed bytes32(0) with the chunkCount field in the outer struct means a zero-chunk dependency still produces a deterministic, distinct hash.

One note on the zero-chunk case: when libraryScript.length == 0 the loop is skipped and the result is keccak256(abi.encode(CONTENT_TYPEHASH, key, 0, bytes32(0))). This is correct and deterministic but is not covered by a test. It is a degenerate case for completeness only — not a correctness risk.


2. StreamCore.retrieveDependencyScriptContentHash ABI/View Surface

The implementation is clean:

function retrieveDependencyScriptContentHash(uint256 tokenId) public view returns (bytes32) {
    _requireMinted(tokenId);
    uint256 collectionId = tokenIdsToCollectionIds[tokenId];
    return dependencyRegistry.getDependencyScriptContentHash(
        collectionInfo[collectionId].collectionDependencyScript
    );
}

The _requireMinted gate is consistent with the rest of the tokenId-centric API surface. One observation worth recording for P1 planning: this function requires a minted token to access what is fundamentally a collection-level property. Off-chain tooling or front-ends wishing to display the dependency hash for a collection before any token is minted must call DependencyRegistry.getDependencyScriptContentHash directly with the dependency key. A future retrieveCollectionDependencyScriptContentHash(uint256 collectionId) view on StreamCore would round out the surface, but this is a P1 convenience, not a P0 correctness issue.

The IDependencyRegistry interface correctly exposes both getDependencyScriptChunkHash and getDependencyScriptContentHash. The implementation declares getDependencyScriptChunkHash as public while the interface declares it external; that is valid in Solidity — public satisfies an external interface requirement.


3. Generative Script Rendering Compatibility

retrieveDependencyScript now uses initialized string.concat:

string memory scripttext = "";          // ← fixes uninitialized-local Slither row
for (...) {
    scripttext = string.concat(scripttext, dependencyRegistry.getDependencyScript(..., i));
}

This is semantically equivalent to the previous abi.encodePacked path for string concatenation, so the rendered output is preserved. The test explicitly validates this identity (see §4 below).

retrieveGenerativeScript still uses abi.encodePacked in two places:

  1. Collection script loop (Line 394): string(abi.encodePacked(scripttext, ...collectionScript[i])) — this is the collection script path, outside this PR's scope. The Slither baseline correctly still lists StreamCore.retrieveGenerativeScript(...).scripttext as Open at smart-contracts/StreamCore.sol#L394.

  2. Final JS assembly: uses abi.encodePacked over multiple fixed-string separators and dynamic values. No collision risk exists here because every adjacent pair of dynamic values is separated by a distinct literal string constant ("let hash='", "';let tokenId=", etc.). This is a different pattern from the encode-packed-collision detector's concern and does not trigger it. The metadata injection risk from unescaped tokenData and dependency scripts is correctly deferred to P1-META-006.


4. Test Coverage in StreamMetadataEncoding.t.sol

testDependencyScriptHashSeparatesAmbiguousChunkBoundaries — Covers the primary P0-META-001 claim:

  • ["ab", "c"] and ["a", "bc"] both render to "abc" (script output unchanged) ✅
  • Their content hashes are distinct ✅
  • The rendered script is verified against _expectedGenerativeScript, which exercises the full retrieveGenerativeScriptretrieveDependencyScript path ✅

testDependencyChunkHashIncludesIndexAndLength — Covers the chunk-index binding:

  • Two empty-string chunks at indices 0 and 1 produce distinct hashes ✅
  • This prevents an attacker from rearranging zero-length padding chunks without changing the proof ✅

Minor gap: The test file duplicates both typehash constants as private constants and independently recomputes the expected hashes in _contentHash/_chunkHash. This is exactly the right approach — it proves the contract's output matches an independently specified encoding, not just itself. The duplication is intentional and valuable.


5. Slither Baseline and Roadmap

The SLITHER_BASELINE.md delta is accurate:

Metric Before After
High 9 8
Medium 29 28
encode-packed-collision 1 0
uninitialized-local 11 10

The P0-META-001 row resolution text is precise: it identifies abi.encode, dependency key, chunk count, chunk index, chunk byte length, and per-chunk content hash as the components, and references test/StreamMetadataEncoding.t.sol as the required regression test.

The roadmap test matrix row for "Dependency script packed encoding" correctly shows Passing with the scope note distinguishing P0 (segment-safe encoding) from P1-META-003 (dependency versioning, provenance, registry identity pinning).


6. ADR 0006 Scope Boundary

The ADR 0006 "Current Behavior" section was updated with this precise language:

retrieveDependencyScriptContentHash(tokenId) exposes a typed dependency content hash for the referenced dependency key. The hash is segment-safe for the current registry content, but it is not a full freeze manifest by itself because it does not pin registry identity, provenance, or immutable version lifecycle.

This is the correct framing. The P0/P1 boundary is clean: this PR closes the encoding ambiguity, and P1-META-003 retains ownership of immutable versions, deprecation semantics, and freeze-manifest pinning. The ADR "Open Follow-Ups" section correctly preserves the P0-META-001 typed hash regression suite as the foundation P1 work will build on.


Minor Observations (Non-blocking)

  1. NatSpec absent on new public functionsgetDependencyScriptChunkHash, getDependencyScriptContentHash, and retrieveDependencyScriptContentHash have no @notice/@param/@return docs. This will matter before Gate F.

  2. Zero-chunk dependency hash untested — A test asserting getDependencyScriptContentHash for an empty dependency key (0 chunks) returns the deterministic expected value would be a useful completeness check for P1-META-003.

  3. addDependency allows post-freeze registry mutation — Acknowledged and correctly deferred to P1-META-003. The content hash correctly reflects the current registry state at query time; callers needing stability must use the immutable versioning planned in P1.

Overall the PR is correct and well-scoped. The typed hash construction is collision-resistant, the rendered output is preserved, the Slither delta is accurate, and the ADR scope boundary is clean.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Latest head addresses the non-blocking observations from comment 4673171581:

  • Added NatSpec for the new dependency hash public views.
  • Added zero-chunk dependency content hash coverage.
  • Refreshed focused/full/Windows/Slither validation; Slither counts remain unchanged at total=685, high=8, medium=28, encode_packed_collision=0, uninitialized_local=10.

Please re-check the latest head.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== DependencyRegistry.sol ==="
cat smart-contracts/DependencyRegistry.sol

echo ""
echo "=== IDependencyRegistry.sol ==="
cat smart-contracts/IDependencyRegistry.sol

echo ""
echo "=== StreamCore.sol (retrieveDependencyScriptContentHash) ==="
grep -n -A 15 "retrieveDependencyScriptContentHash" smart-contracts/StreamCore.sol

echo ""
echo "=== StreamMetadataEncoding.t.sol ==="
cat test/StreamMetadataEncoding.t.sol

echo ""
echo "=== SLITHER_BASELINE.md ==="
cat ops/SLITHER_BASELINE.md

Length of output: 30322



@punk6529: Re-checked the latest head. All three previously non-blocking observations are resolved.


Observation 1 — NatSpec on new public views ✅

DependencyRegistry now has full NatSpec on both new functions:

Function @notice @param coverage @return
getDependencyScriptChunkHash both params
getDependencyScriptContentHash one param

One minor note: the grep for StreamCore.retrieveDependencyScriptContentHash was run without leading-line context (-B), so the NatSpec block above Line 742 was not captured. The two DependencyRegistry functions are the substantive new surface area, and those are fully documented.


Observation 2 — Zero-chunk dependency content hash coverage ✅

testEmptyDependencyContentHashIsDeterministic correctly:

  • Registers a dependency with a zero-length chunk array.
  • Calls getDependencyScriptContentHash directly on the registry (no minted token needed — the right call site).
  • Asserts equality against the independently computed _contentHash(dependencyKey, new string[](0)).

This confirms the degenerate chunkCount=0, chunksHash=bytes32(0) path produces keccak256(abi.encode(CONTENT_TYPEHASH, key, 0, bytes32(0))) deterministically.


Observation 3 — Slither baseline counts ✅

ops/SLITHER_BASELINE.md confirms:

Metric Value
Total 685
High 8
Medium 28
encode-packed-collision 0
uninitialized-local 10

Unchanged from the prior capture, consistent with a documentation-only + NatSpec + test delta.


The PR is correct and complete for the P0-META-001 scope. No new issues identified.

[approve]

@punk6529 punk6529 merged commit 20bd9d9 into main Jun 10, 2026
2 checks passed
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.

[P0-META-001] Replace dependency-script packed concatenation with collision-safe encoding

1 participant