The Envelope
The attestation envelope is what the wire carries today and what the standard should add as first-class fields.
The Envelope
Open draft: 4 fields in preview, 5 fields drafted, all up for revision
The 4 fields below are what our open-source preview IAttestationVerifier carries on-chain. The 5 proposed additions are a working draft. Both sets are open including the 4 already in the preview. We'd rather get the schema right with input from integrators and adjacent-standard maintainers than ship the schema we wrote first.
The envelope is the data structure that travels over the wire. It's the set of fields a verifier consumes when it answers "is this attestation valid?". Today's envelope is intentionally minimal because UPC was designed for a single use case: ZK membership in an ASP tree. A standard that's going to serve more than one privacy protocol needs to grow into something more general: typed subjects, lifecycle metadata, revocation, selective disclosure.
This page maps what's live today against the fields the standard should grow into.
The live wire carries four fields. Five proposed additions cover subject typing, issuer provenance, validity window, nullifier semantics, and revocation. All additions are forward-compatible. Verifiers that don't understand them ignore them.
Today's envelope
The on-chain IAttestationVerifier.verify(uint256 identity, bytes proof) carries four conceptual fields. They aren't typed at the ABI level: identity is a single uint and proof is opaque bytes, but the membership circuit's public inputs always include:
| Field | Type | Role |
|---|---|---|
identity | uint256 (Poseidon commitment) | Who/what the attestation is about. Hidden behind the Poseidon hash. |
proof | PLONK proof bytes | The zero-knowledge witness that identity is in the tree. |
root | uint256 (Merkle root) | The published ASP tree root being attested against. |
nullifier | uint256 (optional) | Anti-replay token for state-changing operations. |
This is the membership-only envelope. It works for the question "is this subject in this approved set?" and refuses to overreach beyond it.
What the envelope is missing
The questions about subject diversity, lifecycle, revocation, and disclosure mode all point to fields the envelope doesn't carry today. Each of these wants to be a typed field at the envelope level, not implicit in the verifier's behavior.
Subject type
Today the subject is always Poseidon(address). The envelope assumes "this is about an address". For the wire to carry attestations about wallets, notes, transactions, pools, credentials, or policy decisions, the envelope needs a subjectType tag so verifiers can dispatch correctly.
Proposed values: address, wallet, note, transaction, pool, credential, policyDecision. Open set: adapters declare which they accept. The full subject-type taxonomy is part of this draft and will firm up as integrators push back on the initial list.
Issuer
Today the trust model is implicit: whoever registered the ASP on ASPRegistryHub is the issuer, and their reputation is what backs the attestation. There's no field on the envelope that names the issuer.
A first-class issuer field, an Ethereum address or a W3C DID, makes provenance explicit. Verifiers can then authorize per-issuer ("I accept attestations from issuers in my whitelist") rather than per-ASP-id.
Validity window
Today validity is coarse: the ASP root has a TTL (default 24 hours) and a 64-root history buffer; a proof against an expired root is rejected. There's no way to express "this attestation is valid from time T1 to T2" at the per-attestation level.
issuedAt and expiresAt (unix timestamps) on the envelope let an issuer commit to a specific validity window. This is useful for credentials with natural expiry (KYC re-verification cycles, accreditation renewals).
Revocation pointer
Today revocation is implicit: the operator removes the member from the tree, publishes a new root, the old root falls out of the history buffer after 64 updates. Detecting "this attestation was revoked" requires watching root publications.
A revocationPointer field: a URI or a registry ID for a revocation list. It lets verifiers check active revocation status without reconstructing tree history.
Nullifier mode
Today nullifier semantics are implicit: state-changing operations (deposit, withdraw, transfer) require a nullifier; read-only checks (eligibility queries) don't. This is convention, not contract.
A nullifierMode field with values single-use or reusable makes the contract explicit at the envelope level.
The proposed envelope, in code
Putting the pieces together, the envelope grows from 4 fields to 9:
struct Attestation {
bytes32 subjectType; // proposed: typed dispatch tag
uint256 identity; // current: Poseidon commitment
address issuer; // proposed: explicit provenance
uint64 issuedAt; // proposed: validity window start
uint64 expiresAt; // proposed: validity window end
uint256 root; // current: Merkle root
uint256 nullifier; // current: optional anti-replay
uint8 nullifierMode; // proposed: single-use vs reusable
bytes revocationPointer; // proposed: URI or registry id
bytes proof; // current: ZK proof bytes
}This is a proposal, not the deployed schema. We'd want input on field naming, optional vs required, and version negotiation before shipping.
Forward compatibility
The proposed extensions are additive: all current fields remain; new fields default to zero/empty for backward compatibility; verifiers that don't understand the new fields ignore them. We'd ship the v1 envelope as a typed Solidity struct alongside the current (identity, proof) ABI, with a wrapper that emits both.
Disclosure modes
The envelope is silent on selective disclosure today. A UPC proof is binary (in-set / not-in-set). For "prove age ≥ 18 without revealing identity" or "prove jurisdiction = X" the envelope needs to compose with selective-disclosure credential formats.
We see three composition shapes worth supporting:
- Embedded gadget: the SD primitive (e.g. BBS+ signature verification) is a Circom gadget compiled into the membership circuit. The envelope's
proofcarries both proofs together. - Two-stage verification: the SD attestation is verified off-chain or via a separate verifier; its result becomes a UPC tree leaf; UPC proves membership in the leaf set.
- Side-channel: UPC carries the membership proof; the SD attestation travels alongside in the same transaction and is verified by a separate ABI.
The envelope itself doesn't pick anything. The subjectType tag plus the verifier adapter does. See Coexistence for how this plays out against EAS, VC, SD-JWT, and BBS+.
What this means for integrators
If you're building against UPC today, you're building against the 4-field envelope. The proposed extensions are forward-compatible: all current fields remain; new fields are additive; verifiers that don't understand them ignore them. Building now is safe; the v1 envelope arrives as a typed wrapper around the same bytes.
UPC Standard (Working Draft)
A working draft of the UPC wire format for compliance attestations. The verifier ABI and registry are live and in use; the envelope schema, subject taxonomy, and lifecycle conventions are proposed here for review.
Lifecycle
Where in a protocol's lifecycle should compliance checks happen? UPP's mapping as a worked example, and why the standard stays silent.