Skip to content

fix(governance-api): normalize EVM (MetaMask) submitters to their Zilliqa address#778

Merged
frankmeds merged 1 commit into
mainfrom
fix/governance-evm-identity-normalization
Jun 5, 2026
Merged

fix(governance-api): normalize EVM (MetaMask) submitters to their Zilliqa address#778
frankmeds merged 1 commit into
mainfrom
fix/governance-evm-identity-normalization

Conversation

@frankmeds

Copy link
Copy Markdown
Contributor

Problem

MetaMask (EVM) users are broken across every on-chain identity check. They sign with their EVM 0x address (Keccak256-derived), but the governance checks all key off the user's Zilliqa address (SHA256-derived) — a different address for the same key:

  • 30-gZIL gate (message.ts): the EVM address holds no gZIL → always MIN_BALANCE.
  • Voter scoring (get-scores.ts reads proposal.balances[addr]): the EVM voter isn't in the Zilliqa-keyed snapshot → score 0 → dropped from the tally.
  • Members / core tab (Proposals.vue): members are zil1… (Zilliqa base16); an EVM author never matches.

Fix

Recover the signer's public key from the EVM personal_sign signature, derive the canonical Zilliqa address (the one ZilPay shows for that key, where the user's gZIL/ZRC2 balances and membership live), and use it as the stored identity.

  • lib/utils/verify-evm-signature.ts: add zilliqaAddressFromEVMSignature()hashMessageSigningKey.recoverPublicKey → compress → getAddressFromPublicKey (all from already-installed ethers@6 + @zilliqa-js/crypto).
  • lib/routes/message.ts: at the single POST /api/message choke point, after the EVM signature is verified, replace body.address with the derived Zilliqa address. Everything downstream (gZIL gate getLiquidity, IPFS pin, DB Message.create) then uses the real Zilliqa identity — for both proposals and votes (same handler).

No frontend changes: with the stored address now a real Zilliqa base16, the members compare, toBech32Address display, and scoring all line up.

Scope / decisions

  • New submissions only — no migration of pre-existing EVM-addressed records (there are likely none real, since MetaMask gZIL proposals never worked).
  • Identity model: a MetaMask user is identified by their key's Zilliqa address. Caveat: it only sees gZIL held on that key's Zilliqa address — gZIL on a separate ZilPay account (different key) still won't count.
  • Known cosmetic limitation: User.vue compares the stored address to the connected EVM account, so a MetaMask user may not see the "You" badge on their own items. Out of scope (would need a client-side Zilliqa-address derivation).

Tests

  • New lib/utils/verify-evm-signature.test.ts: signs with a fixed test key and asserts zilliqaAddressFromEVMSignature(msg, sig) equals the ground-truth getAddressFromPublicKey(compress(pubkey)) and differs from the EVM address. Wired into npm test (now runs both suites; green).

Security review

Reviewed (security-reviewer): 0 critical / 0 major. Identity binding is sound — the Zilliqa address is derived from the same signature already verified against body.address, so a user can only obtain their own address; forging would require a valid secp256k1 signature for the target key. Crypto verified by round-trip; malformed signatures throw and are caught (→ 400).

Pre-existing follow-up (not introduced here): body.sig.message is never reconciled with body.msg on either the EVM or Schnorr path — a client can sign one string and submit a different proposal body. Worth a separate fix (assert sig.message === body.msg).

Verify on staging

  1. npm test green in governance-api.
  2. With MetaMask, create a proposal on the gZIL space using a key whose Zilliqa address holds ≥30 gZIL → expect 201 (no MIN_BALANCE); confirm GET /api/<space>/proposals stored a 0x… Zilliqa address (not the EVM one).
  3. Vote from that MetaMask account → the vote appears in the tally with correct weight.
  4. Logs show "EVM identity normalized to Zilliqa address" with the derived zilAddress.

…liqa address

MetaMask users signed with their 0x (Keccak) address, but the gZIL gate, the voter-scoring snapshot, and space members all key off the Zilliqa (SHA256) address - a different address for the same key - so EVM users failed every check.

- Add zilliqaAddressFromEVMSignature(): recover the pubkey from the EVM personal_sign signature and derive the canonical Zilliqa address.
- In POST /api/message, after verifying the EVM signature, replace body.address with the derived Zilliqa address. Covers proposals and votes (same handler); the gate, IPFS pin, and DB record then use the real Zilliqa identity.
- Add a regression test and wire it into npm test.

New submissions only; no migration. Frontend unchanged (a MetaMask user may not see the 'You' badge on their own items - cosmetic, tracked separately).
@frankmeds frankmeds merged commit f0339c6 into main Jun 5, 2026
3 checks passed
@frankmeds frankmeds deleted the fix/governance-evm-identity-normalization branch June 5, 2026 08:04
frankmeds added a commit that referenced this pull request Jun 5, 2026
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