Skip to content

fix(typedData): do not hash a trailing 0 for enum variants without arguments - #1721

Closed
ara-stock wants to merge 1 commit into
starknet-io:developfrom
ara-stock:fix/1341-enum-unit-variant
Closed

ara-stock wants to merge 1 commit into
starknet-io:developfrom
ara-stock:fix/1341-enum-unit-variant

Conversation

@ara-stock

Copy link
Copy Markdown

Motivation and Resolution

Fixes #1341.

In revision 1 typed data, an enum variant declared as () (for example { "No Fee": [] }) was encoded as hash_array(variant_index, 0). encodeValue splits the parameter list with "()".slice(1, -1).split(','), which returns [""]. The empty subtype was then passed through unchanged and hashed as 0.

SNIP-12 encodes only the parameters of the chosen variant:

Enc[enum] = hash_array(type_hash(enum), variant_index, Enc[chosen_variant_parameter1],..., Enc[chosen_variant_parameterN])

A variant with no parameters therefore adds nothing after variant_index. starknet.py does the same: _extract_enum_types("()") returns [], and the result is hash_many([variant_index]).

The fix drops empty subtypes before encoding. Variants with parameters are not affected, and encodeType and getTypeHash do not change.

Message hashes for account 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826, using starknet.py 0.30.0 TypedData.from_dict(...).message_hash:

typed data starknet.js before starknet.js after starknet.py
{ "No Fee": [] } (example from the issue, added as a test) 0x25271e84cc1b753388cf16a5e22b8f2c001e5efd024e04f7edb03f161203754 0x60456a10c6ce8c808e1d2709efdf717929650ad6ce3f1680a662f271cc162ed 0x60456a10c6ce8c808e1d2709efdf717929650ad6ce3f1680a662f271cc162ed
__mocks__/typedData/example_enum.json (someEnum2: { "Variant 1": [] }) 0x6e61abaf480b1370bbf231f54e298c5f4872f40a6d2dd409ff30accee5bbd1e 0x69b8e06cf1afab12f3983bd75243a1b4a43d313dd407837292f715a28d57017 0x69b8e06cf1afab12f3983bd75243a1b4a43d313dd407837292f715a28d57017

Notes:

  • The message hash changes only for revision 1 typed data that uses a variant without arguments. Signatures that were made with the old hash for such messages will no longer verify against the new hash. I opened this against develop as a bug fix because the old value did not match the spec or starknet.py. If you prefer beta with a breaking-change marker, I can retarget it.
  • Like starknet.py, starknet.js does not include type_hash(enum) in the enum encoding, even though the spec formula does. I left that unchanged because it is a separate question, and changing it would break agreement with starknet.py.
  • example_enumNested.json does not choose a variant without arguments, so its hash does not change. I could not cross-check that file with starknet.py, because starknet.py rejects it with "Dangling types ... [StructB]".

RPC version (if applicable)

N/A

Usage related changes

  • typedData.getMessageHash (and so signing and verifying revision 1 messages) now returns the SNIP-12 and starknet.py value when a chosen enum variant has no arguments.

Development related changes

  • Updated the expected example_enum.json message hash. The new value matches starknet.py.
  • Added a focused test for a () variant. It checks that the encoded value is poseidon([variant_index]) with no trailing 0, and that the message hash equals the starknet.py reference value.

Checklist:

  • Performed a self-review of the code
  • Rebased to the last commit of the target branch (or merged it into my branch)
  • Linked the issues which this PR resolves
  • Documented the changes in code (API docs will be generated automatically)
  • Updated the tests
  • All tests are passing (npm run test:unit: 76 suites, 2125 tests passed. Also passed: npm run lint, npm run ts:check, prettier. I did not run the devnet integration suite, because this change only touches src/utils/typedData.ts.)

I used an AI assistant while preparing this; I reproduced the mismatch against starknet.py and ran the tests myself.

🤖 Generated with Claude Code

…guments

A revision 1 enum variant declared as `()` was encoded as
hash_array(variant_index, 0) because splitting the empty parameter list
produced one empty subtype. SNIP-12 encodes only the chosen variant's
parameters, so a variant without arguments hashes to
hash_array(variant_index). This matches starknet.py.

Fixes starknet-io#1341

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@PhilippeR26

Copy link
Copy Markdown
Collaborator

Thank you for the detailed analysis and the reference hashes.

You are right: the text of SNIP-12 revision 1 does not add a 0 for a variant without parameters.

However, we will not change how starknet.js hashes revision 1 messages. Wallets, dapps and deployed contracts were built against the current starknet.js output, so it has become the de facto standard for revision 1. Changing a revision 1 hash would break the signatures they create or verify. Earlier fixes that aligned revision 1 with the text (#1281, #1288) were reverted in #1291.

These differences will be fixed in SNIP-12 revision 2, not in revision 1: SNIP-12 revision 2 draft (starknet-io/SNIPs#178).

The draft covers this exact case: "A variant with no parameters hashes as hash_array(type_hash(E), variant_index)". It also says: "Revision 1 is unchanged and remains valid. Its hashes are hard-coded in deployed account contracts [...], so its rules cannot be corrected in place." If you want to help, the revision 2 discussion is the right place.

For these reasons, I am closing this PR without merging it.

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.

SNIP-12 Incorrect encoding for empty enum variant

2 participants