viem is a typed, tree-shakeable library for Ethereum clients, accounts, contracts and ABI coding, and the foundation of wagmi. Local accounts from privateKeyToAccount() sign with @noble/curves (viem 2.57.7 depends on 1.9.1 of it), so their signatures are deterministic RFC 6979 and low-S — identical, for the same key and message, to what ethers produces.
account.signMessage() applies the EIP-191 prefix and keccak-256 and returns 65 bytes ending in v = 27 or 28; sign() with a hash parameter signs a 32-byte digest unchanged, which is how EIP-712 digests and transaction hashes are signed. The check confirmed both against independent implementations.
Verification is more permissive than in ethers. recoverMessageAddress() and the verifyMessage utility accepted the mirrored high-S signature — mathematically valid, but a form ethers rejects with non-canonical s. If your system deduplicates or stores signatures, normalize s yourself, or a second, different-looking signature for the same message will be accepted. In the other direction, the 64-byte EIP-2098 compact form failed with invalid signature length in both functions; expand it with parseCompactSignature and compactSignatureToSignature first.
The standalone verifyMessage utility only checks externally owned accounts. Smart-contract wallets (Safe, account-abstraction wallets, passkey wallets) sign through ERC-1271 or ERC-6492, and only the verifyMessage action on a public client asks the contract. Inspect typed data with the EIP-712 inspector, sign-in messages with the SIWE verifier, and raw signatures with the Ethereum signature verifier.
The sample signs a sign-in message and checks it two ways: by verification against the address and by recovering the signer.