Slush web wallet: signPersonalMessage builds an expired zkLogin envelope

hi, not sure if this post is best here, or on the Sui discord, Slush web wallet: signPersonalMessage builds an expired zkLogin envelope

Evidence package - 25 Aug 2026. Ready to file to Slush / Mysten Labs.

One sentence: Slush web’s signPersonalMessage wraps a cryptographically
VALID Secp256r1 ephemeral signature in a zkLogin envelope whose proof carries
maxEpoch = 8 (current Sui mainnet epoch: 1229), so every correct verifier
refuses it - while the SAME wallet’s transactions carry maxEpoch = 1230 and
execute fine on mainnet.

The contrast, from the same wallet on the same day

Surface zkLogin maxEpoch Ephemeral scheme Verdict
Transaction GN4fTRkFzYF8SguVJf7w6LUuuoC6MFRq8e9hCGWPRwtX (register, executed on mainnet 24 Aug 13:08 UTC) 1230 2 (Secp256r1) valid, executed
Transaction B25LUBtAPRuHFjM4yCTQTLU8Pk56W2gWSeBddp4HxHGv (certify+complete, executed 13:15 UTC) 1230 2 (Secp256r1) valid, executed
Personal message (Seal SessionKey certificate, taps on 24-25 Aug) 8 2 (Secp256r1) refused by every verifier

Both transaction envelopes read directly from chain (GraphQL
transaction.signatures.signatureBytes, parsed with
@mysten/sui/zklogin parseZkLoginSignature).

The personal-message evidence (QA build p20260825c, 25 Aug 05:23 UTC)

  • Wallet: Slush web, com.mystenlabs.suiwallet.web, registered via
    @mysten/slush-wallet 1.1.13 (1.1.18 is byte-identical in both sign
    methods - the envelope is built by the my.slush.app popup, not the npm
    package). Account: zkLogin, 0xf4502a93...aa3c1c2.
  • Message: a standard Seal SessionKey personal message (“Accessing keys of
    package 0x682d… for 10 mins from … session key …”), produced by
    @mysten/seal 1.3.8 getPersonalMessage().
  • signPersonalMessage returned bytes exactly matching the request
    (bytesMatch: true) and a 975-byte flag-5 (zkLogin) signature.
  • Parsed envelope: ephemeral userSignature flag 2 (Secp256r1, 98 bytes),
    maxEpoch = 8, ephemeral public key
    AxprBmSsHCxehsu1hya6JhKG8kf+ADiGBP+lMSse/K+x.
  • The inner ephemeral signature verifies over the exact message bytes via
    Mysten’s own Secp256r1PublicKey.verifyPersonalMessage (rawBytes: true;
    the base64-string variant is false - the wallet signed the right bytes).
  • The complete envelope is refused by:
    • @mysten/sui verifyPersonalMessageSignature via the fullnode
      signature-verification service: “Signature is not valid for the provided
      message” (a clean invalid, not an environmental error);
    • Seal key servers (which delegate zkLogin PM verification to their
      fullnode): “User signature on the session key is invalid”.

Why every verifier is RIGHT to refuse

A proof capped at epoch 8 expired roughly 1,221 epochs ago. The verifiers are
behaving correctly; the envelope is invalid as constructed. The bug is in the
my.slush.app popup’s personal-message flow: it attaches stale or placeholder
proof material (maxEpoch 8) around a fresh, correct ephemeral signature,
instead of the current session proof it uses for transactions.

Impact

Any dapp that verifies Slush-web personal-message signatures fails for every
zkLogin account: Seal SessionKey certificates (this product’s retrieval
path), sign-in-with-Sui flows, any offchain signature check. Transactions are
unaffected.

Instrument notes (for reproduction)

The probe ran in-page: parse flag-5 signature body with
parseZkLoginSignature, extract userSignature, verify the ephemeral layer
with the SDK’s Ed25519PublicKey/Secp256r1PublicKey
.verifyPersonalMessage against the requested bytes and a base64-string
variant. The probe was validated on synthetic signatures in both directions
before use. Known adjacent issue class: MystenLabs/sui#18949.

Thanks for the detailed report. This does look suspicious, especially the stale maxEpoch on signPersonalMessage.

I’ll check with the Slush/Sui team and follow up here once I have confirmation.