LibHashNoAlloc, HASH_NIL, src/LibHashNoAlloc.sol, rain-lib-hash and any import line appear nowhere in the README; hashBytes/hashWords/combineHashes first appear as bare identifiers in the collision list. The Solution bullet promises "a reference implementation of the pattern that can be fuzzed against", but the library ships three leaf hashers and a binary combine; composition and the fold are written inline per type (the only fold is a private test helper). Bullet four of "Given the above, we can" ("Provide a guide for implementation, maintenance and quality assurance", L286) maps to no heading, while L313 tells implementers to hardcode the size per type without saying what keeps the struct and the literal equal.
Do: a one-sentence description at L1 naming the library, file and package; reword L221-222 as "primitives ... composition written inline per type, pinned by test/HashPattern.t.sol and test/HashPatternFold.t.sol"; a #### Reference implementation section before "Security of composition" with HASH_NIL, the four signatures with overloads, forge soldeer install rain-lib-hash~<version> and the versioned import line import {LibHashNoAlloc, HASH_NIL} from "rain-lib-hash-<version>/src/LibHashNoAlloc.sol"; (signatures and a one-line collision pointer only; do not re-teach the NatSpec); a #### Implementing and testing the pattern section before "Dev stuff": each inline implementation repeats a type's shape twice (definition + size literal); the check is a fuzz test per hashed type against a builtin oracle that never shares the literal (abi.encode of the words for value-only structs, the builtin composition for pointer fields) plus an fmp-delta test that the literal is the allocation — HashPattern.t.sol and MemoryLayout.t.sol are those tests for Foo. Section texts in the fix below.
Findings from the audit run
F43 [LOW] dim 3 cat — README.md:1-3, 215-222, 511-523
README never names the library, its API, HASH_NIL, the soldeer package or an import line, and its 'reference implementation of the pattern' promise is broader than what ships
LibHashNoAlloc, HASH_NIL, src/LibHashNoAlloc.sol, rain-lib-hash and any import appear nowhere; hashBytes/hashWords/combineHashes first appear as bare identifiers in the collision list with no signatures or overloads; the nil seed is described only as keccak256(0, 0). The Solution section promises 'a reference implementation of the pattern that can be fuzzed against', but the library ships the three leaf hashers and a binary combine only — no struct composition or fold function is exported (the only fold is the private foldPattern helper in a test), so a consumer with an inline fold (rainlang.interface) has no exported reference to fuzz against and the README does not say so.
EVIDENCE: README L1-3 # rain.lib.hash / ## Problem; L221-222 'Provide a reference implementation of the pattern that can be fuzzed against to show inline implementations of the pattern provide valid outputs'. grep -n -E 'LibHashNoAlloc|HASH_NIL|rain-lib-hash|soldeer|import' README.md -> none; hashBytes/hashWords/combineHashes only at L513-523. src L8, 96, 110, 122, 136. test/HashPatternFold.t.sol L36 function foldPattern(Foo[] memory foos) internal pure (test-internal). Survey: rainlang.interface hand-inlines the composition.
FIX: README L1: one-sentence description naming LibHashNoAlloc in src/LibHashNoAlloc.sol, published to soldeer as rain-lib-hash. L221-222: 'Provide a reference implementation of the pattern's primitives, hashBytes, hashWords and combineHashes plus the HASH_NIL seed, that inline implementations can be fuzzed against; composition (the per-struct steps and the fold) is written inline per type from those primitives, worked as fuzz tests in test/HashPattern.t.sol and test/HashPatternFold.t.sol.' Insert a '#### Reference implementation' section before 'Security of composition' listing HASH_NIL, the four functions with signatures and overloads, forge soldeer install rain-lib-hash~<version> and the import line (text in d3:docs:readme#3).
F44 [LOW] dim 3 cat — README.md:281-286, 302
README promises a 'guide for implementation, maintenance and quality assurance' that no section provides, while prescribing a hand-hardcoded per-type size literal with no stated check
Bullet four of 'Given the above, we can' maps to no heading; the pattern tells implementers to 'just hardcode it per-type' (the field count twice: struct + 0x80 literal) without saying what keeps the two equal, and never tells the reader that test/HashPattern.t.sol and test/MemoryLayout.t.sol are the QA template. A consumer adding a field to a signed struct and leaving the literal hashes one field fewer than the signature is believed to cover.
EVIDENCE: README L283-286 bullets; L286 '- Provide a guide for implementation, maintenance and quality assurance'; L302 'Because the size of the data never changes, we can just hardcode it per-type.'; L64-68 names the maintenance concern. grep -n '^#' README.md: no implementation/QA heading between 'Security of composition' (L466) and 'Dev stuff' (L539).
FIX: Add before '## Dev stuff' a '#### Implementing and testing the pattern' section: every inline implementation repeats each type's shape twice (definition + size literal); the check is a fuzz test per hashed type asserting the inline hash equals keccak256(abi.encode(<fields>)) (static) or the builtin composition (pointers), plus an fmp-delta test that the size literal is the allocation; test/HashPattern.t.sol and test/MemoryLayout.t.sol are those tests for Foo (text in d6:hazard:cat1#6 / d3:docs:readme#4). Or delete L286.
Severity check. LOW is right. Scheme L118: 'minor issue or theoretical risk'. The hazard shape is real in the only source consumer — rainlang.interface src/lib/caller/LibContext.sol hash(SignedContextV1) L76-92 hand-inlines the composition with hardcoded 0x20 / offset literals for a signed struct — but that consumer carries its own oracle (test/src/lib/caller/LibContextSlow.sol per SURVEY), the literal lives in consumer code the README cannot check, and rainlang.interface#133 is dropping the dependency. Nothing in this repo's production path is affected; the library's own tests already embody the check. Not INFO: L286 is an accuracy defect (a promise the document does not keep, dimension 3 'dangling reference' / completeness) rather than a pure suggestion. Not MEDIUM: no mitigating-factor-bounded real concern in this repo. Under Pass-6 L373 (if reframed as a hazard finding) it would rate INFO/LOW — 'a convention kept by discipline today that could be cheaply enforced' / breaks no production here — consistent with LOW.
Fix texts
Whole-repo audit of rainlanguage/rain.lib.hash at 1e6e59f3d1fc4fd30c86a5836aa156eed382e729 (main, 2026-09-06; audit skill 0.35.0 at claude-audit-skills 9fefd09902d777aabc967ef6625d038e6c2101b7). Pass-2 gate: ledgered (adversarial-mutation-test record 2026-09-03 at 5055221444d547aba034b88fbd2525973b17d9a9; library code unchanged since). Fixes marked verified were applied to a scratch copy and run with forge in the rainix sol-shell (dbcd9d3c) during the audit; everything else is proposed only. Full run record: /home/gildlab/artifacts/rain.lib.hash-audit-2026-09-06/.
LibHashNoAlloc,HASH_NIL,src/LibHashNoAlloc.sol,rain-lib-hashand any import line appear nowhere in the README;hashBytes/hashWords/combineHashesfirst appear as bare identifiers in the collision list. The Solution bullet promises "a reference implementation of the pattern that can be fuzzed against", but the library ships three leaf hashers and a binary combine; composition and the fold are written inline per type (the only fold is a private test helper). Bullet four of "Given the above, we can" ("Provide a guide for implementation, maintenance and quality assurance", L286) maps to no heading, while L313 tells implementers to hardcode the size per type without saying what keeps the struct and the literal equal.Do: a one-sentence description at L1 naming the library, file and package; reword L221-222 as "primitives ... composition written inline per type, pinned by test/HashPattern.t.sol and test/HashPatternFold.t.sol"; a
#### Reference implementationsection before "Security of composition" withHASH_NIL, the four signatures with overloads,forge soldeer install rain-lib-hash~<version>and the versioned import lineimport {LibHashNoAlloc, HASH_NIL} from "rain-lib-hash-<version>/src/LibHashNoAlloc.sol";(signatures and a one-line collision pointer only; do not re-teach the NatSpec); a#### Implementing and testing the patternsection before "Dev stuff": each inline implementation repeats a type's shape twice (definition + size literal); the check is a fuzz test per hashed type against a builtin oracle that never shares the literal (abi.encodeof the words for value-only structs, the builtin composition for pointer fields) plus an fmp-delta test that the literal is the allocation — HashPattern.t.sol and MemoryLayout.t.sol are those tests forFoo. Section texts in the fix below.Findings from the audit run
F43 [LOW] dim 3 cat — README.md:1-3, 215-222, 511-523
README never names the library, its API, HASH_NIL, the soldeer package or an import line, and its 'reference implementation of the pattern' promise is broader than what ships
LibHashNoAlloc,HASH_NIL,src/LibHashNoAlloc.sol,rain-lib-hashand any import appear nowhere; hashBytes/hashWords/combineHashes first appear as bare identifiers in the collision list with no signatures or overloads; the nil seed is described only askeccak256(0, 0). The Solution section promises 'a reference implementation of the pattern that can be fuzzed against', but the library ships the three leaf hashers and a binary combine only — no struct composition or fold function is exported (the only fold is the private foldPattern helper in a test), so a consumer with an inline fold (rainlang.interface) has no exported reference to fuzz against and the README does not say so.EVIDENCE: README L1-3
# rain.lib.hash / ## Problem; L221-222 'Provide a reference implementation of the pattern that can be fuzzed against to show inline implementations of the pattern provide valid outputs'.grep -n -E 'LibHashNoAlloc|HASH_NIL|rain-lib-hash|soldeer|import' README.md-> none; hashBytes/hashWords/combineHashes only at L513-523. src L8, 96, 110, 122, 136. test/HashPatternFold.t.sol L36function foldPattern(Foo[] memory foos) internal pure(test-internal). Survey: rainlang.interface hand-inlines the composition.FIX: README L1: one-sentence description naming
LibHashNoAllocinsrc/LibHashNoAlloc.sol, published to soldeer asrain-lib-hash. L221-222: 'Provide a reference implementation of the pattern's primitives,hashBytes,hashWordsandcombineHashesplus theHASH_NILseed, that inline implementations can be fuzzed against; composition (the per-struct steps and the fold) is written inline per type from those primitives, worked as fuzz tests in test/HashPattern.t.sol and test/HashPatternFold.t.sol.' Insert a '#### Reference implementation' section before 'Security of composition' listing HASH_NIL, the four functions with signatures and overloads,forge soldeer install rain-lib-hash~<version>and the import line (text in d3:docs:readme#3).F44 [LOW] dim 3 cat — README.md:281-286, 302
README promises a 'guide for implementation, maintenance and quality assurance' that no section provides, while prescribing a hand-hardcoded per-type size literal with no stated check
Bullet four of 'Given the above, we can' maps to no heading; the pattern tells implementers to 'just hardcode it per-type' (the field count twice: struct + 0x80 literal) without saying what keeps the two equal, and never tells the reader that test/HashPattern.t.sol and test/MemoryLayout.t.sol are the QA template. A consumer adding a field to a signed struct and leaving the literal hashes one field fewer than the signature is believed to cover.
EVIDENCE: README L283-286 bullets; L286 '- Provide a guide for implementation, maintenance and quality assurance'; L302 'Because the size of the data never changes, we can just hardcode it per-type.'; L64-68 names the maintenance concern.
grep -n '^#' README.md: no implementation/QA heading between 'Security of composition' (L466) and 'Dev stuff' (L539).FIX: Add before '## Dev stuff' a '#### Implementing and testing the pattern' section: every inline implementation repeats each type's shape twice (definition + size literal); the check is a fuzz test per hashed type asserting the inline hash equals
keccak256(abi.encode(<fields>))(static) or the builtin composition (pointers), plus an fmp-delta test that the size literal is the allocation; test/HashPattern.t.sol and test/MemoryLayout.t.sol are those tests forFoo(text in d6:hazard:cat1#6 / d3:docs:readme#4). Or delete L286.Severity check. LOW is right. Scheme L118: 'minor issue or theoretical risk'. The hazard shape is real in the only source consumer — rainlang.interface src/lib/caller/LibContext.sol
hash(SignedContextV1)L76-92 hand-inlines the composition with hardcoded 0x20 / offset literals for a signed struct — but that consumer carries its own oracle (test/src/lib/caller/LibContextSlow.sol per SURVEY), the literal lives in consumer code the README cannot check, and rainlang.interface#133 is dropping the dependency. Nothing in this repo's production path is affected; the library's own tests already embody the check. Not INFO: L286 is an accuracy defect (a promise the document does not keep, dimension 3 'dangling reference' / completeness) rather than a pure suggestion. Not MEDIUM: no mitigating-factor-bounded real concern in this repo. Under Pass-6 L373 (if reframed as a hazard finding) it would rate INFO/LOW — 'a convention kept by discipline today that could be cheaply enforced' / breaks no production here — consistent with LOW.Fix texts
Whole-repo audit of rainlanguage/rain.lib.hash at
1e6e59f3d1fc4fd30c86a5836aa156eed382e729(main, 2026-09-06; audit skill 0.35.0 at claude-audit-skills9fefd09902d777aabc967ef6625d038e6c2101b7). Pass-2 gate:ledgered(adversarial-mutation-test record 2026-09-03 at5055221444d547aba034b88fbd2525973b17d9a9; library code unchanged since). Fixes marked verified were applied to a scratch copy and run withforgein the rainix sol-shell (dbcd9d3c) during the audit; everything else is proposed only. Full run record:/home/gildlab/artifacts/rain.lib.hash-audit-2026-09-06/.