Identity Proofs (Cardano)
Here we explain how Fairway encodes KYC/AML eligibility on Cardano using a single UTXO with a Merkle root, Fairway signature, a verification timestamp, and a reference to a Midnight proof UTXO.
Background
Instead of minting identity tokens per user (e.g., CIP-113 or NFTs), Fairway maintains a canonical Compliance UTXO that contains:
merkle_root
→ commitment to all compliant addresses + claims.sanctions_epoch
→ global counter for sanctions/risk updates.fairway_sig
→ Fairway’s signature binding root + epoch + Midnight ref.midnight_ref
→ pointer to the ZK-proof UTXO on Midnight (audit trail).
Each Merkle leaf represents a user and includes:
address
→ wallet identifier.claims
→ e.g.,KYC2
,EU_RESIDENT
.kyc_verified_at
→ timestamp of last KYC check.
Eligibility is checked by proving Merkle membership and verifying both:
Fairway signature over the Compliance UTXO state.
Freshness conditions (timestamp vs. sanctions epoch).
Data Model
UTXO Datum
{
"merkle_root": "0xabc123...",
"sanctions_epoch": 452,
"midnight_ref": "0xdeadbeef#0",
"fairway_sig": "sig(fairway_key, hash(merkle_root || sanctions_epoch || midnight_ref))"
}
Merkle Leaf
{
"address": "addr1...",
"claims": ["KYC2", "EU_RESIDENT"],
"kyc_verified_at": "2025-03-15T10:00:00Z"
}
Flow
sequenceDiagram
participant Vault as Decentralized Storage (PII)
participant Agent as Fairway Cloud Agent
participant Midnight as Midnight Proof UTXO
participant Cardano as Compliance UTXO
participant User as Wallet
participant dApp as Protocol Validator
Vault->>Agent: Scoped KYC read
Agent->>Midnight: Generate & post ZK proof (proof UTXO)
Agent->>Cardano: Update Merkle root + sanctions_epoch + Fairway sig
User->>dApp: Submit Merkle proof {address, claims, kyc_verified_at}
dApp->>Cardano: Verify Merkle membership, Fairway sig, sanctions_epoch
dApp->>User: Access granted if valid
Validator Pseudocode (Aiken)
validator checkEligibility {
fn is_valid(ctx: ScriptContext, proof: MerkleProof, leaf: Leaf, max_age: Int) -> Bool {
let utxo = get_utxo(ctx, FAIRWAY_UTXO_REF)
// 1. Verify Merkle proof
let ok_proof = verify_merkle(proof, utxo.datum.merkle_root)
// 2. Verify Fairway signature
let payload = hash(utxo.datum.merkle_root || utxo.datum.sanctions_epoch || utxo.datum.midnight_ref)
let ok_sig = verify_signature(FAIRWAY_PUBKEY, payload, utxo.datum.fairway_sig)
// 3. Check sanctions epoch freshness
let ok_epoch = utxo.datum.sanctions_epoch == current_sanctions_epoch()
// 4. Check KYC recency (dApp-defined)
let age = ctx.tx.now - leaf.kyc_verified_at
let ok_kyc = age <= max_age
ok_proof && ok_sig && ok_epoch && ok_kyc
}
}
max_age
is defined by the protocol/institution (e.g., 180 days).The validator never opens raw PII.
midnight_ref
links to a verifiable ZK proof on Midnight for audits.
Benefits
Compact → one UTXO anchors all compliant users.
Flexible → institutions define their own
max_age
for KYC proofs.Auditable →
midnight_ref
links back to ZK proof.Compliant → sanctions freshness via epoch, KYC recency via timestamp.
Composable → eligibility checks slot directly into LP/staking validators.
Next Steps
Learn Revocation Epochs & Freshness → how timestamps + epochs combine.
See Build on Cardano → full Aiken integration guide.
Explore Trust Registries → issuer key governance (roadmap).
Last updated
Was this helpful?