Back to Research
7 min read0%
Post-QuantumJune 9, 2026

Post-quantum EVM multisig on a stock Safe

By Matteo Vicari

AI agent? Read this article as clean Markdown to save tokens.
Post-quantum EVM multisig on a stock Safe

Rivalabs builds the infrastructure for post-quantum signatures onchain. We start with the Ephemeral Keys protocol and its reference implementation, NiceTry, and build out the primitives for post-quantum signing and digital asset custody.

In this article we show a small but useful integration: using NiceTry ERC-4337 accounts as owners of an unmodified Safe multisig. The result is a 2-of-3 Safe operated by FORS+C-authenticated accounts, without teaching the Safe anything about FORS+C.

Post-quantum wallet infrastructure on EVM chains is increasingly moving through account abstraction. The reason is simple: account abstraction lets the account decide how authorization works, without requiring the base protocol or every application contract to adopt a new signature scheme.

Safe multisigs are an interesting test case for this model. They are already widely used for custody, governance, treasury management, and operational approvals. They also have a strict signature interface built around owner addresses, thresholds, and transaction hashes.

The question is therefore:

Can a post-quantum account operate a stock Safe without modifying the Safe?

For our infra, the answer is yes.The important point is that the Safe never verifies a post-quantum signature. FORS+C runs one layer above the Safe, inside each NiceTry account's ERC-4337 validation path. The Safe only sees ordinary owner addresses and ordinary pre-validated approvals.

What the integration does

The integration uses NiceTry accounts as Safe owners.

Each NiceTry account authenticates a UserOperation with FORS+C, if the UserOperation is valid, the account can call the Safe. From the Safe's perspective, this is just a contract owner calling approveHash or execTransaction.

The Safe authorization path is unchanged:

FORS+C signature
  -> NiceTry validateUserOp
  -> NiceTry account callData
  -> Safe approveHash / execTransaction
  -> Safe threshold check

This keeps the post-quantum logic in the account layer and the M-of-N logic in the Safe layer. Safe remains a stock Safe v1.4.1 deployment. NiceTry does not add a verifier to the Safe, does not modify execTransaction, and does not put FORS+C bytes inside the Safe signature blob.

How Safe sees the transaction

A Safe transaction is defined by the usual tuple:

(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, nonce)

The Safe hashes that tuple using its EIP-712 construction. The resulting safeTxHash is what owners approve. Because the Safe nonce is part of the hash, an approval is bound to one transaction at one position in the Safe's sequence.

When execTransaction is called, Safe runs checkNSignatures. For each 65-byte signature record, the v byte decides how the record is interpreted:

v valueSafe behavior
27/28, or >30ECDSA recovery with ecrecover
0EIP-1271 contract signature via isValidSignature
1Pre-validated approval via msg.sender or approvedHashes

NiceTry uses v = 1.

There are two ways this can pass:

owner == msg.sender

or:

approvedHashes[owner][safeTxHash] != 0

This is the whole integration surface. The Safe counts owner approvals as usual and the only difference is how those approvals are produced.

The layered model

Layered call flow: a FORS+C-signed UserOp is validated at the EntryPoint, the NiceTry account then runs its callData, and the stock Safe checks v=1 approvals against its threshold.

FORS+C is verified at the account layer. The Safe only checks v=1 owner approvals against its threshold.

The model has three layers:

LayerComponentRole
1EntryPoint / NiceTry validationVerifies FORS+C and rotates the key
2NiceTry account executionCalls the Safe
3SafeCounts owner approvals and enforces M-of-N

The Safe remains therefore only a threshold counter over approvals​.

The owner account must not expose a second route that can call the Safe without passing the FORS+C check. If an account has an unrestricted execution function, an unsafe upgrade path, or a module that bypasses validation, the post-quantum argument fails.

The owner set also matters: no threshold-satisfying subset can be entirely quantum-breakable. In a hypothetical 2-of-3 over {Alice (PQ), Bob (PQ), Charlie (classical)}, one classical owner is not enough to break custody, because every valid pair still needs Alice or Bob.

The Sepolia deployment

The demo uses:

