Commit 30762c8
committed
feat(detector): flag BLS12-381 proof points reaching a pairing without a subgroup check
Adds `bls_subgroup_check` (SANCT_BLS_SUBGROUP_UNCHECKED), which flags
BLS12-381 pairing usage where a proof point reaches the pairing call with
nothing having established subgroup or curve membership.
The pairing e: G1 x G2 -> GT is defined on the prime-order subgroups, not the
full curve. BLS12-381 has a cofactor in both groups (~2^64 in G1, ~2^318 in
G2), so the curve holds plenty of points outside the subgroup a proof system
reasons about. Pairing one of those evaluates the map outside the domain the
soundness argument covers, and two things follow:
- Malleability. Given a valid proof point P, an attacker can often produce
P + T for a small-order T such that the verification equation still holds.
The proof serializes differently but verifies identically, so any replay
defence keyed on the proof bytes — a used-proof set, a nullifier derived
from the encoding — is bypassed with a fresh-looking submission.
- Forgery. Off-subgroup, the relations the verifier checks no longer pin the
witness down. This is the mechanism behind the small-subgroup attacks that
made subgroup checks mandatory in the BLS signature standard.
The check costs a scalar multiplication per point, which is exactly why
implementations reach for the _unchecked variants and why this survives
review. Skipping it is safe only for points the contract produced itself.
Two shapes are reported, both of which put an unvalidated point in front of a
pairing:
1. A point built with an _unchecked constructor or deserializer
(deserialize_uncompressed_unchecked, from_compressed_unchecked,
new_unchecked, ...). In arkworks these are precisely the entry points that
skip the subgroup check.
2. A point arriving already typed as G1Affine / G2Affine / G1Projective /
G2Projective. Deserialization happened elsewhere, so nothing in this
function establishes membership.
False positives are held down three ways. The rule gates on the file actually
using BLS12-381 before parsing at all, so an _unchecked constructor in
unrelated code means nothing here. It requires a pairing, miller_loop,
final_exponentiation or verify call to be reached — deserializing a point for
storage makes no verification claim. And any membership check in the function
silences it, including the correct arkworks idiom of unchecked deserialization
followed by an explicit is_in_correct_subgroup_assuming_on_curve. Mapping into
the subgroup with clear_cofactor / mul_by_cofactor is accepted too, since
that is a valid remedy where rejecting is not appropriate.
`#[cfg(test)]` modules are skipped and inline
`sanctifier:ignore[SANCT_BLS_SUBGROUP_UNCHECKED]` is honoured, matching the
conventions of the surrounding rules.
Documented in docs/detectors/bls_subgroup_check.md with the cofactor figures,
the vulnerable shape, the fix (both conditions — is_in_correct_subgroup_-
assuming_on_curve assumes on-curve, as the name says), and references: the
CFRG BLS signature draft's KeyValidate requirement, Sean Bowe on BLS12-381's
cofactor structure, the 0xPARC ZK Bug Tracker, Trail of Bits on proof
malleability, and arkworks' Validate::Yes/No documentation for what the
_unchecked variants actually skip. Rows added to the detector catalog and the
finding-code table; the docs-coverage test enforces both.
Tests: 7 unit tests covering both flagged shapes, both accepted check forms,
the non-BLS and never-pairs cases, and suppression.
Note for reviewers: two pre-existing failures in `memory::tests` reproduce on
an unmodified checkout of main on macOS (memory sampling) and are unrelated to
this change.1 parent 8710a74 commit 30762c8
7 files changed
Lines changed: 564 additions & 0 deletions
File tree
- docs
- detectors
- tooling/sanctifier-core
- src
- rules
- tests/fixtures/corpus
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| 31 | + | |
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| 25 | + | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
127 | 128 | | |
128 | 129 | | |
129 | 130 | | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
130 | 137 | | |
131 | 138 | | |
132 | 139 | | |
| |||
0 commit comments