MessageSigner Best Pratices
Recommendations
Default to Auto Methods
Unless there is a specific reason to use non-auto methods (such as synchronous signing in a guaranteed backend context), always prefer signMessageAuto(), tweakAndSignMessageAuto(), and signMLDSAMessageAuto(). They handle environment detection automatically and future-proof your code.
Always Verify Signatures
Never trust signed data without verification, especially data received over a network. Use verifySignature(), tweakAndVerifySignature(), or verifyMLDSASignature() before acting on signed messages.
Pass Original Messages to Verification
All verification methods hash the message internally with SHA-256. Pass the original message string or bytes, not a pre-computed hash. The only exception is if both signer and verifier explicitly agree on a pre-hashed protocol.
Use Untweaked Public Keys for Tweaked Verification
Pass the untweaked public key to tweakAndVerifySignature(). The method applies the Taproot tweak internally. Passing an already-tweaked key produces a double-tweaked key and verification will fail.
Provide Network for Tweaked Backend Signing
When calling tweakAndSignMessageAuto() or tweakAndSignMessage() with a local keypair, the network parameter is required for computing the correct Taproot tweak. Omitting it throws an error.
Reconstruct Keypairs for Remote ML-DSA Verification
Use QuantumBIP32Factory.fromPublicKey() for remote ML-DSA verification. When only a public key is available, reconstruct a public-key-only keypair before calling verifyMLDSASignature(). This is required because the verification algorithm needs security level metadata embedded in the keypair object.
Structure Signed Messages with Context
Include action type, timestamps, and nonces in signed messages to prevent replay attacks. Structured payloads ensure signatures cannot be reused in unintended contexts.
const message = JSON.stringify({
action: 'transfer',
amount: '50000',
timestamp: Date.now(),
nonce: toHex(crypto.getRandomValues(new Uint8Array(16))),
});
const signed = await MessageSigner.signMessageAuto(message, keypair);Never Expose Private Keys
The OP_WALLET architecture exists specifically to keep private keys inside the browser extension. In backend code, load keys from secure storage (environment variables, HSM, encrypted vaults) and avoid logging them.
Handle OP_WALLET Absence Gracefully
In browser code, check isOPWalletAvailable() before signing to provide a user-friendly error message instead of an unhandled exception.
Match Security Levels for ML-DSA
When verifying ML-DSA signatures, the security level used for QuantumBIP32Factory.fromPublicKey() must match the level used during signing. Mismatched levels cause verification failure.