RoleAddress
Alice0xE572f04357958B334AbC2BF2C9E31492eF95FE14
Bob0x58a6de8bFb517d3ABebacA88cFC6081A9255Dfbf
Charlie0xf7FeE1BfBb5b22eC67362c0d26D7Aed99A309992
Safe0x0C5222D605B167a192E1bbe2119cEEbb02DE9977
EntryPoint v0.7.00x0000000071727De22E5E9d8BAf0edAc6f37da032

FORS+C is a few-time signature scheme.Reusing a key degrades security, so the NiceTry account consumes a key and rotates to the next one during validation. The OwnerRotated event marks that rotation.

The lifecycle has three transactions.

1. Enroll

tx 0x7c688d7c...f5c10 uses UserOperationEvent.actualGasUsed = 469,897.

Alice's UserOperation deploys the Safe through the canonical SafeProxyFactory, with owners {Alice, Bob, Charlie} and threshold 2. The deployment uses the standard SafeToL2Setup migration. During validation, Alice rotates:

0x79dFA6... -> 0x1731861D...

The Safe sees three owner addresses. It does not receive any FORS+C signature material.

2. Vote

tx 0xaf5297fe...0c1e8d uses UserOperationEvent.actualGasUsed = 201,437.

Alice calls:

Safe.approveHash(0x5024ae...)

The Safe records:

approvedHashes[Alice][0x5024ae...] = 1

and emits ApproveHash. Alice rotates again:

0x1731861D... -> 0x920A6960...

This is the first approval. It is an onchain commitment to a Safe transaction hash, not an inline signature.

3. Execute

tx 0x6350196b...95c494 uses UserOperationEvent.actualGasUsed = 227,837.

Bob calls:

Safe.execTransaction(to=Charlie, value=0.05 ETH, ...)

The Safe signature blob contains two v=1 records:

r = Bob (0x58a6...), s = 0, v = 1
r = Alice (0xE572...), s = 0, v = 1

Bob passes because Bob is the executor, so owner == msg.sender. Alice passes because Alice approved the same hash in the previous transaction. Together they meet the threshold of 2.

The Safe sends 0.05 ETH to Charlie and emits:

ExecutionSuccess(0x5024ae...)

Bob rotates:

0x4f5017... -> 0x244A1a...

No ECDSA recovery is used for either owner and no FORS+C bytes are included in the Safe calldata.

The vote and execution are linked by the Safe transaction hash:

0x5024aebff8b8417da15ad3f369680c85fac0d0eae68d616902019479882809ea

This hash is the value Alice approved in tx 2 and the value the Safe executed in tx 3.

It reproduces from the Safe v1.4.1 EIP-712 construction:

domainSeparator = keccak256(abi.encode(
    keccak256("EIP712Domain(uint256 chainId,address verifyingContract)"),
    11155111,
    Safe
))

structHash = keccak256(abi.encode(
    SAFE_TX_TYPEHASH,
    Charlie,
    0.05e18,
    keccak256(0x),
    0,
    0,
    0,
    0,
    0x0,
    0x0,
    0
))

safeTxHash = keccak256(0x19 || 0x01 || domainSeparator || structHash)
           = 0x5024aebff8b8417da15ad3f369680c85fac0d0eae68d616902019479882809ea

This is the same coordination model as an ordinary Safe multisig: the owners agree on the exact Safe transaction tuple and approve its hash. Only the approval medium changes, while the Safe transaction model stays the same and sees no difference.

Conclusions

NiceTry does not turn Safe into a post-quantum multisig, it keeps Safe exactly where Safe is strong: owner sets, thresholds, transaction hashes, and execution.

The post-quantum part lives in the owners: each NiceTry account authenticates a UserOperation with FORS+C, rotates its key, and then creates or uses a normal Safe v=1 approval. The Safe counts those approvals as it always has, without questioning their source's authentication method.

The resulting model is:

post-quantum account authorization
  -> ordinary Safe approval
  -> ordinary Safe threshold execution

This design composes post-quantum accounts with existing Safe custody, without modifying the Safe or asking it to understand post-quantum cryptography.

The same mechanism also gives existing Safe deployments a path to post-quantum custody, swapping owners for NiceTry accounts one at a time so a live Safe migrates without redeployment or threshold changes.