Skip to main content

Programmable Policies

Policies are programmable constraints on data reveals. They answer three questions for every commitment in the Specter protocol:

  1. Who can reveal the committed data?
  2. When can the data be revealed?
  3. Under what conditions is the reveal permitted?

Policies apply to any data type in the protocol -- not just tokens. A timelock can restrict when a credential becomes valid. A destination restriction can ensure an API key is only delivered to a specific recipient. A threshold witness gate can require board approval before corporate secrets are unsealed. The policy system is data-agnostic by design.

How Policies Work

A policy is a smart contract that implements the IRevealPolicy interface. When a user commits data with a policy attached, two values are embedded in the commitment hash:

  • policyId -- the Ethereum address of the policy contract
  • policyParamsHash -- keccak256(abi.encode(policyParams)) reduced modulo the BN254 scalar field prime

Because these values are inputs to the Poseidon commitment hash, they are cryptographically bound to the commitment. They cannot be stripped, modified, or bypassed after the commitment is created. A commitment made with a timelock policy will always require the timelock policy -- there is no administrative override, no escape hatch, and no way to remove it.

Dual Enforcement

Specter enforces policies at two independent layers. Both must pass for a reveal to succeed.

1. ZK Circuit Enforcement (Prover Side)

The policyId and policyParamsHash are public inputs to the ZK proof. The prover must know the correct policy address and parameters to generate a valid proof. Dummy quadratic constraints in the circuit (policyIdSquare === policyId * policyId) ensure the prover cannot omit these values -- attempting to do so produces an invalid proof.

2. On-Chain Enforcement (Verifier Side)

After the proof is verified, the vault contract executes the policy's validate() function via staticcall with a 100,000 gas limit. The policy contract receives the full reveal context and returns true or false. If the policy returns false or reverts, the entire reveal transaction is rejected.

This dual enforcement means that even a malicious prover cannot bypass a policy. The ZK proof binds the policy to the commitment (the prover must know it), and the on-chain execution verifies the policy logic (the chain must approve it). Neither layer alone is sufficient -- both are required.

The IRevealPolicy Interface

Every policy contract implements the following interface:

interface IRevealPolicy {
function validate(
bytes32 commitment, // The commitment being revealed
bytes32 nullifier, // The nullifier for this reveal
address recipient, // The address receiving the data/tokens
uint256 amount, // The amount being revealed (0 for non-token data)
address token, // The token address (address(0) for native GHOST)
bytes calldata policyParams // ABI-encoded parameters specific to this policy
) external view returns (bool valid);
}

Key constraints on policy contracts:

ConstraintReason
Called via staticcallNo state writes permitted -- policies are pure validation logic
100,000 gas limitPrevents policies from performing expensive computation or griefing
Must return booltrue to allow the reveal, false to reject
No external callsPolicy contracts should not depend on other contract state (reliability)
No constructor / no adminReference policies are stateless -- anyone can verify their behavior

PolicyRegistry

The PolicyRegistry is a purely informational contract for discoverability. It does not enforce anything at the protocol level -- unregistered policies work exactly the same as registered ones. The registry exists so that users and frontends can discover available policies.

struct PolicyInfo {
string name; // Human-readable name
string description; // Brief description of behavior
address registrant; // Who registered it
uint256 timestamp; // When it was registered
}

Anyone can register a policy by calling register(address policy, string name, string description). The only requirement is that the policy address must be a deployed contract (not an EOA). The registry provides paginated listing via getPolicies(offset, limit) and lookup via getPolicy(address).

Data-Agnostic Policy Enforcement

Policies are not limited to token reveals. The same policy system applies to every data type in the protocol:

Data TypePolicy ExampleEffect
Tokens (GHOST)TimelockExpiryTokens cannot be revealed before unlock time or after expiry
CredentialsDestinationRestrictionDegree attestation can only be delivered to a specific employer
API KeysTimelockExpiryKey is only valid during a specific time window
Encrypted DocumentsThresholdWitness3-of-5 board members must sign before document is unsealed
ImagesDestinationRestrictionProvenance proof delivered only to the authenticated buyer
Bearer InstrumentsTimelockExpiry + DestinationRestrictionTicket redeemable only at event time, only by ticket holder

The amount parameter is 0 for non-token data types, and the token parameter is address(0). Policy contracts can inspect these fields to implement data-type-specific logic, or they can ignore them and enforce purely time-based or recipient-based constraints.

Built-In Policies

Specter ships with three reference policy contracts:

PolicyParametersConstraint
TimelockExpirylockUntil, expiresAtReveal only within a time window
DestinationRestrictionallowedRecipient or Merkle allowlistReveal only to specific address(es)
ThresholdWitnessWitness set, signatures, thresholdM-of-N signatures required

These are reference implementations. Any developer can deploy custom policy contracts implementing IRevealPolicy -- the protocol imposes no restrictions on policy logic beyond the interface, gas limit, and staticcall constraint.

No Policy (Default)

When policyId == address(0), no policy is enforced. In this case, policyParamsHash must also be 0. The commitment can be revealed at any time, by anyone who knows the phantom key, to any recipient. This is the default behavior for commitments created without a policy.