ADR-007: Anonymity Revoking Dashboard — MVP
| Status | Rejected |
| Date | 2026-03-11 |
Context
In the encrypted balances privacy system, every on-chain operation (transfer, shield, unshield) includes an encrypted piece of information — specifically, the transaction amount encrypted under the Anonymity Revoker's public key using ElGamal on the Grumpkin curve. This compliance mechanism allows a designated party (the Anonymity Revoker), upon request from a regulator, to decrypt these values and trace funds flow to investigate potentially illicit activity. The broader compliance requirements are described in Anonymity Revoking.
Today there is no tooling for the Anonymity Revoker to actually perform decryption. This ADR describes how to build an MVP dashboard — a minimal but secure web application that allows authorized users to decrypt transaction data on demand, with full audit logging. ADR-008 addresses the production version with hardened key security (Nitro Enclaves) and additional dashboard functionality.
MVP scope and non-goals
The MVP intentionally simplifies the full compliance requirements to deliver a working system quickly:
- In scope: single-key decryption, user authentication and authorization, per-request audit logging, basic UI for submitting transaction hashes and viewing decrypted results.
- Not in scope: case management, report export.
Assumptions
- The anonymity revoking key is a single ElGamal private key on the Grumpkin curve.
- The system's security rests on AWS secret management and the assumption that the admin account is not compromised.
- There can be multiple parties acting as Anonymity Revokers, each authenticating independently. Every decryption request is persistently logged for full transparency.
Proposal
Roles
| Role | Description |
|---|---|
| Admin | Generates the anonymity revoking key pair. Stores the private key in AWS Secrets Manager and keeps a physical backup (metal plate). Manages the email whitelist of authorized users via the AWS Management Console. Has direct access to AWS. |
| Anonymity Revoker (user) | An authorized person who can log in and submit decryption requests. Authenticates via Google. Cannot access the raw private key. |
Architecture
┌──────────────┐ ┌──────────────┐ ┌───────────────┐ ┌─────────────────┐
│ Frontend │────▶│ API Gateway │────▶│ Lambda │────▶│ Secrets Manager │
│ (Vercel) │ │ (REST API) │ │ (decrypt fn) │ │ (revoking key) │
└──────────────┘ └──────┬───────┘ └───────┬───────┘ └─────────────────┘
│ │ │
│ ┌─────┴──────┐ ┌─────┴──────┐
└─────────────▶│ Cognito │ │ DynamoDB │
│ (authn/z) │ │ (audit log)│
└────────────┘ └────────────┘
Services
| Service | Role |
|---|---|
| Vercel | Hosts the frontend SPA. Provides git-push-to-deploy, preview deployments, and CDN-backed hosting with minimal configuration. |
| Cognito User Pool | Handles authentication. Configured with Google federation so users sign in with their Google account. The admin maintains an email whitelist — only addresses on the list can authenticate. |
| API Gateway | Exposes a REST API for the frontend. Cognito authorizer validates the JWT on every request, ensuring only authenticated users can call the backend. |
| Lambda | Handles decryption requests. On each invocation: loads the anonymity revoking private key from Secrets Manager, performs ElGamal decryption on the requested payload, writes an audit record to DynamoDB, and returns the result. |
| Secrets Manager | Stores the anonymity revoking private key. Only the Lambda execution role has read access. The key never leaves AWS in plaintext (except during the admin's initial generation and backup). |
| DynamoDB | Stores the immutable audit log. Each record contains: email address of the requesting user, transaction hash(es), encrypted payload that was decrypted, decrypted result, and timestamp. |
User flow
-
Admin setup (one-time):
- Admin generates the ElGamal key pair (private key + public key on Grumpkin).
- Admin stores the private key in Secrets Manager, deploys the public key to the on-chain contracts, and keeps a physical backup of the private key.
- Admin configures the Cognito email whitelist with authorized users' Google email addresses.
-
Decryption request:
- User navigates to the dashboard and signs in via Google (Cognito).
- User enters one or more transaction hashes and clicks "Deanonymize".
- Frontend calls API Gateway with the Cognito JWT and the list of transaction hashes.
- API Gateway validates the JWT (authentication) and checks the user belongs to the authorized pool (authorization).
- Lambda fetches the encrypted compliance payloads for the given transactions (from chain or an indexer), loads the private key from Secrets Manager, decrypts each payload, writes an audit record to DynamoDB, and returns the decrypted details.
- Frontend displays the transaction details alongside the decrypted amounts.
Audit log schema
| Field | Type | Description |
|---|---|---|
requestId | String (ULID) | Unique identifier for the request |
userEmail | String | Email address of the user who made the request |
txHashes | List<String> | Transaction hashes submitted for decryption |
encryptedPayloads | List<String> | The encrypted ciphertexts that were decrypted |
timestamp | String (ISO 8601) | When the request was processed |
The audit table should be configured with DynamoDB point-in-time recovery enabled and deletion protection, so records cannot be silently removed.
AWS security configuration
The MVP relies on AWS-managed services for its security boundary. The following IAM and service-level controls should be in place:
IAM roles and least-privilege access:
| Principal | Permissions | Notes |
|---|---|---|
| Lambda execution role | secretsmanager:GetSecretValue (scoped to the revoking key ARN), dynamodb:PutItem (scoped to the audit table), read access to chain data source | This is the only principal that can read the private key. |
| API Gateway | Invoke the Lambda function | Cognito authorizer attached — unauthenticated requests are rejected before reaching Lambda. |
| Admin (IAM user or role) | Full access to Secrets Manager, Cognito User Pool, DynamoDB audit table | Should use MFA-protected sessions. This is the only human with AWS console access. |
| Anonymity Revokers | None — no AWS access | They interact exclusively through the frontend via Cognito-issued JWTs. |
Service-level hardening:
- Secrets Manager: Enable automatic rotation policy (even if the key is long-lived, rotation infrastructure should exist). Deny all access except the Lambda execution role via a resource policy.
- DynamoDB audit table: Enable point-in-time recovery and deletion protection. Apply an IAM policy that denies
DeleteItemandUpdateItemon audit records for all principals (append-only). The admin retains the ability to modify this policy if legally required. - Cognito: Disable self-signup — only admin-added email addresses can authenticate. Token validity should be short-lived (1 hour access token, no long-lived refresh tokens).
- API Gateway: Enable request throttling and WAF rules to limit abuse. All traffic over HTTPS only.
- CloudTrail: Enable CloudTrail logging on all Secrets Manager and DynamoDB API calls, providing an AWS-level audit trail independent of the application-level DynamoDB log.
- VPC isolation: The Lambda function should run in a dedicated VPC with no internet egress except through a NAT gateway (to reach AWS service endpoints). This limits exfiltration paths if the function is compromised.
Consequences
What becomes easier:
- Authorized Anonymity Revokers can decrypt transaction data through a simple web interface — no direct key access or CLI tooling needed.
- Every decryption is logged with the identity of who requested it and when, providing transparency and accountability.
- The system can be deployed quickly with managed AWS services, with no custom infrastructure to maintain.
- Adding or removing authorized users is a simple admin operation (update the Cognito whitelist).
What becomes harder:
- The private key exists as a single secret in Secrets Manager — a compromise of the admin's AWS account exposes the key.
- The MVP has no concept of cases or legal orders — there is no structured workflow to tie a decryption request to a specific regulatory inquiry.
- The Lambda loads the full private key on every invocation. While it stays within the AWS trust boundary, this is a wider attack surface than designs where no single component holds the full key.
What stays the same:
- On-chain contracts and circuits are unchanged — they already emit encrypted compliance payloads.
- The cryptographic scheme (ElGamal on Grumpkin) is unchanged.
- End-user privacy properties are unaffected — only authorized Anonymity Revokers with a valid Google account on the whitelist can trigger decryption.