0xjuno’s post on moving existing accounts to hybrid ECDSA + Falcon signatures got me looking at the other side of that migration. The account swaps its class, keeps its address, and then it keeps calling contracts that were written for accounts that sign with r and s. The roadmap page says existing accounts don’t change automatically and owners choose when, so there is no switch day. It happens one account at a time and your contract gets no warning.
Nothing here is urgent, the same page still lists wallet support, key management, audits and adoption as open work. The numbers below come from the starknet-pq-account repo, which is testnet only and unaudited, so read them as a rough size.
A signature is not two felts
In that hybrid account a signature is [ecdsa_r, ecdsa_s, falcon x 60], so 62 felts, and is_valid_signature returns VALID only when both parts verify. Anything shaped around r and s can’t carry that: an entry point taking r: felt252, s: felt252, a struct that stores the pair, an assert(signature.len() == 2).
This isn’t only a post-quantum thing either. Argent accounts have allowed Secp256r1, Eip191 and WebAuthn signers since v0.4.0.
OpenZeppelin’s permit is the shape I’d copy. It takes signature: Span<felt252> and hands it to the owner’s is_valid_signature without looking inside. StarkWare’s privacy pool does the same with tx_info.signature.
Reading the key and checking it yourself
This is the one I’d look for first, because the obvious patch makes it worse.
Some contracts don’t ask the account at all. They read its public key and run check_ecdsa_signature themselves. After migration that read fails, because the migrated class has get_ecdsa_public_key and no get_public_key. So the quick fix is to point the contract at the new getter and keep checking ECDSA.
What this basically means is that the account now rejects an ECDSA-only signature in __validate__, and your contract accepts one for a permit or an order. The idea of the hybrid is that the ECDSA key alone can’t move anything, and that only holds if every contract asks the account. A contract that checks the key on its own keeps its own rule, and the migration never reaches it.
Who pays for the check
From the repo’s README, a transfer costs about 1.85M L2 gas from a plain OpenZeppelin account, 15.8M from a hybrid account with the Poseidon Falcon verifier and 53.9M with the standard SHAKE-256 one.
In a normal transaction the user pays that. But when your contract calls is_valid_signature on someone else’s signature, the gas lands on whoever sent the transaction. A relayer settling a batch of signed orders, or a multisig checking each signer, pays it once per signature. The chain info page lists 10^8 L2 gas as the limit for validate, so by those numbers two SHAKE-256 checks inside one __validate__ are already over it.
What a migrated account stops supporting
The README also says SRC-9 outside execution, so paymasters and session keys, isn’t available after migration yet, and the migrated account’s supports_interface only answers true for SRC-5 and SRC-6. A dapp that sends users through execute_from_outside needs a normal transaction as a fallback for those accounts. And if you allowlist account class hashes anywhere, a migrated account keeps its address but not its class hash.
What would break first in your contracts if a user showed up tomorrow with a 62-felt signature?