Skip to content

[RLH-28] [LOW] Small doc inaccuracies: a README section citation that does not exist, a stale Yul comment, a wrong bucket qualifier, a wrong nesting-size sentence, an induction step attributed to the base case #68

Description

@thedavidmeister
  • test/MemoryLayout.t.sol L7 cites README section "Memory layout"; no such heading exists (the prose is under "### The pattern"); every other README citation in the tests names a real heading. LOW.
  • README L432 and test/HashPattern.t.sol L206: // Write C and D to scratch to produce the final hash E annotates keccak256(0, 0x40), which writes nothing; C and D were stored at L425/L430. Use "Hash C and D, already in scratch, to produce the final hash E".
  • README L234 "All non-struct types end up in one of 3 buckets" while L259-260 puts the struct's region in bucket one. Use "Every type ends up in one of 3 buckets".
  • test/MemoryLayout.t.sol L231-233 "an Outermost holding an Outer holding a Foo is 2 words at each level" — Foo is 4 words (asserted at L167); only the two outer sizes are asserted. Use "2 words at each of the two outer levels".
  • README L498-501 concludes "Equal folds therefore have equal lengths" from the base case alone; the length conclusion needs the peeling step. Replacement bullet (doc-only): "the fold over 0 items is the hash of 0 bytes and the fold over n >= 1 items is hash(fold(n - 1) + hash(item)), a hash of 64 bytes. If the folds over n and m items are equal, with n <= m, peeling one layer at a time gives fold(n - 1) = fold(m - 1) and equal last-item hashes, down to fold(0) = fold(m - n); the hash of 0 bytes cannot equal a hash of 64 bytes, so n = m, and each peeled pair of equal item hashes is, by induction over the item type, a pair of equal items."

Findings from the audit run

F49 [LOW] dim 3 cat — test/MemoryLayout.t.sol:7-8

Struct doc cites a README section "Memory layout" that does not exist

No README heading is named 'Memory layout'; the layout prose the tests pin lives under '### The pattern'. Every other section reference in the tests quotes a real heading.

EVIDENCE: test/MemoryLayout.t.sol L7-8 /// The struct README.md "Memory layout" describes: a 4-word region, one word ; grep -n '^#' README.md has no such heading; claim at README L258-262 under ### The pattern.

FIX: L7: /// The struct README.md "The pattern" describes: a 4-word region, one word

F46 [INFO] dim 3 cat — README.md:432-433 (test/HashPattern.t.sol:205-206)

Yul comment 'Write C and D to scratch' describes a step the code has already done

C is written to scratch at L425 and D at L430; the final statement only hashes them. The verbatim test copy carries the same comment.

EVIDENCE: README L425 mstore(0, keccak256(0, 0x40)), L430 mstore(0x20, keccak256(add(deref_, 0x20), mload(deref_))), L432-433 // Write C and D to scratch to produce the final hash E / let E := keccak256(0, 0x40); test/HashPattern.t.sol L205-206.

FIX: README L432 and test L205: // Hash C and D, already in scratch, to produce the final hash E.

Fix: comment-only edit on both lines; no bytecode, no assertion, no ruling touched, compiles trivially (no forge run needed for a // line). Proposed wording "Hash C and D, already in scratch, to produce the final hash E" is accurate and complete. No test is proposed and none is appropriate — the existing testHandlingPointers already asserts e == keccak(C||D), so behaviour is pinned; the gap is purely documentation. Fix is complete: both occurrences (README + test) named.

Severity: INFO is right. Skill scheme L118-120: "suggestion for improvement with no direct risk". Dimension 3 (documentation), not Pass-6, so the L373 hazard scale does not apply; even read as a hazard, the comment cannot mislead anyone into a wrong hash because the surrounding comments on L424/L429 already state C and D are stored. Production impact for the internal-function hashing library (sole source importer rainlang.interface signed-context digest): zero — README example code and a test comment, no library source. Keep INFO.

F47 [INFO] dim 3 cat — README.md:234-238, 259-262

'All non-struct types end up in one of 3 buckets' but the next example puts a struct in bucket one

Structs and static arrays are exactly bucket one; the qualifier contradicts the Foo example that follows.

EVIDENCE: README L234 All non-struct types end up in one of 3 buckets:; L259-260 'The thing it points to falls into the first bucket, a 4-word region of memory defined by its type.'

FIX: README L234: Every type ends up in one of 3 buckets:

F51 [INFO] dim 3 cat — test/MemoryLayout.t.sol:231-233

Doc says the nested chain is '2 words at each level' but the innermost Foo is 4 words

Only Outermost and Outer are 2 words and only those two sizes are asserted; Foo is 0x80 (testFooIsFourWords).

EVIDENCE: L231-233 'Outer holding a Foo is 2 words at each level'; body L262, L264 assert the two outer sizes; L167 asserts Foo is 0x80.

FIX: L232: 'Outer holding a Foo is 2 words at each of the two outer levels, and'

F66 [INFO] dim 5 cat — README.md:498-501

Fold induction attributes the equal-length conclusion to the base case alone

The base case separates only length 0 from >= 1; equal lengths for n, m >= 1 follow by unwinding H(fold(n-1)‖H(x)) = H(fold(m-1)‖H(y)) until one side reaches 0. The 'therefore' skips the step the injectivity-across-lengths argument rests on.

EVIDENCE: README L498-501 'the fold over 0 items is the hash of 0 bytes, which cannot equal the hash of the 64 bytes any longer fold hashes. Equal folds therefore have equal lengths and, by induction, equal items.'

FIX: Replace L498-501 with the unwinding argument (text in d5:correctness:readme#4).


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/.

Activity

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

Metadata

Metadata

Labels

auditAudit findinglowAudit findingpass3Audit finding

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions