Rule
checked-digest-narrowing
A full canonical hash is range-checked by a Uint cast on its source route to a ledger write or exported return.
Specimen 58 of 96 · Other active opt-ins / Privacy and cryptographic derivations
- Severity
- info
- Confidence
- high
- Maturity
- experimental
- Selection
- Opt-in
- Policy intent
- not required
- Release
- v0.1.1
- Engine facts used
resolved-sourcecanonical-stdlib-callsresolved-typeslocal-result-uses
Selection and scope#
Not selected by default. Select it with --detectors or the detectors list in nightshade.json. Either one replaces profile selection, so list every rule you want.
./.nightshade/node_modules/.bin/nightshade ./contracts --detectors checked-digest-narrowing
./.nightshade/node_modules/.bin/nightshade detector show checked-digest-narrowingThe first command runs only this rule; --detectors takes a comma-separated list. The second prints the rule's metadata from your installed version.
Severity is review priority. It does not express exploitability or impact. Confidence is detector metadata, not a probability. All built-in rules are experimental.
Compiler scope: the engine supports the pinned profiles 0.28.0, 0.29.0, 0.30.0, 0.31.1, 0.33.0-rc.2, 0.34.0, 0.35.0-rc.1 and 0.35.0, with ZKIR2 by default. Support for this rule can be narrower; the rule documentation below states any narrower scope.
Corpus checks#
Synthetic corpus checks for this rule in the 0.1.1 proof run. A check counts only with compiler acceptance and complete analysis.
| Positive detected | Corrected quiet | Lookalike quiet | Other negative quiet | Incomplete |
|---|---|---|---|---|
| 1/1 | 1/1 | 1/1 | 0/0 | 0 |
No deviations were recorded for this rule.
These are behavior checks on synthetic fixtures, not a precision or recall estimate. See evaluation.
Rule documentation#
From docs/detectors/checked-digest-narrowing.md at v0.1.1.
Availability: opt-in. Severity informational, confidence high for the resolved source relationship. Select with --detectors checked-digest-narrowing.
Compact converts Bytes<m> to Uint by decoding the entire byte vector as a little-endian integer and checking the target range. It does not truncate the value to the target width. A cast of a full hash to Uint<64> therefore checks all 32 bytes against 2^64 - 1.
Example, scenario and recommendation#
const digest = persistentHash<Uint<64>>(value);
const narrowed = digest as Uint<64>;
recordedDigest = disclose(narrowed as Bytes<32>);If the application meant to store a full digest identifier, retain digest. If a numeric projection is intended, define the exact byte selection or other protocol-approved projection before converting it. Preserve deliberate checked conversions. Altering an existing stored representation requires protocol and migration review.
Matching and known intentional cases#
The rule follows a canonical persistentHash or keccak256 Bytes<32> result through immutable local identity aliases, disclosure and identity casts. It reports a cast to a supported Uint type when the result, optionally re-encoded as Bytes, has a direct ordinary Cell assignment or scalar exported return in that resolved function. Custom exclusive Uint bounds retain their actual maximum. Findings group source uses per cast and canonical function instance.
This is an entry-referenced source relationship, including syntactic dead branches and zero-iteration bodies. It does not prove successful execution, a failing input, every-path rejection, hash-output distribution, funds loss or deployed impact. Proof-of-work targets and intentional partial checked conversions can match; these intent-dependent shapes keep the rule opt-in. An explicit equality/bound elsewhere does not change the reported conversion semantics.
Already bounded numeric encodings, selected/reconstructed/transformed digests, Field casts, arithmetic uses, aggregate returns, ADT/projected writes, outer captures and arbitrary helper-result forwarding are outside the matching scope. Canonical imported aliases and instantiated source modules are distinguished from application namesakes. A shared private/hash provenance is insufficient to establish an unchanged digest.
Evidence and incomplete results#
Reference: Compact 0.34.0 source 1f671fc27818df2b2676b3a97f85b2b821756243, doc/compact-reference.mdx:3296-3304 and canonical hash API definitions. The owned Counter positive emits convertBytesToUint(18446744073709551615n, 32, digest, …) and preserves the original Counter increment. Corrected/full-digest and lookalike/bounded-encoding controls remain quiet. Compiler-only output inspection does not execute a runtime or proof and is not an independent bug label.
The owned triplet compiles and retains the expected conversion shape on pinned 0.30.0, 0.31.1, 0.33.0-rc.2, 0.34.0 and 0.35.0. The reported maximum is the declared source-type maximum. Compiler 0.30.0 emits a Number maximum literal and an older byte-encoding helper; later profiles emit BigInt. These compile-only checks do not qualify runtime cutoff equivalence across compiler/runtime versions.
The source query charges traversal, depth, alias copies and entry copies. On exhaustion it discards partial observations and emits a rule-scoped incomplete diagnostic. Baseline analysis coverage remains separate; incomplete results are never clean. No scan budgets or engine transfers are changed by this rule.
Suppress a match#
If a match is intentional, suppress it on the line before with the exact rule ID and a reason of at least eight characters:
// nightshade-disable-next-line checked-digest-narrowing -- Reason this match is intentionalDiagnostics cannot be suppressed, and an unmatched directive for a selected rule makes analysis incomplete. See configuration for fingerprint suppressions and baselines.