How it works
- Off-chain compliance screening runs per party via LSEG World-Check (entity) and CipherOwl (wallet)
- On a passing result, an EIP-712
Attestationmessage is signed by M of the N configured attester keys - The signed bundle is submitted on-chain:
attest(attestation, signatures). The submitter is not trusted - the signatures authorize, so no single key (and no submitter) can forge a Pass - At the last deposit (or a later
execute()call), the KeystoneSettlement contract callsareAllPartiesCleared(settlementId, partyHashes)with the hashes bound at registration - If every bound hash has a passing attestation, the settlement executes; otherwise it stays registered until attestation lands
Functions
attest()
Submit an M-of-N signed compliance attestation for a party in a settlement.- Pass only. Only
Passattestations exist on-chain - a failed or flagged screening is never written to the registry; the gate simply never clears for that party. Any other status revertsInvalidComplianceStatus. - Deadline. A bundle submitted after
deadlinerevertsAttestationExpired, so a leaked old signature cannot be replayed indefinitely. - M-of-N signatures. Each signature must recover to a configured attester, signers must be in strictly ascending address order (rejects duplicates), and at least the threshold M must be present - otherwise
SignersNotSortedOrDuplicate,UnknownAttester, orThresholdNotMet. - Sequence. Re-attesting the same party increments a per-record
sequencecounter, so downstream systems can distinguish the first attestation from a re-screen.
isCleared()
Check if a specific party has a passing attestation.areAllPartiesCleared()
Check if every supplied party hash has a passing attestation for the settlement. This is the function the KeystoneSettlement contract calls as its execution gate, passing the party hashes bound at registration (the gate can never pass vacuously - registration rejects empty hash lists).Party hash computation
Party hashes are computed askeccak256(abi.encode(partyId_bytes32, walletAddress)). The same inputs MUST be used at registration (KeystoneSettlement partyHashes) and at attestation, or the gate never clears:
This allows separate attestations for entity-level and wallet-level screening while maintaining a consistent hashing scheme.
Status mapping
Off-chain, KeyStone tracks three screening outcomes per party:PASS, FLAGGED, and FAIL. On-chain, only one of them ever becomes a record:
Deposits are still accepted while the gate is closed - only execution is blocked. The
ComplianceStatus enum retains Flagged (2) and Fail (3) values for ABI compatibility, but attest rejects them.
Immutable M-of-N threshold
The attester set and the threshold are fixed at deployment and immutable: the contract has no function to add or remove an attester or to change the threshold. Changing the set means deploying a fresh registry, deploying KeystoneSettlement against it, and repointing the backend - a visible, coordinated, multi-step operation that cannot happen in a single transaction. There is no on-chain path to forge a Pass. The pause authority (PAUSER_ROLE, with DEFAULT_ADMIN_ROLE existing only to rotate it) is halt-only: it can freeze new attestations in an emergency but cannot forge, alter, or delete one.
The current testnet deployments run a 2-of-3 attester set operated by KeyStone as an interim arrangement; the design goal is independent attester operators, at which point no single organization can clear the gate alone.
Events
What KeyStone stores
KeyStone never stores raw KYC/AML data. The compliance flow stores:
The compliance provider (LSEG, CipherOwl) remains the source of truth for the actual screening data. KeyStone only records the outcome.