The trust registry is the central authority in a verification ecosystem. It answers a deceptively simple question: is this issuer authorized to issue this type of credential? Behind that question lies a distributed system that must handle multi-tenant isolation, event sourcing for auditability, sub-100 ms query latency, and strict consistency guarantees. This post describes how we built it.
Design Principles
We designed the trust registry around four principles:
- Tenant isolation: Every customer's data must be completely isolated. A query from Tenant A must never return data belonging to Tenant B, even under adversarial conditions.
- Auditability: Every state change — issuer registration, trust level modification, credential type creation, revocation — must be recorded in an immutable event log.
- Consistency: Verification decisions must reflect the latest state. A revocation must take effect immediately, not after a cache refresh.
- Availability: The registry must sustain verification traffic even during deployments, database maintenance, or partial infrastructure failures.
PostgreSQL with Row-Level Security
The trust registry stores its state in PostgreSQL. We evaluated dedicated multi-tenant databases, schema-per-tenant isolation, and row-level security (RLS). We chose RLS because it provides strong isolation guarantees without the operational overhead of managing hundreds of database instances or schemas.
Every table includes a tenant_id column. PostgreSQL RLS policies ensure that queries can only access rows matching the current session's tenant context. The tenant context is set at connection time via SET app.current_tenant and enforced by the database engine — the application code cannot bypass it, even with a SQL injection vulnerability.
-- Example RLS policy
CREATE POLICY tenant_isolation ON issuers
USING (tenant_id = current_setting('app.current_tenant')::uuid);
ALTER TABLE issuers ENABLE ROW LEVEL SECURITY;
ALTER TABLE issuers FORCE ROW LEVEL SECURITY;
The FORCE clause ensures that even table owners (including the application's database user) are subject to the policy. This defense-in-depth measure protects against privilege escalation.
Event Sourcing
The trust registry uses event sourcing for all state mutations. Rather than updating rows in place, we append events to an events table and derive the current state by replaying them. The events table has the following structure:
CREATE TABLE registry_events (
event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
aggregate_id UUID NOT NULL,
event_type TEXT NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
sequence_num BIGSERIAL
);
Every mutation — IssuerRegistered, TrustLevelChanged, CredentialTypeCreated, AttestationRevoked — is an event with a typed payload. The sequence_num column provides a total ordering within each tenant.
Materialized Views for Queries
Reading state by replaying events on every query would be too slow. Instead, we maintain materialized state tables that are updated transactionally when events are appended. The event insert and the state update happen in the same database transaction, ensuring the materialized view is always consistent with the event log.
This hybrid approach gives us the auditability of event sourcing with the query performance of a traditional relational model. The events table is the source of truth; the materialized tables are a read-optimized projection.
Tenant Isolation Beyond the Database
Database-level isolation is necessary but not sufficient. We enforce tenant boundaries at multiple layers:
- API gateway: The gateway extracts the tenant identifier from the authentication token and injects it into every downstream request. Services never trust tenant IDs provided by the client.
- Service layer: Each service validates that the tenant context in the request matches the authenticated session before processing any operation.
- Audit logging: The audit service records the tenant ID, user ID, action, and resource for every API call. Audit logs are themselves tenant-isolated using RLS.
- Encryption: Each tenant's sensitive data (e.g., private key references, PII) is encrypted with a tenant-specific data encryption key (DEK), which is itself encrypted with a key encryption key (KEK) stored in the HSM.
The Verification Flow
When a verifier submits an attestation for verification, the flow traverses several services:
- The API gateway authenticates the request, applies rate limiting, and routes it to the verification service.
- The verification service parses the attestation envelope and extracts the issuer DID and key ID.
- The service queries the trust registry to confirm the issuer is registered, active, and authorized to issue the credential type.
- The service retrieves the issuer's public key and verifies the cryptographic signature.
- The service checks the revocation registry to confirm the attestation has not been revoked.
- The service verifies the transparency log inclusion proof against the current signed tree head.
- The result — including the issuer's trust level, signature validity, revocation status, and log inclusion — is returned to the verifier.
Steps 3 through 6 execute concurrently where possible. The trust registry lookup and revocation check are independent and can proceed in parallel, reducing overall latency.
Availability and Failover
The trust registry runs as a Go service deployed across multiple availability zones on AWS ECS Fargate. PostgreSQL runs on Amazon RDS with Multi-AZ deployment and automated failover. Read replicas serve verification queries during primary maintenance windows.
We use connection pooling (PgBouncer) to manage database connections efficiently and prevent connection exhaustion during traffic spikes. Health checks at the load balancer, service, and database levels ensure that unhealthy instances are removed from the serving path within seconds.
Lessons Learned
Building the trust registry taught us several lessons worth sharing:
- RLS is powerful but requires discipline. Every new table, every migration, and every test must account for tenant isolation. We enforce this through CI checks that fail the build if any table lacks an RLS policy.
- Event sourcing pays off in regulated environments. When a customer asks "what changed and when?", the answer is a filtered query on the events table — not a forensic reconstruction from application logs.
- Hybrid event sourcing outperforms pure event sourcing at scale. Materializing state for reads eliminates the replay cost while preserving the audit trail.
For more on the trust registry architecture, see the technical documentation. If you are building verification infrastructure and want to discuss architectural patterns, reach out through the console.
