Zero-knowledge compliance verification
This tutorial walks you through DPO2U's end-to-end compliance verification pipeline — from generating an LGPD policy kit to recording a zero-knowledge proof on the the host chain. You will understand how each smart contract works, how privacy is preserved at every step, and how to verify an Attestation programmatically.
Prerequisites:
- Familiarity with blockchain concepts (transactions, smart contracts, hashes)
- Basic understanding of privacy regulations (LGPD or GDPR)
- Node.js v18+ installed (for the verification examples)
- [Compact toolchain](https://docs.dpo2u.com installed (optional, for compiling contracts)
You don't need to know Compact or zk-SNARKs to follow this tutorial. Each concept is introduced when it first appears.
Why zero-knowledge proofs for compliance
Traditional compliance relies on trust: a consultant reviews documents, produces a PDF, and everyone agrees to treat it as "verification." The PDF can be edited. The review date can be falsified. Nobody checks because checking is expensive.
Public blockchains solve immutability but create a new problem: all state is transparent. How do you prove compliance with privacy laws (LGPD/GDPR) without exposing the very data those laws protect? This is the privacy paradox that zero-knowledge proofs resolve.
A zk-SNARK lets a Prover convince a Verifier that a statement is true without revealing any information beyond the truth of the statement itself. Think of it like the Ali Baba cave analogy: Alice proves she knows the secret password by consistently exiting from whichever side Bob requests — after 20 rounds, the probability of luck is 1 in 1,048,576, yet Bob never hears the password.
For a deeper exploration of this paradigm shift — from "trust us" to "verify mathematically" — read Trustless trust: how on-chain attestations replace reputation management.
The key insight: compliance becomes a verifiable property of a system, not a document stored in a shared drive. The proof is on-chain, the documents are on IPFS, and verification is a single API call.
How zk-SNARKs work: the three phases
Every zk-SNARK goes through three phases:
| Phase | When it happens | What it produces |
|---|---|---|
| Setup | Once, at contract deploy | Proving key (pk) and verifying key (vk) |
| Proof generation | Each time an Attestation is issued | A compact proof (a few KB) that the circuit was executed correctly |
| Verification | Any time, by anyone | A boolean: valid or invalid (milliseconds, on-chain) |
Proving Key (pk) ──┐
Witness (private) ──┤──→ [ZK-SNARK Algorithm] ──→ Proof (π)
Circuit ───────────┘
Verifying Key (vk) ──┐
Proof (π) ────────────┤──→ [Verifier] ──→ VALID / INVALID
Public Output ────────┘
The witness is the private data — in DPO2U's case, the full compliance attestation JSON. The circuit is the smart contracts logic compiled into arithmetic constraints. The proof attests that the circuit was executed correctly with a valid witness, without revealing what the witness contains.
The the host chain uses zk-SNARKs because they produce smaller proofs and faster on-chain verification. ZK-STARKs offer quantum resistance but generate larger proofs, increasing verification costs. For compliance use cases where verification efficiency is critical, zk-SNARKs are the better fit.
The compliance verification flow
The entire pipeline — from a client's request to an on-chain Attestation — passes through five stages:
Here is what happens at each stage:
-
Schema generation — A client AI calls
generate_lgpd_kitvia the MCP Server. The Expert Agent produces apolicy.jsonconforming to thedpo2u/lgpd/v1schema, plus human-readable reports (privacy notice, DPIA). -
Immutability via IPFS — The reports and schema are uploaded to IPFS via the Lighthouse SDK. Each artifact receives a CID (content-addressed hash). Modifying a single byte changes the CID, invalidating all downstream references.
-
Zero-trust edge analysis — The Auditor Agent retrieves the schema and CIDs, validates all mandatory fields, and scores compliance on a 0–100 scale. The validation runs in a sandbox with no network access beyond IPFS retrieval and the the host chain RPC endpoint.
-
On-chain settlement — The Auditor's score and CID are wrapped in a zk-SNARK proof by the local Proof Server (
proof-server:7.0.0). The proof is submitted toComplianceRegistry.compacton the the host chain. -
Verification — Any party calls
check_compliance_status(company_id)and receives a verifiable result. No PDF. No shared drive. No trust required.
For the full protocol lifecycle with validation rules and failure behaviors, read From PDF to proof.
Understanding the ComplianceRegistry contract
ComplianceRegistry.compact is the central registry that holds all generated Attestations. Here is the contract using real Compact syntax:
// compliance-attestation.compact
pragma language_version 0.20;
// ── LEDGER STATE (public on-chain) ──────────────────────────────
// These values are visible to anyone on the blockchain.
// SHA-256 hash of the most recent compliance attestation.
// The hash is public (verifiable), but the attestation content is private.
export ledger complianceHash: Opaque<"string">;
// Public key of the contract owner (DPO or company).
export ledger owner: PublicKey;
// Timestamp of the last issued attestation.
export ledger lastAttestationTimestamp: Uint64;
// ── CIRCUITS (contract functions) ───────────────────────────────
// Parameters are PRIVATE by default — they never appear on the blockchain.
// Initialize the contract, setting the owner.
export circuit initialize(ownerKey: PublicKey): [] {
// disclose() moves a value from the private domain to the public ledger.
owner = disclose(ownerKey);
}
// Issue a new compliance attestation.
// attestationData remains PRIVATE; only its hash is stored publicly.
export circuit issueAttestation(
attestationData: Opaque<"string">, // Full JSON (PRIVATE)
attestationHash: Opaque<"string">, // SHA-256 hash (becomes PUBLIC)
timestamp: Uint64, // Issuance time (becomes PUBLIC)
signerKey: PrivateKey // Proves caller is the owner
): [] {
// Verify the caller is the contract owner.
// publicKey() derives the public key from the private key.
assert(checkSignature(publicKey(signerKey), owner));
// Store the hash publicly. The original attestationData
// NEVER touches the public ledger.
complianceHash = disclose(attestationHash);
// Record the issuance timestamp.
lastAttestationTimestamp = disclose(timestamp);
}
Key points to understand:
-
export ledger— Declares public on-chain state. Anyone can read these values. This is the only data that lives on the blockchain. -
export circuit— Defines a circuit function. The the contract compiler turns each circuit into a zk-SNARK with a deterministic size. Circuit parameters are private by default. -
disclose()— The bridge between private and public. Assigning a private value to a ledger variable withoutdisclose()causes a compilation error. This makes data leaks impossible by design. -
Opaque<"string">— Represents an external value (a JavaScript string) that Compact can store and transmit without interpreting its internal structure. Useful for hashes, identifiers, and CIDs. -
assert(checkSignature(...))— Authorization check that runs inside the zk-SNARK circuit. The verifier confirms the check passed without seeing the signer's private key.
Trying to write complianceHash = attestationHash without disclose() produces a compile-time error. This is not a convention — it is enforced by the compiler. Private data cannot leak to the ledger accidentally.
| On-chain (the ledger) | Off-chain (IPFS) |
|---|---|
| Hashed company ID | Privacy notice (full text) |
| IPFS CID (pointer) | DPIA document |
| Compliance score | policy.json with all fields |
| Timestamp | Company profile data |
| Agent DID | DPO contact information |
Compiling the contract
The the contract compiler transforms the contract into zk-SNARK circuits and generates the cryptographic keys:
compact compile contracts/compliance-attestation.compact contracts/managed/compliance-attestation
This produces the following artifacts:
| Directory | Contents | Purpose |
|---|---|---|
contract/ | index.js, index.d.ts | TypeScript API to call the contract's circuits from your DApp |
keys/ | *.pk, *.vk | Proving key (generates proofs) and verifying key (verifies proofs) |
zkir/ | .zkir files | Zero-Knowledge Intermediate Representation — the bridge between Compact and the cryptographic backend |
How Compact differs from Solidity
If you come from an Ethereum / Solidity background, Compact requires a mental shift. It is not a Solidity variant — it is a zero-knowledge circuit language.
| Aspect | Solidity | Compact |
|---|---|---|
| Execution model | EVM bytecode | zk-SNARK circuits |
| Privacy | Public by default | Private by default |
| Loops | Arbitrary (for, while) | No arbitrary loops (circuit size must be deterministic) |
| Memory | Dynamic allocation | Fixed data structures |
| Strings | Native support | Opaque<"string"> (field elements) |
| Disclosure | N/A | disclose() for selective revelation |
| State declaration | mapping, uint, address | export ledger with typed fields |
The critical difference is the disclose() primitive. In Compact, all data is private unless you explicitly choose to reveal it. This is the opposite of Solidity, where all state is public unless you engineer privacy on top.
// Compact: selective disclosure
export circuit verifyCompliance(company_id: Opaque<"string">): [] {
// The circuit reads the ledger state and the private input,
// but only discloses what the developer explicitly chooses.
// Here we reveal the score but NOT the agent DID or CID.
disclose(complianceHash);
disclose(lastAttestationTimestamp);
}
For a comparison of ZK-circuit smart-contract languages vs general-purpose ones, see the architecture docs.
The attestation data model
Every Attestation starts as a compliance assessment generated by the DPO2U Agent. This JSON document is private — it is never stored directly on the blockchain:
{
"version": "1.0",
"schemaType": "DPO2U_COMPLIANCE_ATTESTATION",
"issuer": {
"did": "did:dpo2u:agent:auditor-001",
"name": "DPO2U Auditor Agent",
"version": "0.9.1"
},
"subject": {
"companyId": "cnpj:12.345.678/0001-90",
"companyName": "Acme Corp Ltda",
"assessmentDate": "2026-03-04T12:00:00Z"
},
"compliance": {
"lgpd": {
"status": "compliant",
"score": 87,
"criticalFindings": 0,
"lastAssessment": "2026-03-04T10:00:00Z"
},
"gdpr": {
"status": "compliant",
"score": 82,
"criticalFindings": 0,
"lastAssessment": "2026-03-04T10:00:00Z"
}
},
"evidenceHashes": {
"dpia": "sha256:a1b2c3d4...",
"dataInventory": "sha256:e5f6g7h8...",
"privacyPolicy": "sha256:i9j0k1l2..."
}
}
The evidenceHashes field is key: instead of including full documents, you include only their SHA-256 hashes. An auditor can verify document integrity (if they are provided separately) without the attestation carrying sensitive data.
This attestation conforms to the dpo2u/lgpd/v1 schema. Four fields are mandatory and block the pipeline if missing:
| Field | LGPD article | Purpose |
|---|---|---|
retention_days | Art. 15 | How long data is kept |
consent_mechanism | Art. 7 | How consent is obtained |
dpo_contact | Art. 41 | Data Protection Officer contact |
last_review | Art. 6, V | When the policy was last reviewed |
The data boundary is strict:
Raw company data never touches the blockchain. Only the hash of the attestation, the IPFS CID (a pointer), and the timestamp are recorded on-chain.
Issuing an attestation
Here is the complete flow for issuing an Attestation programmatically. The script loads a compliance assessment, hashes it, and submits it to the smart contracts:
import { createHash } from 'crypto';
import { readFileSync } from 'fs';
// TypeScript API generated by `compact compile`
import { ComplianceAttestationContract } from
'../contracts/managed/compliance-attestation/contract/index.js';
// Generate a deterministic SHA-256 hash of the attestation
function generateAttestationHash(attestation: object): string {
const normalized = JSON.stringify(
attestation,
Object.keys(attestation).sort()
);
return createHash('sha256').update(normalized).digest('hex');
}
async function issueComplianceAttestation() {
// 1. Load the compliance assessment (generated by the DPO2U Agent)
const attestationData = JSON.parse(
readFileSync('./attestation-data.json', 'utf-8')
);
// 2. Hash the attestation — this hash goes on-chain
const attestationHash = generateAttestationHash(attestationData);
// 3. Connect to the host chain
const provider = await ComplianceAttestationContract.createProvider({
endpoint: 'https://rpc.dpo2u.com
networkId: 'preprod'
});
const wallet = provider.createWallet(process.env.DPO_PRIVATE_KEY!);
// 4. Call the issueAttestation circuit
// - attestationData is PRIVATE (never leaves the local machine)
// - attestationHash is PUBLIC (stored on the ledger)
const contract = new ComplianceAttestationContract(
process.env.CONTRACT_ADDRESS!,
wallet
);
const tx = await contract.issueAttestation(
JSON.stringify(attestationData), // private witness
attestationHash, // public output
BigInt(Date.now()), // timestamp
wallet.privateKey // authorization
);
const receipt = await tx.wait();
console.log('Attestation registered on-chain');
console.log('Transaction:', receipt.transactionHash);
console.log('Compliance hash:', attestationHash);
}
issueComplianceAttestation();
When you call contract.issueAttestation(), the SDK automatically generates the zk-SNARK proof using the proving key generated during compilation. This step may take a few seconds. The proof is included in the transaction and verified on-chain by the the host chain validators.
Verifying an attestation
Once an Attestation is on-chain, any party can verify it. Here are two approaches:
Using the MCP Server
The simplest method — a single API call:
curl -X POST https://mcp.dpo2u.com/tools/check_compliance_status \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"company_id": "cnpj:12.345.678/0001-90"}'
Response:
{
"compliant": true,
"score": 92,
"last_validated": "2026-03-13T14:30:00Z",
"proof_url": "zk-attestation://attestation/0x7a3f..."
}
For rate limits and error codes, see the MCP Server documentation.
Using TypeScript with the the SDK
For programmatic verification — including hash comparison against a local copy of the attestation:
import { createHash } from 'crypto';
import { ComplianceAttestationContract } from
'../contracts/managed/compliance-attestation/contract/index.js';
async function verifyAttestation(
contractAddress: string,
localAttestationData: object
) {
// Connect read-only — no private key needed for verification
const provider = await ComplianceAttestationContract.createReadOnlyProvider({
endpoint: 'https://rpc.dpo2u.com
});
const contract = new ComplianceAttestationContract(contractAddress, provider);
// Read the hash stored on-chain
const onChainHash = await contract.complianceHash();
const onChainTimestamp = await contract.lastAttestationTimestamp();
// Recompute the hash from the local attestation data
const localHash = createHash('sha256')
.update(JSON.stringify(
localAttestationData,
Object.keys(localAttestationData).sort()
))
.digest('hex');
// Compare: if they match, the attestation is authentic and untampered
const isValid = localHash === onChainHash;
console.log('On-chain hash:', onChainHash);
console.log('Local hash: ', localHash);
console.log('Timestamp: ', new Date(Number(onChainTimestamp)).toISOString());
console.log('Valid: ', isValid);
return isValid;
}
The the SDK examples target the testnet. Contract addresses and RPC endpoints will change when mainnet launches.
Agent authorization with AgentRegistry
Not everyone can create Attestations — only agents registered in AgentRegistry.compact have permission. This contract maintains a ledger of approved did:dpo2u:agent:* identities:
pragma language_version 0.20;
// Public ledger of authorized agent DIDs
export ledger agentCount: Uint64;
// Register a new agent — only governance can call this
export circuit registerAgent(
agentDid: Opaque<"string">,
agentName: Opaque<"string">,
permissions: Uint8,
governanceKey: PrivateKey
): [] {
// Verify caller has governance permission
assert(checkSignature(publicKey(governanceKey), governanceOwner));
// The agent DID and name become public on the ledger
// but the governance key remains private
agentCount = disclose(agentCount + 1);
}
// Check if an agent is authorized to issue attestations
export circuit isAuthorized(agentDid: Opaque<"string">): [] {
// Circuit verifies the agent exists and has WRITE permission
// The proof confirms authorization without revealing
// the full permission set or private key
disclose(agentDid);
}
The authorization model uses permission bits:
| Bit | Value | Description |
|---|---|---|
| READ | 1 | Query on-chain data |
| WRITE | 2 | Create Attestations |
| TREASURY | 4 | Financial operations |
| DEPLOY | 8 | Deploy contracts |
| GOVERNANCE | 16 | Manage agent registry |
When ComplianceRegistry calls isAuthorizedAgent(agent_did), it delegates to AgentRegistry.isAuthorized(). The zk-SNARK proves the agent has WRITE permission without revealing the agent's full permission set or private key.
Agent DIDs follow the pattern did:dpo2u:agent:<name>. Only agents listed in AgentRegistry can interact with ComplianceRegistry. For the Base Chain equivalent, see the AgentRegistry Solidity contract.
Selective disclosure: practical scenarios
One of the most powerful applications of zk-SNARKs for compliance is selective disclosure — proving exactly what needs to be proven, and nothing more.
Regulatory audit (ANPD)
The Brazilian data protection authority (ANPD) wants to verify that a company has a designated DPO and an up-to-date privacy policy. With zk-SNARKs, the company proves exactly that — without revealing the DPO's name, the policy content, or any other operational data.
B2B supplier contract
A company requires suppliers to demonstrate LGPD compliance before signing a data processing agreement. The supplier generates a proof that their compliance score exceeds 80 — without revealing the exact score, their vulnerabilities, or any other sensitive information.
M&A due diligence
During an acquisition, the buyer needs to verify the target company's compliance status. The target proves it has no critical pending violations — without exposing operational data processing details that are competitively sensitive.
LGPD/GDPR mapping
Each field in the dpo2u/lgpd/v1 schema maps to specific LGPD articles. The zero-knowledge verification ensures compliance without exposing the underlying data:
| LGPD principle | Article | Schema field | ZK verification |
|---|---|---|---|
| Finalidade (Purpose) | Art. 6, I | data_categories | Circuit checks categories are declared |
| Adequação (Adequacy) | Art. 6, II | data_categories | Circuit validates categories match declared purpose |
| Necessidade (Necessity) | Art. 6, III | data_categories | Circuit checks no excessive categories |
| Livre acesso (Free access) | Art. 6, IV | dpo_contact | Circuit verifies contact is non-null |
| Qualidade (Data quality) | Art. 6, V | last_review | Circuit checks review within 365 days |
| Transparência (Transparency) | Art. 6, VI | privacy_notice doc | CID exists on IPFS (verifiable) |
| Segurança (Security) | Art. 6, VII | On-chain Attestation | The proof itself is the security guarantee |
| Prevenção (Prevention) | Art. 6, VIII | dpia doc | CID exists on IPFS (verifiable) |
| Responsabilização (Accountability) | Art. 6, X | Immutable CID + Attestation | Permanent, tamper-proof record |
| Consentimento (Consent) | Art. 7, I | consent_mechanism | Circuit checks mechanism is declared |
These principles map directly to GDPR Articles 5 and 6. The schema is designed for LGPD but the verification logic applies equally to GDPR compliance assessments.
For a developer-oriented guide to LGPD's 10 legal bases and when each applies, read LGPD legal bases: a developer guide. For the nuances of legitimate interest — the most dangerous legal basis — see Legitimate interest: the dangerous legal basis.
Attestation lifecycle
A compliance attestation is not a one-time event — it is a continuous process. DPO2U supports a lifecycle aligned with regulatory requirements:
| Event | Recommended frequency | DPO2U action |
|---|---|---|
| Compliance assessment | Quarterly | Agent runs new analysis, issues updated attestation |
| Policy update | As needed | New attestation referencing updated policy CID |
| Security incident | Immediate | Incident response attestation recorded on-chain |
| Regulatory audit | On demand | Regulator verifies the history of on-chain hashes |
| Annual renewal | Yearly | Full attestation with updated score |
Each new attestation overwrites the complianceHash on the ledger, but the previous values remain in the blockchain's transaction history — providing a complete, tamper-proof audit trail.
What's next
- Smart contracts — full overview of all seven smart contracts
- Architecture — how contracts fit into the 5-layer protocol stack
- LGPD Kit schema — the complete
dpo2u/lgpd/v1specification - MCP Server — API reference for compliance verification tools
- Getting started — integrate DPO2U into your application