Warning
Code in this repository is not audited and may contain serious security holes. Use at your own risk.
Safe + Corona
This repository brings Corona post-quantum threshold signatures to the Safe smart account on the Lux C-Chain EVM. Corona is a Ring-LWE (RLWE) two-round t-of-n threshold signature (eprint.iacr.org/2024/1113 (lattice / Learning-With-Errors)).
Unlike safe-frost — which ships a self-contained secp256k1 Schnorr verifier in
Solidity — Corona verification is performed by the on-chain coronaThreshold
precompile at 0x012206 (part of the Lux LP-4200 unified PQCrypto block). The
Solidity in this repository is a thin, decomplected adapter: it frames the
precompile's exact calldata, staticcalls it under a strict success-word check,
and binds the verified key to a Safe owner.
safe-corona supports the same three use cases as safe-frost:
- as a Safe signer (an EIP-1271 owner — CoronaSigner)
- as a Safe co-signer (a transaction guard — SafeCoronaCoSigner)
- as an ERC-4337 account (CoronaAccount)
Precompile
| Field | Value |
|---|---|
| Address | 0x012206 |
| Verify ABI (raw wire format) | threshold:uint32(4) ‖ totalParties:uint32(4) ‖ msgHash:bytes32(32) ‖ signature |
| Domain-separation context | lux-evm-precompile-corona-v1 |
| Return value | 32-byte word: bytes32(1) iff valid, bytes32(0) otherwise (never reverts on a cryptographic false) |
| Reference implementation | github.com/luxfi/precompile/corona |
The "message" for every Safe use is the 32-byte Safe-transaction hash (or the
ERC-4337 userOpHash), which is EIP-712-bound to the Safe address, chain id and
nonce — so a signature authorises exactly one Safe / chain / nonce. The
precompile additionally binds the domain-separation context above, so a Corona
signature cannot be replayed against any other precompile in the PQCrypto block.
Important
The precompile reads its calldata as a raw packed layout, not an ABI-encoded function call — there is no 4-byte selector. Corona.sol frames the bytes exactly as the Go
Run()parses them. Sending an ABI-encoded call (with a selector) would misalign every field.
Contracts
contracts/Corona.sol— the verifier library. One function,verify(...), that framesa(threshold, totalParties)pair (advisory metadata; soundness comes from the embedded group key), the public key / signature and the 32-byte hash into the precompile's wire format and strict-staticcalls0x012206. Holds no state and enforces no policy (decomplected: dispatch only).contracts/SafeCoronaSigner.sol— an EIP-1271 validator that makes a single Corona key a first-class Safe owner. Commit the key once viaaddOwnerWithThreshold; the Safe'scheckNSignaturesvalidates this owner's slice through thev = 0contract-signature path. M-of-N thresholds mixing ECDSA, secp256r1 and Corona owners work natively.contracts/SafeCoronaCoSigner.sol— a Safe transaction guard requiring everyexecTransactionto additionally carry a valid Corona co-signature.contracts/CoronaAccount.sol— an ERC-4337 account authorised by a committed Corona key.
Key binding
Corona signatures are a multi-kilobyte serialized lattice signature that EMBEDS its own group key (matrix A, rounded public key b̃). An owner / account therefore commits
a keccak256 of the stable group-key region of the serialized signature (the trailing 64 of 80 polynomials) at construction (immutable, so the validators are stateless view
functions and side-step the Safe's state-changing-validator guard). At verify
time the supplied key is re-hashed against the commitment, closing the "attacker
supplies an arbitrary key" attack via second-preimage resistance.
Signature payload
The contract-signature / userOp signature payload is the raw serialized Corona signature bytes (the group key is embedded in it; there is no separate public-key argument).
Testing
forge test
The tests prove verify() against a real known-answer-test (KAT) vector —
163840-byte 2-of-3 serialized threshold signature produced by a genuine Corona ceremony. The KAT was generated by
lux/precompile/cmd/safekatdump and independently proven to verify against
the real Go precompile by lux/precompile/cmd/safekatverify, so the bytes the
Solidity frames are known to be accepted on-chain.
Because the precompile is a luxd-native Go precompile (absent from a stock
Foundry EVM), the tests install a mock at 0x012206 via vm.etch whose sole job
is to assert calldata-framing conformance: it returns the success word iff
the incoming calldata is byte-for-byte identical to the proven KAT input. Any
drift in the wire format (field order, length-prefix widths, an accidental ABI
selector) changes the calldata and the test fails. The headline test is
Corona.t.sol::test_CalldataFramingMatchesPrecompileABI.
The fixture lives at tests/kat.json.
Integration with the Lux ecosystem
This package is part of the Lux blockchain ecosystem.
- GitHub: https://github.com/luxfi
- Docs: https://docs.lux.network
- Sibling PQ Safe wiring:
luxfi/safe/contracts/pq(ML-DSA / SLH-DSA / Pulsar) - Verifier precompile family:
luxfi/precompile(LP-4200 PQCrypto block)