Skip to content

fix: reject non-canonical field elements in FieldArray SSZ decoding#56

Open
aryaethn wants to merge 1 commit into
leanEthereum:mainfrom
aryaethn:fix/reject-non-canonical-field-decoding
Open

fix: reject non-canonical field elements in FieldArray SSZ decoding#56
aryaethn wants to merge 1 commit into
leanEthereum:mainfrom
aryaethn:fix/reject-non-canonical-field-decoding

Conversation

@aryaethn

@aryaethn aryaethn commented Jul 5, 2026

Copy link
Copy Markdown

What

FieldArray::from_ssz_bytes decoded each 4-byte limb with F::new(u32::from_le_bytes(..)). F::new reduces its argument mod p, so a limb v and its alias v + p decode to the same field element. For KoalaBear (p = 2130706433), 2p - 1 < 2^32, so every canonical limb has such an alias that fits in a u32, while encoding always emits the canonical as_canonical_u32().

The result: the SSZ encoding of signatures, public keys, and secret keys is malleable — a distinct byte string decodes to the same object and still verifies — contradicting the canonical-encoding contract the code documents and that consensus-layer SSZ requires.

Fix

Reject any limb >= F::ORDER_U32 with DecodeError::BytesInvalid, and add a regression test covering the modulus boundary and a concrete alias. This mirrors the canonical-deserialization check already enforced on the field type in leanVM (audit finding F-01).

Tested

$ cargo test --lib array::tests                              # 16 passed; 0 failed
$ cargo test                                                 # full suite: 114 passed; 0 failed
$ cargo clippy --all-targets --all-features -- -D warnings   # clean

FieldArray::from_ssz_bytes decoded each limb with F::new(u32), which reduces
its argument mod p. Because 2p - 1 < 2^32, every canonical limb value v has a
non-canonical alias v + p that fits in a u32 and decodes to the same element,
while encoding always emits the canonical form. This made the SSZ encoding of
signatures, public keys, and secret keys malleable, contradicting the canonical
encoding contract required for consensus-layer compatibility.

Reject any limb >= F::ORDER_U32 with DecodeError::BytesInvalid and add a
regression test. Mirrors the canonical-deserialization check already enforced
on the field type in leanVM (audit finding F-01).
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.

1 participant