# Specter Protocol > A Privacy Protocol for Data - Site: https://whitepaper.specterchain.com - Source: https://github.com/Specter-Foundation/whitepaper-website - Generated: 2026-04-03 ## Table of Contents - [Specter Protocol](https://whitepaper.specterchain.com/) - [The Data Transparency Problem](https://whitepaper.specterchain.com/problem/data-on-chain) - [Current Solutions and Their Limits](https://whitepaper.specterchain.com/problem/current-solutions) - [The Ghost Protocol](https://whitepaper.specterchain.com/protocol/overview) - [The Commit/Reveal Mechanism](https://whitepaper.specterchain.com/protocol/commit-reveal) - [Data Types and Commitment Variants](https://whitepaper.specterchain.com/protocol/data-types) - [Architecture Overview](https://whitepaper.specterchain.com/architecture/overview) - [Cosmos SDK Foundation](https://whitepaper.specterchain.com/architecture/cosmos-sdk) - [EVM Compatibility](https://whitepaper.specterchain.com/architecture/evm-compatibility) - [Ghostmint Precompile](https://whitepaper.specterchain.com/architecture/ghostmint-precompile) - [Poseidon Hash Function](https://whitepaper.specterchain.com/commitments/poseidon-hashing) - [Commitment Structure](https://whitepaper.specterchain.com/commitments/commitment-structure) - [Merkle Trees](https://whitepaper.specterchain.com/commitments/merkle-trees) - [Nullifier System](https://whitepaper.specterchain.com/commitments/nullifiers) - [Zero-Knowledge Proofs](https://whitepaper.specterchain.com/zk-proofs/overview) - [Groth16 on BN254](https://whitepaper.specterchain.com/zk-proofs/groth16-bn254) - [Redemption Circuit](https://whitepaper.specterchain.com/zk-proofs/redemption-circuit) - [Access Proof Circuit](https://whitepaper.specterchain.com/zk-proofs/access-proof-circuit) - [Trusted Setup](https://whitepaper.specterchain.com/zk-proofs/trusted-setup) - [Phantom Keys](https://whitepaper.specterchain.com/phantom-keys/overview) - [Standard Phantom Keys](https://whitepaper.specterchain.com/phantom-keys/standard-keys) - [Open Ghost Protocol](https://whitepaper.specterchain.com/phantom-keys/open-ghost) - [Phantom Identity](https://whitepaper.specterchain.com/phantom-identity/overview) - [How Phantom Identity Works](https://whitepaper.specterchain.com/phantom-identity/how-it-works) - [Split-Key Architecture](https://whitepaper.specterchain.com/phantom-identity/split-key-architecture) - [Phantom Identity vs. Phantom Keys](https://whitepaper.specterchain.com/phantom-identity/vs-phantom-keys) - [Access Proofs](https://whitepaper.specterchain.com/persistent-access/access-proofs) - [Bearer Formats](https://whitepaper.specterchain.com/persistent-access/bearer-formats) - [Encryption & Key Management](https://whitepaper.specterchain.com/persistent-access/encryption) - [Programmable Policies](https://whitepaper.specterchain.com/policies/overview) - [Circuit-Level Policy Binding](https://whitepaper.specterchain.com/policies/circuit-binding) - [Timelock & Expiry](https://whitepaper.specterchain.com/policies/timelock-expiry) - [Destination Restriction](https://whitepaper.specterchain.com/policies/destination-restriction) - [Threshold Witness](https://whitepaper.specterchain.com/policies/threshold-witness) - [GHOST Token](https://whitepaper.specterchain.com/ghost-token/overview) - [Supply Mechanics](https://whitepaper.specterchain.com/ghost-token/supply-mechanics) - [Mint & Burn Flow](https://whitepaper.specterchain.com/ghost-token/mint-burn-flow) - [Governance](https://whitepaper.specterchain.com/ghost-token/governance) - [Scaling Strategy](https://whitepaper.specterchain.com/scaling/overview) - [Batch Operations](https://whitepaper.specterchain.com/scaling/batch-operations) - [Session Vaults](https://whitepaper.specterchain.com/scaling/session-vaults) - [Sharded Merkle Trees](https://whitepaper.specterchain.com/scaling/sharded-trees) - [Relayer Network](https://whitepaper.specterchain.com/infrastructure/relayer-network) - [Stealth Addresses](https://whitepaper.specterchain.com/infrastructure/stealth-addresses) - [Cross-Chain Interoperability](https://whitepaper.specterchain.com/infrastructure/cross-chain) - [Threat Model](https://whitepaper.specterchain.com/security/threat-model) - [Cryptographic Assumptions](https://whitepaper.specterchain.com/security/cryptographic-assumptions) - [Audit Status](https://whitepaper.specterchain.com/security/audits) - [Development Phases](https://whitepaper.specterchain.com/roadmap/phases) - [Research Directions](https://whitepaper.specterchain.com/roadmap/research) - [Glossary](https://whitepaper.specterchain.com/glossary) - [References](https://whitepaper.specterchain.com/references) --- # Specter Protocol A Privacy Protocol for Data Commit any data — credentials, API keys, images, encryption keys, tokens — to a Merkle tree as a Poseidon hash. Reveal it later with a zero-knowledge proof. No link between commit and reveal. No trusted intermediary. No exposed plaintext. Specter is a sovereign Layer 1 blockchain purpose-built for data privacy. ### Data Commitments Hash any data into a Poseidon commitment and insert it into a Merkle tree. The original data never touches the chain. Retrieve or prove knowledge of it later using a Groth16 zero-knowledge proof — without revealing the data itself or linking the proof to the original commit. ### Phantom Identity Operate on-chain without a persistent public address. Phantom Identity uses split-key cryptography so that every interaction appears to originate from a fresh, unlinkable identity. No transaction graph. No behavioral fingerprint. Full protocol-level pseudonymity. ### Programmable Policies Bind commitments to enforceable on-chain rules — timelocks, destination restrictions, threshold witnesses, expiry dates — all verified inside the ZK circuit. Policies travel with the data, not with the account. Compliance without surveillance. | Parameter | Value | |---|---| | Consensus | CometBFT BFT | | Framework | Cosmos SDK v0.53.2 | | EVM | Full compatibility | | ZK System | Groth16 on BN254 | | Hash Function | Poseidon | | Merkle Depth | 20 (~1M entries) | | Native Token | GHOST | | Max Supply | 1 billion | ## Executive Summary Specter is a data privacy protocol built as a sovereign Layer 1 blockchain. Its core primitive — the **Ghost Protocol** — is a commit/reveal system that works with arbitrary data, not just tokens. Any piece of data can be hashed into a Poseidon commitment, inserted into an on-chain Merkle tree, and later retrieved or proven via a Groth16 zero-knowledge proof. The commit and reveal operations are cryptographically unlinkable: no observer can connect a commitment to its corresponding reveal. This design is **data-agnostic by construction**. The same circuit and the same Merkle tree that enables private token transfers also enables private credential verification, sealed API key storage, unlinkable image provenance, and encrypted key distribution. The protocol does not distinguish between data types at the commitment layer — it sees only hashes. Specter runs on Cosmos SDK with CometBFT consensus, giving it sovereignty over its own validator set, governance, and upgrade path. Full EVM compatibility means existing Solidity tooling, wallets, and dApps can deploy on Specter without modification. A custom precompile (`GhostMint`) exposes the commit/reveal operations to EVM smart contracts at native speed. ### GHOST Is One Application of This Protocol The GHOST token is the native gas and staking token of the Specter network. Its private transfer mechanism — burn tokens into a commitment, mint fresh tokens from a ZK proof — is a specialized instance of the Ghost Protocol applied to fungible value. But the protocol itself is far broader. Credentials, images, API keys, encryption keys, bearer instruments, and any other structured data can use the same commit/reveal infrastructure with the same privacy guarantees. The whitepaper that follows describes the protocol in full: the problem it solves, the cryptographic primitives it uses, the architecture that hosts it, and the token economics that sustain it. ## How to Read This Whitepaper The document is organized in fourteen sections: 1. **The Problem** — Why on-chain data transparency is a liability, and why current solutions fall short. 2. **Protocol Overview** — The Ghost Protocol commit/reveal system, data types, and commitment variants. 3. **Architecture** — Cosmos SDK integration, EVM compatibility, and the GhostMint precompile. 4. **Data Commitments** — Poseidon hashing, commitment structure, Merkle trees, and nullifiers. 5. **Zero-Knowledge Proofs** — Groth16 on BN254, the redemption circuit, access proof circuit, and trusted setup. 6. **Phantom Keys** — Standard keys and Open Ghost keys for flexible identity models. 7. **Phantom Identity** — Split-key architecture for unlinkable on-chain interaction. 8. **Persistent Access** — Access proofs, bearer formats, and encrypted data retrieval. 9. **Policy System** — Circuit-bound policies including timelocks, destination restrictions, and threshold witnesses. 10. **GHOST Token** — Supply mechanics, mint/burn flow, and governance. 11. **Scaling** — Batch operations, session vaults, and sharded Merkle trees. 12. **Infrastructure** — Relayer network, stealth addresses, and cross-chain bridges. 13. **Security** — Threat model, cryptographic assumptions, and audit plan. 14. **Roadmap** — Development phases and open research questions. --- # The Data Transparency Problem Blockchains were designed for verifiability. Every transaction, every state change, every byte of calldata is permanently visible to every participant in the network. This property — radical transparency — is essential for trustless consensus. It is also a fundamental liability for any application that handles sensitive data. The problem is not limited to financial transactions. **All data written to a public blockchain is permanently, globally visible.** This includes credentials, API keys, metadata, access patterns, contract interactions, and any other information that touches the chain. The transparency that makes blockchains trustworthy also makes them unsuitable for most real-world data handling. ## What Is Exposed ### Financial Data Every token transfer, swap, liquidity provision, and lending operation is recorded with full sender/receiver addresses, amounts, and timestamps. An observer can reconstruct a complete financial profile of any address: - Net worth across all on-chain assets - Income sources and payment patterns - Trading strategies and portfolio rebalancing - Counterparty relationships This is not a theoretical concern. On-chain analytics firms routinely map wallet clusters to real-world identities using publicly available data. A single interaction with a KYC-gated exchange links an entire transaction history to a legal name. ### Credentials and Access Tokens Decentralized identity systems, verifiable credentials, and on-chain attestations all suffer from the same transparency problem. When a credential is issued on-chain, the relationship between issuer and holder is public. When an access token is verified on-chain, the access pattern is public. This means: - An employer can see every credential verification an employee has participated in - A service provider can track which users hold which credentials - Credential revocations are visible, revealing when and why access was removed ### API Keys and Secrets Smart contracts that integrate with external services must handle API keys, webhook URLs, and other secrets. Storing these on-chain — even in encrypted form — exposes the ciphertext, the access pattern, and the relationship between the contract and the external service. Key rotation events are visible. Usage patterns reveal operational details. ### Metadata and Behavioral Patterns Even when the payload itself is encrypted or stored off-chain, the metadata is often more revealing than the content: - **Timing**: When transactions occur reveals time zones, work schedules, and operational rhythms - **Frequency**: How often an address interacts with a contract reveals dependency and importance - **Graph structure**: The set of addresses that interact with each other reveals organizational structure - **Gas patterns**: Transaction fee behavior reveals urgency and sophistication ## Real-World Consequences ### Financial Surveillance Transparent financial data enables front-running (MEV extraction based on observing pending transactions), copy-trading (replicating the strategies of successful addresses), and targeted social engineering (approaching individuals whose on-chain wealth is visible). Institutional participants face competitive intelligence leakage — every trade, every position, every hedge is visible to competitors. ### Competitive Intelligence Leakage Organizations deploying smart contracts expose their business logic, user base, and operational patterns. A competitor can observe: - How many users are interacting with a protocol - What the average transaction size is - When usage spikes and drops occur - Which other protocols the user base interacts with This is the equivalent of making a company's internal analytics dashboard public. ### Credential Exposure On-chain credential systems create a public record of who holds what credentials, who issued them, and when they were verified. Medical credentials, professional certifications, background checks, and access permissions become part of an immutable public record. Even the act of *checking* a credential creates a public trace. ### Metadata Correlation Individual pieces of metadata may seem harmless, but correlation across multiple data points is powerful. An address that interacts with a healthcare dApp, a specific pharmacy contract, and an insurance protocol reveals a medical condition — without any of those applications explicitly publishing health data. The transparency of the chain makes these correlations trivially computable. ## The Regulatory Tension Data privacy regulations — GDPR, CCPA, HIPAA, and their international equivalents — impose strict requirements on how personal data is collected, stored, and shared. Public blockchains violate nearly all of these requirements by default: | Requirement | Public Blockchain Reality | |---|---| | Right to erasure | Data is immutable and permanent | | Data minimization | All data is replicated to every node | | Purpose limitation | Data is available for any purpose | | Access control | Data is readable by anyone | | Consent management | No mechanism to revoke access | This creates a fundamental tension. Applications that need the trust guarantees of a blockchain also need privacy guarantees that blockchains do not provide. Developers are forced to choose between trustless verifiability and data protection compliance — a choice that should not be necessary. ## The Core Insight Data on a blockchain needs the same privacy guarantees that society already expects for private messages, medical records, and financial statements. The solution is not to avoid putting data on-chain — that sacrifices the trust and verifiability that blockchains provide. The solution is to build a chain where **data can be committed, proven, and verified without being revealed**. This is the problem Specter was designed to solve. The Ghost Protocol provides a cryptographic mechanism to commit any data to an on-chain Merkle tree as a hash, and later prove knowledge of that data using a zero-knowledge proof — without exposing the data itself, without linking the proof to the original commitment, and without trusting any intermediary. --- # Current Solutions and Their Limits Several projects have attempted to bring privacy to blockchains. Each makes meaningful contributions, but none provides a **general-purpose data commitment system with ZK reveals on a sovereign L1**. They are either limited to a single data type (tokens), dependent on trusted hardware, or architecturally constrained to a specific execution environment. ## Comparison | Solution | Scope | Proof System | Data Support | Composability | Limitation | |---|---|---|---|---|---| | **Tornado Cash** | Token mixing | Groth16 (Ethereum) | Tokens only (fixed denominations) | None (isolated pool) | Sanctioned by OFAC; pool-based custody; no general data support; frozen funds risk | | **Aztec** | Private DeFi | PLONK (custom rollup) | DeFi primitives (transfers, swaps) | Within Aztec ecosystem | Rollup on Ethereum; not sovereign; limited to DeFi use cases; complex programming model | | **Zcash** | Private payments | Groth16 (Sapling) / Halo 2 (Orchard) | Payment transactions only | None (UTXO model) | Payments only; no smart contracts; no general data; limited ecosystem | | **Secret Network** | General private compute | TEE (Intel SGX) | Any (in trusted enclave) | Smart contract composability | Trust Intel hardware; side-channel attacks demonstrated; not cryptographically private | | **IPFS / Filecoin** | Data availability | None | Any file | None (storage layer) | No privacy — content-addressed means anyone with the CID can read the data; no proofs | ## Detailed Analysis ### Tornado Cash Tornado Cash demonstrated that Groth16 proofs and Merkle trees could provide meaningful transaction privacy on Ethereum. Its architecture — deposit tokens into a pool, withdraw from the pool with a ZK proof — is the direct ancestor of Specter's commit/reveal model. However, Tornado Cash has critical limitations: - **Token-only**: The system exclusively handles ETH and a small set of ERC-20 tokens in fixed denominations. There is no mechanism for committing arbitrary data. - **Pool custody**: Deposited tokens sit in a smart contract. If the contract is sanctioned, paused, or exploited, funds are frozen. Users do not truly own their deposited assets — they own a proof that the pool owes them. - **Fixed denominations**: Users must deposit and withdraw in preset amounts (0.1 ETH, 1 ETH, 10 ETH, 100 ETH). This limits usability and creates denomination-based linkability. - **Regulatory action**: OFAC sanctions against Tornado Cash in August 2022 demonstrated that pool-based privacy is fragile. The contract still exists on Ethereum, but most front-ends are down and most compliant entities will not interact with addresses that have touched it. - **No composability**: Tornado Cash is an isolated application. Its privacy does not extend to other contracts or protocols. ### Aztec Aztec is building a ZK-rollup on Ethereum with native privacy for DeFi operations. It uses PLONK proofs and a custom programming language (Noir) to enable private smart contracts. Limitations: - **Rollup, not sovereign**: Aztec inherits Ethereum's security but also its constraints. It cannot set its own consensus rules, validator set, or fee market. Upgrades require coordination with the Ethereum L1. - **DeFi-focused**: The primary use cases are private token transfers and private DeFi interactions. General-purpose data commitments are not a core design goal. - **Programming complexity**: Noir is a new language with a small ecosystem. Developers must learn Aztec-specific abstractions for private state, which increases adoption friction. - **Data availability on Ethereum**: Rollup data is posted to Ethereum calldata, which is publicly visible. While individual transactions are encrypted, the metadata (transaction count, batch timing, gas usage) is exposed on the L1. ### Zcash Zcash pioneered production zero-knowledge proofs for blockchain privacy. Its shielded pool uses Groth16 (Sapling) and Halo 2 (Orchard) to enable fully private transactions where sender, receiver, and amount are hidden. Limitations: - **Payments only**: Zcash is a payment-focused cryptocurrency. It has no smart contract capability, no general-purpose execution environment, and no mechanism for committing arbitrary data. - **UTXO model**: The UTXO-based architecture makes it difficult to build complex applications on top of Zcash. There is no account state, no contract storage, and no programmable logic beyond the payment circuits. - **Low shielded adoption**: Despite being available since 2016, the vast majority of Zcash transactions are transparent (non-shielded). The optional nature of privacy reduces the anonymity set for users who do use shielded transactions. - **No composability**: Shielded transactions cannot interact with external protocols, oracles, or cross-chain bridges without exiting the shielded pool. ### Secret Network Secret Network uses Trusted Execution Environments (TEEs) — specifically Intel SGX enclaves — to provide private smart contract execution. Contract state is encrypted at rest and decrypted only inside the enclave. Limitations: - **Hardware trust assumption**: Security depends on Intel's SGX implementation being free of vulnerabilities. This is a strong assumption. Multiple side-channel attacks against SGX have been demonstrated in academic literature (Foreshadow, Plundervolt, SGAxe). - **Not cryptographically private**: TEE-based privacy is fundamentally different from ZK-based privacy. A TEE provides *computational* isolation, not *mathematical* proof. If the enclave is compromised, all data is exposed retroactively. - **Single point of failure**: All validators run the same Intel hardware. A vulnerability in SGX compromises the entire network simultaneously. - **Attestation complexity**: Verifying that a validator is running genuine SGX hardware requires Intel's attestation service, introducing a centralized dependency. ### IPFS and Filecoin IPFS provides content-addressed, decentralized file storage. Filecoin adds economic incentives for storage providers. Together, they solve the data availability problem but not the data privacy problem. Limitations: - **No privacy**: IPFS is content-addressed. Anyone who knows the Content Identifier (CID) of a file can retrieve and read it. There is no access control, no encryption at the protocol level, and no proof system. - **No proofs**: There is no mechanism to prove knowledge of data stored on IPFS without revealing the data or the CID. You cannot demonstrate that a file meets certain criteria without making the file public. - **Encryption is DIY**: Users can encrypt data before uploading to IPFS, but key management, access control, and proof generation are entirely the user's responsibility. There is no protocol-level integration. - **Availability, not verifiability**: IPFS guarantees that data is available and has not been tampered with (via content addressing). It does not guarantee anything about the *meaning* or *properties* of the data without revealing it. ## What Is Missing The landscape reveals a consistent gap. No existing solution provides all of the following: 1. **Data-agnostic commitments** — the ability to commit any data type (credentials, images, API keys, tokens, encryption keys) to an on-chain structure. 2. **ZK-based reveals** — the ability to prove knowledge of committed data without revealing it, using cryptographic proofs rather than trusted hardware. 3. **Unlinkability** — the inability for any observer to connect a commitment to its corresponding reveal. 4. **Sovereign L1** — a chain that controls its own consensus, governance, and upgrade path, not dependent on another chain's constraints. 5. **EVM composability** — the ability for existing Solidity contracts and tooling to interact with the privacy system natively. 6. **Programmable policies** — the ability to bind enforceable rules (timelocks, restrictions, thresholds) to commitments, verified inside the ZK circuit. Specter is designed to fill this gap. The Ghost Protocol provides a general-purpose commit/reveal system over Poseidon-hashed Merkle trees with Groth16 ZK proofs, running on a sovereign Cosmos SDK chain with full EVM compatibility. The sections that follow describe how this works. --- # The Ghost Protocol The Ghost Protocol is Specter's core primitive. It is a **commit/reveal system for any data**. A user hashes data into a Poseidon commitment, inserts that commitment into an on-chain Merkle tree, and later proves knowledge of the committed data using a Groth16 zero-knowledge proof. The commit and reveal operations are cryptographically unlinkable — no observer can determine which commitment corresponds to which reveal. This is not a token mixer. It is not a payment privacy scheme. It is a **general-purpose data commitment and verification protocol** that happens to also support private token operations as one of its use cases. ## Core Primitive The fundamental operation of the Ghost Protocol is: 1. **Hash any data** into a Poseidon commitment 2. **Insert** the commitment into an on-chain Merkle tree 3. **Reveal** by generating a ZK proof of knowledge of the commitment preimage and Merkle tree membership 4. **Verify** the proof on-chain and record a nullifier to prevent replay That is the entire protocol at its most abstract level. Everything else — token transfers, credential verification, key distribution, access proofs — is a specialization of this pattern applied to different data types. ## Data-Agnostic by Design The Ghost Protocol does not care what `dataHash` represents. At the commitment layer, the protocol sees only a field element — the output of a Poseidon hash. The semantics of the data are determined by the application layer, not the protocol layer. This means the same infrastructure supports: ### Credentials A university hashes a degree attestation. The graduate commits it. Later, the graduate proves they hold a valid degree to an employer — without revealing the university, the degree specifics, or any link to the original issuance. ### Images A photographer hashes an original image. The hash is committed on-chain as a provenance record. Later, the photographer can prove they are the original creator without revealing the image, the timestamp of the commitment, or any identifying metadata. ### API Keys A service provider generates an API key and commits its hash on-chain. A client proves they hold a valid key without revealing the key itself. Key rotation creates a new commitment; the old key's nullifier is spent, making it invalid. ### Encryption Keys A user commits the hash of a public encryption key. Recipients can verify the key's authenticity by checking the commitment against the Merkle tree, and the key holder can prove ownership without exposing the key to the chain. ### Tokens (GHOST) A user burns GHOST tokens by committing the token amount, token ID, and associated metadata into the Merkle tree. Later, the user mints fresh tokens by proving knowledge of the commitment. The burn and mint are unlinkable — this is a private transfer. ### Bearer Instruments An entity creates a digital bearer instrument (a ticket, a coupon, a one-time access pass) by committing its hash. The holder proves possession and redeems it by revealing with a ZK proof. The nullifier prevents double-spending. ## Key Properties ### Unlinkability The commit (Vanish) and reveal (Summon) transactions cannot be connected by any observer. The ZK proof demonstrates knowledge of a commitment in the Merkle tree without identifying *which* commitment. The anonymity set is the entire set of commitments in the tree — up to approximately 1 million entries at Merkle depth 20. Unlinkability holds even against a fully cooperating set of validators. The proof reveals nothing about the index, position, or insertion time of the commitment. ### Unforgeability A valid reveal requires a Groth16 proof that the prover knows the preimage of a commitment that exists in the Merkle tree. Without knowledge of the secret values (secret, nullifier secret, and blinding factor), it is computationally infeasible to generate a valid proof. The soundness of Groth16 on the BN254 curve guarantees this property under standard cryptographic assumptions. ### Non-Replayability Every reveal produces a **nullifier** — a deterministic value derived from the commitment's secret inputs. The nullifier is recorded on-chain in a registry. If the same commitment is revealed a second time, the nullifier will already exist in the registry, and the transaction will be rejected. This prevents double-spending of tokens, double-use of credentials, and replay of any other commitment type. The nullifier is derived as: $$ \text{nullifier} = \text{Poseidon}(\text{nullifierSecret}, \text{leafIndex}) $$ This formulation ensures that the nullifier is deterministic (the same commitment always produces the same nullifier) but unlinkable (the nullifier cannot be traced back to the commitment without knowing the nullifier secret). ### Data Confidentiality The original data never appears on-chain. Only its Poseidon hash is committed, and the ZK proof reveals only the public outputs defined by the circuit (root, nullifier hash, data hash or recipient, and access tag). The chain stores commitments and nullifiers — never plaintext data. ## The Token Flow Is a Special Case Private token transfers on Specter follow the exact same commit/reveal pattern described above, with one specialization: the `dataHash` encodes a token amount, token ID, and policy parameters. When a token commitment is revealed, the protocol mints fresh tokens to the specified recipient. When a token commitment is created, the protocol burns the corresponding tokens. This burn/mint model means there is no custodial pool, no locked funds, and no contract that can be sanctioned or frozen. Tokens are destroyed on commit and created on reveal. The only link between the two operations is the ZK proof — which reveals nothing about the original transaction. The implications of this design — and the contrast with pool-based approaches like Tornado Cash — are explored in detail in the [Commit/Reveal Mechanism](./commit-reveal.md) section. ## Protocol Layers The Ghost Protocol operates across three layers: | Layer | Responsibility | Components | |---|---|---| | **Cryptographic** | Commitment generation, proof creation, nullifier derivation | Poseidon hash, Groth16 prover, BN254 curve arithmetic | | **Consensus** | Merkle tree management, nullifier registry, proof verification | CometBFT validators, Cosmos SDK modules (`x/ghostmint`) | | **Application** | Data type semantics, policy enforcement, user interaction | EVM smart contracts, GhostMint precompile, client SDKs | The cryptographic layer is constant across all data types. The consensus layer manages the shared state (Merkle trees and nullifier sets). The application layer determines what happens when a commitment is created or revealed — whether tokens are burned/minted, credentials are verified, or access is granted. This separation of concerns is what makes the protocol extensible. Supporting a new data type does not require changes to the ZK circuits or the consensus layer. It requires only a new `dataHash` computation at the application layer and, optionally, a new policy type at the policy layer. --- # The Commit/Reveal Mechanism The Ghost Protocol's commit/reveal mechanism has two phases: **Vanish** (commit) and **Summon** (reveal). These names reflect the user experience — data or value "vanishes" from public view and is later "summoned" back into existence with a proof. The two operations are cryptographically unlinkable. This page describes both phases in detail, shows how they work for general data and for tokens side by side, and explains why the burn/mint model is superior to deposit/withdraw for token privacy. ## Phase 1: Vanish (Commit) The Vanish operation takes private data and seals it on-chain as an irreversible commitment. After Vanish completes, the data exists on-chain only as a Poseidon hash inside a Merkle tree. The original data, the sender's relationship to the commitment, and the timing of insertion are all hidden from future observers once the anonymity set grows. ### General Data Flow For arbitrary data (credentials, images, API keys, encryption keys), the Vanish operation proceeds as follows: 1. **Generate secrets**: The user generates a random `secret`, a `nullifierSecret`, and a `blinding` factor. These are stored locally and never transmitted. 2. **Compute data hash**: The user computes `dataHash = Poseidon(data)` where `data` is the credential, key, image hash, or other payload. 3. **Compute commitment**: The commitment is `Poseidon4(secret, nullifierSecret, dataHash, blinding)`. 4. **Submit Vanish transaction**: The commitment (a single field element) is submitted to the chain. 5. **Merkle insertion**: The `x/ghostmint` module inserts the commitment as the next leaf in the Merkle tree and updates the path from the leaf to the root. The original data is never transmitted to the chain. Only the 256-bit commitment is stored on-chain. ### Token Flow For GHOST tokens (or any supported token), the Vanish operation additionally destroys the tokens: 1. **Generate secrets**: Same as general data — `secret`, `nullifierSecret`, and `blinding`. 2. **Compute commitment**: The token commitment uses a 7-input Poseidon hash: `Poseidon7(secret, nullifierSecret, tokenId, amount, blinding, policyId, policyParamsHash)`. 3. **Submit Vanish transaction**: The commitment is submitted along with the token amount and token ID. 4. **Token burn**: The `x/ghostmint` module **burns** the specified tokens from the sender's account. The tokens are destroyed — they no longer exist in any account, pool, or contract. 5. **Merkle insertion**: The commitment is inserted into the Merkle tree, exactly as with general data. The key difference: when tokens are vanished, they are **burned, not deposited**. There is no pool. There is no contract holding funds. The tokens cease to exist. ## Phase 2: Summon (Reveal) The Summon operation proves knowledge of a commitment without identifying which commitment. It generates a Groth16 zero-knowledge proof and submits it to the chain for verification. If the proof is valid and the nullifier has not been used, the reveal succeeds. ### General Data Flow For arbitrary data, the Summon operation verifies and returns the data hash: 1. **Fetch Merkle proof**: The client retrieves the authentication path for the commitment's leaf position from the chain. 2. **Compute nullifier**: `nullifier = Poseidon(nullifierSecret, leafIndex)`. 3. **Generate ZK proof**: The client generates a Groth16 proof with the following structure: - **Private inputs**: `secret`, `nullifierSecret`, `dataHash`, `blinding`, `leafIndex`, Merkle authentication path - **Public inputs**: `root`, `nullifierHash`, `dataHash`, `accessTag` 4. **Submit Summon transaction**: The proof and public inputs are submitted to the chain. 5. **On-chain verification**: - The Groth16 verifier checks the proof against the verification key - The `root` is checked against the set of recognized Merkle roots - The `nullifierHash` is checked against the nullifier registry 6. **Nullifier recording**: The nullifier is added to the registry, preventing replay. 7. **Data hash returned**: The verified `dataHash` is emitted as an event or returned to the calling contract. ### Token Flow For tokens, the Summon operation mints fresh tokens: 1. **Fetch Merkle proof**: Same as general data. 2. **Compute nullifier**: Same as general data. 3. **Generate ZK proof**: The proof uses the 7-input token circuit: - **Private inputs**: `secret`, `nullifierSecret`, `tokenId`, `amount`, `blinding`, `policyId`, `policyParamsHash`, `leafIndex`, Merkle authentication path - **Public inputs**: `root`, `nullifierHash`, `recipient`, `amount` 4. **Submit Summon transaction**: The proof, public inputs, and recipient address are submitted. 5. **On-chain verification**: Same checks as general data — valid proof, recognized root, unused nullifier. 6. **Nullifier recording**: Same as general data. 7. **Token mint**: The `x/ghostmint` module **mints** fresh tokens to the specified recipient address. These are new tokens, not transfers from a pool. ## Why Burn/Mint Is Superior to Deposit/Withdraw Traditional privacy protocols (Tornado Cash, Railgun, etc.) use a **deposit/withdraw** model: tokens are deposited into a smart contract pool, and later withdrawn from that pool with a ZK proof. This creates a custodial intermediary — the pool contract — that holds all deposited funds. Specter's **burn/mint** model eliminates the pool entirely. Tokens are destroyed on Vanish and created on Summon. There is no intermediate state where tokens exist in a contract. | Property | Deposit/Withdraw (Pool) | Burn/Mint (Specter) | |---|---|---| | **Custodial risk** | Pool contract holds all funds | No pool — tokens do not exist between commit and reveal | | **Regulatory surface** | Pool can be sanctioned, blocked, or frozen | No entity or contract to target — burn and mint are protocol operations | | **Smart contract risk** | Pool contract is a high-value exploit target | No pool contract to exploit | | **Frozen funds** | If pool is compromised or sanctioned, all deposited funds are at risk | Tokens do not exist between operations — nothing to freeze | | **Supply integrity** | Total supply is unchanged (tokens are in the pool) | Total supply temporarily decreases on Vanish and restores on Summon | | **Implementation** | Requires a custodial contract with deposit/withdraw logic | Integrated into the consensus layer — burn and mint are native operations | ### No Custodial Pool In a pool-based system, every deposited token sits in a contract. That contract is a single point of failure. If the contract has a bug, an attacker can drain all deposits. If a government sanctions the contract address, all deposited funds are effectively frozen. If the contract is upgradeable, the upgrade authority can potentially steal funds. Specter has no pool. After Vanish, the tokens do not exist anywhere — they have been burned by the consensus layer. After Summon, fresh tokens are minted by the consensus layer. The only entity that can burn or mint tokens is the protocol itself, governed by the validator set and the ZK verification logic. ### No Frozen Funds When OFAC sanctioned the Tornado Cash contract addresses in August 2022, any tokens deposited in those contracts became effectively inaccessible through compliant front-ends and RPC providers. The tokens still existed on-chain but could not be withdrawn without interacting with a sanctioned address. In Specter's burn/mint model, there is no contract address to sanction. The burn operation removes tokens from the sender's account via the consensus layer. The mint operation creates tokens in the recipient's account via the consensus layer. There is no intermediate address, no pool, and no contract. The regulatory surface is fundamentally different. ### Supply Accounting In a pool model, the total token supply remains constant — tokens simply move from user accounts to the pool contract and back. In Specter's burn/mint model, the total supply temporarily decreases when tokens are Vanished and returns to its previous level when tokens are Summoned. This is by design: the commitment in the Merkle tree represents a claim on future minting, not a balance in a pool. The protocol ensures that total minting never exceeds total burning through the nullifier registry (each commitment can only be revealed once). ## The Generality Principle Both flows — general data and token — share the same cryptographic foundation: 1. Poseidon commitment inserted into a Merkle tree 2. Groth16 proof of preimage knowledge and Merkle membership 3. Nullifier to prevent replay The difference is what happens at the application layer: - **General data**: The verified `dataHash` is returned or emitted - **Tokens**: The `x/ghostmint` module burns on Vanish and mints on Summon This means every improvement to the proof system, Merkle tree management, or nullifier registry benefits all data types simultaneously. A credential commitment and a token commitment use the same tree, the same verifier, and the same nullifier set. The protocol is general; the applications are specific. --- # Data Types and Commitment Variants The Ghost Protocol supports multiple commitment structures, each optimized for a different class of data. All variants share the same underlying Poseidon hash function and the same Merkle tree infrastructure. They differ in the number and semantics of their inputs, which determines what properties the ZK proof can attest to. This page describes the three commitment variants — Open Ghost, Token, and Access Proof — and shows how different real-world data types map to them. ## Commitment Variant 1: Open Ghost (4-Input) The Open Ghost commitment is the general-purpose variant. It commits to an arbitrary data hash with no assumptions about the data's structure or semantics. $$ \text{commitment} = \text{Poseidon}_4(\text{secret},\; \text{nullifierSecret},\; \text{dataHash},\; \text{blinding}) $$ | Input | Description | |---|---| | `secret` | Random value known only to the committer. Required for proof generation. | | `nullifierSecret` | Random value used to derive the nullifier. Separate from `secret` to allow delegation of reveal rights without compromising the secret. | | `dataHash` | `Poseidon(data)` — the hash of the actual payload. Can be the hash of a credential, an image, an API key, an encryption key, or any other data. | | `blinding` | Random blinding factor that ensures two commitments to the same data produce different commitment values. Prevents deduplication attacks. | The `dataHash` field is the protocol's point of extensibility. Because `dataHash` is simply the Poseidon hash of arbitrary data, the Open Ghost commitment can encode anything. The protocol does not interpret `dataHash` — it only verifies that the prover knows a preimage of the commitment that includes this hash. ### Reveal Public Inputs When an Open Ghost commitment is revealed, the ZK proof exposes: | Public Input | Purpose | |---|---| | `root` | The Merkle tree root at the time of proof generation. Verifier checks this is a recognized root. | | `nullifierHash` | `Poseidon(nullifierSecret, leafIndex)` — recorded on-chain to prevent double-reveal. | | `dataHash` | The hash of the underlying data. Allows the verifier or receiving contract to act on the data type without seeing the plaintext. | | `accessTag` | An optional tag binding the proof to a specific context (session, contract, purpose). Prevents proof reuse across contexts. | ## Commitment Variant 2: Token (7-Input) The Token commitment extends the Open Ghost structure with fields specific to fungible and non-fungible token operations. It encodes the token identity, amount, and policy bindings directly in the commitment. $$ \text{commitment} = \text{Poseidon}_7(\text{secret},\; \text{nullifierSecret},\; \text{tokenId},\; \text{amount},\; \text{blinding},\; \text{policyId},\; \text{policyParamsHash}) $$ | Input | Description | |---|---| | `secret` | Random value known only to the committer. | | `nullifierSecret` | Random value for nullifier derivation. | | `tokenId` | Identifier for the token type (e.g., GHOST native token, an ERC-20 equivalent, or an NFT identifier). | | `amount` | The quantity of tokens committed. Encoded as a field element. For NFTs, this is 1. | | `blinding` | Random blinding factor. | | `policyId` | Identifier for the policy bound to this commitment (e.g., timelock, destination restriction). Zero if no policy. | | `policyParamsHash` | `Poseidon(policyParams)` — hash of the policy parameters. Allows the circuit to verify policy compliance without revealing the full policy. | The additional inputs allow the ZK circuit to verify token-specific properties: - The amount revealed matches the amount committed (no inflation) - The token ID matches (no type substitution) - The policy conditions are satisfied (timelock has elapsed, destination is approved, threshold is met) ### Reveal Public Inputs | Public Input | Purpose | |---|---| | `root` | Recognized Merkle root. | | `nullifierHash` | Prevents double-spending. | | `recipient` | The address that will receive the minted tokens. | | `amount` | The token quantity to mint. Verified in-circuit to match the committed amount. | ## Commitment Variant 3: Access Proof (4 Public Inputs) The Access Proof variant is designed for **non-consumptive** data access — proving knowledge of committed data without spending the commitment. Unlike the Open Ghost and Token variants, an Access Proof does not record a nullifier. The commitment remains in the Merkle tree and can be proven against repeatedly. This variant is used for persistent credentials, ongoing access rights, and any scenario where the user needs to prove the same fact multiple times. ### Public Inputs | Public Input | Purpose | |---|---| | `root` | Recognized Merkle root. | | `dataHash` | The data hash being proven. Allows the verifier to check what type of data the prover claims to hold. | | `sessionNonce` | A nonce binding the proof to a specific session or time window. Prevents proof replay across sessions while allowing repeated use within a session. | | `accessTag` | A tag binding the proof to a specific verifier, contract, or purpose. Prevents a proof generated for one context from being used in another. | ### Differences from Consumptive Reveals | Property | Open Ghost / Token (Consumptive) | Access Proof (Non-Consumptive) | |---|---|---| | Nullifier recorded | Yes — commitment can only be revealed once | No — commitment can be proven repeatedly | | Commitment consumed | Yes — after reveal, the commitment is "spent" | No — commitment remains active in the tree | | Use case | One-time redemption (tokens, bearer instruments) | Ongoing access (credentials, subscriptions, identity) | | Replay protection | Nullifier registry | Session nonce + access tag | | Supply effect (tokens) | Burns on commit, mints on reveal | No supply effect — proof only | ## Data Type Mapping The following table shows how different real-world data types map to the commitment variants, circuits, and retrieval modes. | Data Type | Commitment Variant | Commitment Inputs | Vault | Circuit | Retrieval Mode | |---|---|---|---|---|---| | **Credentials** | Open Ghost (4-input) | `secret, nullifierSecret, Poseidon(credential), blinding` | General Merkle tree | Open Ghost circuit or Access Proof circuit | Access Proof (non-consumptive) — prove credential repeatedly | | **Images** | Open Ghost (4-input) | `secret, nullifierSecret, Poseidon(imageHash), blinding` | General Merkle tree | Open Ghost circuit | Consumptive (provenance claim) or Access Proof (ongoing proof) | | **API Keys** | Open Ghost (4-input) | `secret, nullifierSecret, Poseidon(apiKey), blinding` | General Merkle tree | Open Ghost circuit | Consumptive — key rotation spends old commitment, creates new | | **Encryption Keys** | Open Ghost (4-input) | `secret, nullifierSecret, Poseidon(pubKey), blinding` | General Merkle tree | Access Proof circuit | Access Proof — prove key ownership without revealing key | | **Tokens (GHOST)** | Token (7-input) | `secret, nullifierSecret, tokenId, amount, blinding, policyId, policyParamsHash` | Token Merkle tree | Token redemption circuit | Consumptive — burn on Vanish, mint on Summon | | **Bearer Instruments** | Open Ghost (4-input) | `secret, nullifierSecret, Poseidon(instrument), blinding` | General Merkle tree | Open Ghost circuit | Consumptive — single-use redemption via nullifier | ### Vault Separation The protocol maintains separate Merkle trees (vaults) for different commitment types: - **General Merkle tree**: Stores Open Ghost commitments for all non-token data types. Shared across credentials, images, keys, and other generic data. - **Token Merkle tree**: Stores Token commitments. Separated to allow independent scaling and to isolate the token supply accounting from general data operations. Both trees use the same depth (20 levels, supporting approximately 1 million entries each), the same Poseidon hash function, and the same proof verification infrastructure. The separation is a logical choice for operational clarity, not a cryptographic necessity. ## Extensibility Adding support for a new data type requires: 1. **Define the data hash computation**: Specify how the new data type is hashed into a `dataHash` field element. For simple data, this is `Poseidon(data)`. For structured data, it may be `Poseidon(field1, field2, ..., fieldN)`. 2. **Choose a commitment variant**: Open Ghost (4-input) for most data types. Token (7-input) only if the data has an amount, identity, and policy binding. 3. **Choose a retrieval mode**: Consumptive (one-time reveal with nullifier) or non-consumptive (repeated access proofs with session nonces). 4. **Implement application logic**: Write the smart contract or module that interprets the revealed `dataHash` and takes the appropriate action. No changes to the ZK circuits are required for new data types that fit within existing commitment structures. The Poseidon hash accepts arbitrary field elements, and the Merkle tree stores arbitrary commitments. The protocol is extensible at the application layer without modifying the cryptographic layer. ### Example: Adding a New Data Type Suppose an application wants to commit **signed documents** to the Ghost Protocol. 1. **Data hash**: `dataHash = Poseidon(documentHash, signerPubKeyHash, timestamp)` 2. **Commitment variant**: Open Ghost (4-input) 3. **Retrieval mode**: Access Proof (non-consumptive) — the signer wants to prove the document exists and was signed by them, repeatedly, without consuming the commitment. 4. **Application logic**: A verifier contract checks the revealed `dataHash` against a registry of accepted document formats and signer public keys. The ZK circuit does not change. The Merkle tree does not change. Only the application-layer interpretation of `dataHash` is new. This is the power of a data-agnostic commitment protocol. --- # Architecture Overview Specter is a data privacy protocol built as a sovereign blockchain. Its architecture is organized into three distinct layers: **Consensus**, **Execution**, and **Data Protocol**. Each layer has a well-defined responsibility, and together they provide a complete system for committing, proving, and revealing arbitrary data — without exposing that data to anyone except the intended recipient. The protocol layer is **data-agnostic by design**. While the GHOST token demonstrates how private value transfer works on Specter, the same commit-reveal-prove architecture handles credentials, images, API keys, encrypted documents, and any other data that needs to be stored or transmitted privately on-chain. ## Three-Layer Stack ### Layer 1: Consensus (CometBFT) The base layer is **CometBFT v0.38.17**, a Byzantine Fault Tolerant consensus engine. It provides: - **Immediate finality** — blocks are final once committed. No confirmations needed. No reorganizations possible. - **BFT safety** — the network remains correct as long as more than 2/3 of validators are honest. A block requires agreement from at least 2/3+ of the validator set. - **Proof-of-Stake** — validators stake GHOST tokens and are subject to slashing for misbehavior (double-signing, extended downtime). - **~2.5-second block times** — tuned for interactive applications while maintaining consensus safety. ### Layer 2: Execution (Cosmos SDK + EVM) The execution layer combines the Cosmos SDK application framework with a full Ethereum Virtual Machine: - **Cosmos SDK v0.53.2** provides the module system, transaction routing, account model, governance, staking, and inter-blockchain communication (IBC). - **cosmos/evm v1.0.0-rc2** embeds a complete EVM inside the Cosmos application, enabling Solidity smart contracts to execute with full Ethereum compatibility. - **IBC (ibc-go v10)** enables cross-chain communication with any IBC-compatible blockchain in the Cosmos ecosystem. This dual execution model means Specter supports both native Cosmos SDK transactions (staking, governance, IBC transfers) and arbitrary EVM smart contract calls (Solidity, Vyper, or any EVM-compatible language). ### Layer 3: Data Protocol (Ghost Protocol Contracts + ZK Circuits) The application layer — the **Ghost Protocol** — consists of Solidity smart contracts deployed to the EVM and off-chain ZK circuits compiled with Circom. This layer implements: - **Commitment schemes** using Poseidon hashing for both generic data and token-specific commitments - **Merkle tree management** for efficient membership proofs over committed data - **Nullifier tracking** for anti-replay protection without linking reveals to commits - **Groth16 ZK proof verification** for on-chain validation of off-chain proofs - **Policy enforcement** for programmable constraints on when and how data can be revealed - **Native token bridging** via the ghostmint precompile for minting/burning GHOST through the privacy system This layer is intentionally data-agnostic. The 4-input commitment variant (`Poseidon4(secret, nullifierSecret, dataHash, blinding)`) accepts any data hash — it does not care whether the underlying data is a credential, an image, an API key, or a message. The 7-input variant adds token-specific fields for the GHOST token use case. ## Architecture Diagram ## Key Modules ### Cosmos SDK Modules | Module | Role | |--------|------| | `x/ghostmint` | Custom module bridging EVM contracts to Cosmos `x/bank` for native GHOST minting/burning | | `x/bank` | Native token accounting, supply tracking, send/receive | | `x/staking` | Validator registration, delegation, reward distribution | | `x/gov` | On-chain governance proposals and voting | | `cosmos/evm` (vm) | Full EVM execution engine within Cosmos | | `cosmos/evm` (feemarket) | EIP-1559 dynamic fee market for gas pricing | | `cosmos/evm` (erc20) | ERC-20 token integration between Cosmos and EVM | | `ibc` (ibc-go v10) | Inter-Blockchain Communication for cross-chain data and asset transfer | ### Smart Contracts (EVM Layer) | Contract | Purpose | |----------|---------| | `CommitRevealVault` | Orchestrates token privacy: commit (burn), reveal (mint), partial withdrawals | | `OpenGhostVault` | Orchestrates generic data privacy: commit any data hash, one-time reveal | | `PersistentKeyVault` | Reusable access to committed data via access proofs (no nullifier spent) | | `CommitmentTree` | Stores token commitment hashes, manages Merkle roots | | `OpenCommitmentTree` | Stores data commitment hashes, separate tree from tokens | | `NullifierRegistry` | Tracks spent nullifiers for token reveals | | `OpenNullifierRegistry` | Tracks spent nullifiers for data reveals and key revocations | | `NativeAssetHandler` | Authorized caller for ghostmint precompile; handles GHOST burn/mint | | `ProofVerifier` | On-chain Groth16 proof verification (generated by snarkjs) | | `AssetGuard` | Whitelist of authorized GhostERC20 tokens | | `TimelockExpiry` | Policy: time-window restrictions on reveals | | `DestinationRestriction` | Policy: recipient allowlists for reveals | | `ThresholdWitness` | Policy: M-of-N signature requirements for reveals | ### ZK Circuits (Off-Chain) | Circuit | Purpose | |---------|---------| | `redemption.circom` | Proves valid token redemption: commitment preimage knowledge, Merkle membership, nullifier correctness, amount conservation, policy binding | | `accessProof.circom` | Proves valid persistent key access: commitment preimage knowledge, Merkle membership, access tag derivation (no nullifier spent) | ## Design Principles **Data-first architecture.** The protocol treats all data uniformly. Token privacy (GHOST) is one application built on the same cryptographic primitives that handle credential privacy, document privacy, or any other data type. The `dataHash` field in a 4-input commitment accepts any 254-bit value — the protocol imposes no schema or interpretation on the data. **On-chain verification, off-chain computation.** All expensive operations — Poseidon hashing of multi-input commitments, Merkle proof construction, ZK proof generation — happen off-chain. The blockchain only verifies the compact Groth16 proof and enforces the nullifier/policy rules. **Separation of concerns.** Consensus does not depend on privacy. Privacy does not depend on a specific token. The EVM layer does not depend on the data protocol. Each layer can be upgraded, extended, or replaced without affecting the others. **Supply conservation.** For the GHOST token use case, the ghostmint module enforces a strict supply invariant: every burn during commit must have a corresponding mint during reveal. This is checked every block at the consensus level, independent of the smart contract layer. --- # Cosmos SDK Foundation Specter is built on **Cosmos SDK v0.53.2**, the application framework for building sovereign, interoperable blockchains. The Cosmos SDK provides the module system, transaction handling, account model, and state machine that form the backbone of the Specter chain. Combined with CometBFT consensus and a full EVM, it gives Specter the flexibility to support both native Cosmos operations and Ethereum-compatible smart contracts. ## Chain Identity | Parameter | Value | |-----------|-------| | **Cosmos SDK Version** | v0.53.2 | | **CometBFT Version** | v0.38.17 | | **Bech32 Prefix** | `specter` (accounts), `spectervaloper` (validators), `spectervalcons` (consensus) | | **Cosmos Chain ID** | `specter-testnet-1` | | **Native Denomination** | `aghost` (1 GHOST = 10^18 aghost) | | **Binary Name** | `specterd` | The bech32 prefix `specter` is used for all native Cosmos addresses. Validator operator addresses use `spectervaloper` and consensus addresses use `spectervalcons`. On the EVM side, standard `0x`-prefixed Ethereum addresses are used, with automatic conversion between the two formats. ## CometBFT Consensus CometBFT v0.38.17 is the consensus engine underlying the Specter chain. It implements a BFT (Byzantine Fault Tolerant) consensus protocol derived from Tendermint, providing strong safety and liveness guarantees. ### Consensus Properties **Two-thirds-plus agreement.** Every block requires pre-vote and pre-commit messages from validators representing more than 2/3 of the total staked weight. A block that receives 2/3+ pre-commits is considered committed and final. **Immediate finality.** Unlike probabilistic finality chains (Bitcoin, Ethereum PoW), CometBFT blocks are final the moment they are committed. There are no confirmations to wait for, no reorganizations to worry about, and no uncle blocks. A transaction included in a committed block will never be reverted. **Byzantine safety.** The network tolerates up to 1/3 of validators being Byzantine (malicious, offline, or faulty) without compromising safety. If more than 1/3 of validators go offline, the chain halts (preserving safety) rather than producing potentially conflicting blocks. **Block production.** Specter is tuned for ~2.5-second block times, balancing interactive application responsiveness with consensus round-trip overhead. The mempool supports up to 20,000 pending transactions. ## Module Architecture The Cosmos SDK organizes blockchain functionality into discrete modules, each responsible for a specific domain. Specter uses the standard SDK modules plus a custom `x/ghostmint` module that bridges the privacy protocol to native token operations. ### Core Modules | Module | Source | Role | |--------|--------|------| | `x/auth` | Cosmos SDK | Account management, transaction authentication, signature verification | | `x/bank` | Cosmos SDK | Native token transfers, supply tracking, multi-denomination balances | | `x/staking` | Cosmos SDK | Validator registration, delegation, unbonding, reward distribution | | `x/slashing` | Cosmos SDK | Validator punishment for double-signing and extended downtime | | `x/gov` | Cosmos SDK | On-chain governance: proposals, deposits, voting, execution | | `x/distribution` | Cosmos SDK | Block reward and fee distribution to validators and delegators | | `x/mint` | Cosmos SDK | Inflation control (configured for zero inflation on Specter) | | `x/evidence` | Cosmos SDK | Submission and handling of Byzantine validator evidence | | `x/feegrant` | Cosmos SDK | Fee allowances enabling one account to pay fees for another | | `x/upgrade` | Cosmos SDK | Coordinated chain upgrades without hard forks | ### EVM Modules | Module | Source | Role | |--------|--------|------| | `evm` (vm) | cosmos/evm v1.0.0-rc2 | Full Ethereum Virtual Machine execution within Cosmos | | `evm` (feemarket) | cosmos/evm v1.0.0-rc2 | EIP-1559 dynamic base fee and priority fee market | | `evm` (erc20) | cosmos/evm v1.0.0-rc2 | Bidirectional conversion between Cosmos native tokens and ERC-20 | ### Custom Module | Module | Source | Role | |--------|--------|------| | `x/ghostmint` | Specter | Bridges EVM smart contracts to `x/bank` for native GHOST minting and burning. Provides the ghostmint precompile at `0x0808`. Tracks cumulative mint/burn totals. Enforces supply invariant every block. | ### IBC Module | Module | Source | Role | |--------|--------|------| | `ibc` | ibc-go v10 | Inter-Blockchain Communication protocol for cross-chain transfers and data | IBC enables Specter to communicate with any IBC-compatible chain in the Cosmos ecosystem. This includes transferring tokens to/from other chains, cross-chain governance messages, and custom IBC packet types. The ibc-go v10 dependency provides the latest IBC protocol features including multi-hop channels. ## The x/ghostmint Module The `x/ghostmint` module is Specter's custom Cosmos SDK module. Its primary purpose is to enable EVM smart contracts (specifically the NativeAssetHandler) to mint and burn native GHOST tokens by calling into the Cosmos `x/bank` module through an EVM precompile. ### Why a Custom Module? In a standard Cosmos EVM chain, EVM contracts cannot directly interact with native Cosmos module operations like minting or burning tokens. The `x/bank` module manages native token supply, but it is only accessible through Cosmos SDK message handlers — not through EVM opcodes. The `x/ghostmint` module solves this by: 1. **Registering an EVM precompile** at address `0x0808` that EVM contracts can call like any other contract 2. **Routing those calls** through a keeper that has direct access to the `x/bank` module's mint and burn functions 3. **Enforcing authorization** so only designated NativeAssetHandler contracts can trigger minting/burning 4. **Tracking supply** with cumulative `totalMinted` and `totalBurned` counters stored in the module's KVStore 5. **Checking invariants** every block to detect unexpected supply changes ### Supply Invariant The ghostmint module runs a supply invariant check at the end of every block. It compares the current total supply of GHOST (from `x/bank`) against the previous block's supply. If the supply has increased unexpectedly (indicating inflation outside of the ghostmint mint/burn cycle), the module logs an error. This invariant operates at the consensus level, independent of the EVM smart contract layer, providing a defense-in-depth check against bugs in the contract system. ### Hard Cap A hard maximum mint supply of **1 billion GHOST** (10^27 aghost) is enforced at the module level. Even if a bug in the vault contracts allowed unbounded minting calls, the ghostmint keeper would reject any mint that would push the cumulative total above this cap. ## Validator Mechanics Specter uses Proof-of-Stake consensus with GHOST as the staking token. ### Staking - Validators must stake GHOST tokens to participate in consensus - Delegators can delegate their GHOST to validators and earn a share of block rewards - Unbonding period applies when un-staking (tokens are locked during this period) - Validator voting power is proportional to their total stake (self-stake + delegations) ### Slashing Validators face economic penalties for two categories of misbehavior: | Offense | Trigger | Consequence | |---------|---------|-------------| | **Double-signing** | Validator signs two different blocks at the same height | Severe slash of staked tokens, permanent jailing | | **Downtime** | Validator misses a threshold of consecutive blocks | Moderate slash, temporary jailing (can unjail) | Slashing is enforced automatically by the `x/slashing` module based on evidence submitted through CometBFT. Double-signing evidence can be submitted by any network participant. ### Governance The `x/gov` module enables on-chain governance for protocol upgrades and parameter changes. Governance proposals follow this lifecycle: Governance can modify chain parameters, trigger software upgrades, spend community pool funds, and execute arbitrary module-level operations — including updating the authorized callers for the ghostmint precompile. ## Transaction Flow A transaction on Specter follows this path through the stack: Both native Cosmos transactions and Ethereum-formatted transactions are supported. Ethereum JSON-RPC endpoints are available for standard Ethereum tooling (MetaMask, Foundry, ethers.js), while Cosmos endpoints support Cosmos-native clients and IBC relayers. --- # EVM Compatibility Specter embeds a full Ethereum Virtual Machine within its Cosmos SDK application using the **cosmos/evm module v1.0.0-rc2**. This means any smart contract that runs on Ethereum can run on Specter — with the same Solidity code, the same tooling, and the same developer experience. The EVM is not a simulation or a compatibility shim; it is a complete execution environment that processes the same opcodes, supports the same precompiles, and follows the same gas accounting as mainnet Ethereum. ## EVM Configuration | Parameter | Value | |-----------|-------| | **EVM Module** | cosmos/evm v1.0.0-rc2 | | **EVM Chain ID (Testnet)** | 5445 | | **Go-Ethereum Fork** | Specter-Foundation/go-ethereum v1.15.11-specter-2 | | **Solidity Version** | ^0.8.20 | | **Max Code Size** | 64 KB (increased from Ethereum's 24 KB default) | | **Max Transaction Gas** | 25,000,000 | | **Fee Model** | EIP-1559 (dynamic base fee + priority fee) | | **Ethereum Fork Level** | Cancun | ## Custom go-ethereum Fork Specter uses a patched version of go-ethereum (`Specter-Foundation/go-ethereum v1.15.11-specter-2`) that includes Cosmos-specific opcode modifications required by the cosmos/evm module. Beyond the Cosmos integration patches, the fork increases the **MaxCodeSize from 24 KB to 64 KB**. ### Why 64 KB? The Poseidon hash function — which is fundamental to the entire data protocol — requires an on-chain library (`PoseidonT3`) that compiles to approximately **55 KB of deployed bytecode**. Ethereum's default limit of 24,576 bytes (24 KB, introduced in [EIP-170](https://eips.ethereum.org/EIPS/eip-170)) would reject this deployment. Poseidon is not a typical smart contract — it is a cryptographic primitive that performs BN254 scalar field arithmetic with a specific round structure optimized for ZK circuits. The bytecode is large because it encodes the full round constant table and S-box transformations in Solidity. There is no way to meaningfully reduce its size without compromising the hash function's security properties. Increasing MaxCodeSize to 64 KB is a targeted change that affects only deployment validation. It does not alter gas costs, execution semantics, or any other EVM behavior. | Library | Deployed Size | Standard Limit | Specter Limit | |---------|--------------|----------------|---------------| | PoseidonT3 (2-input Poseidon) | ~55 KB | 24 KB | 64 KB | | Standard Solidity contracts | < 24 KB | 24 KB | 64 KB | ## Solidity Toolchain Specter's smart contracts are developed and deployed using standard Ethereum tooling: | Tool | Purpose | |------|---------| | **Solidity ^0.8.20** | Contract language (overflow checks, custom errors, immutable variables) | | **Foundry** | Development framework (forge compile, forge test, forge script, forge create) | | **OpenZeppelin Contracts** | Audited base contracts (access control, upgradability patterns, utilities) | No custom compiler or language extensions are required. Any Solidity developer can read, modify, and deploy Specter contracts using their existing toolchain. ## EIP-1559 Fee Market The `feemarket` module implements Ethereum's EIP-1559 dynamic fee mechanism: **Base fee** adjusts dynamically based on block gas utilization. When blocks are more than 50% full, the base fee increases; when they are less than 50% full, it decreases. The base fee portion of transaction fees is burned (removed from circulating supply). **Priority fee (tip)** is an optional additional fee that users include to incentivize validators to prioritize their transaction. The priority fee goes directly to the block proposer. This mechanism provides predictable fee estimation and prevents fee spikes from sustained congestion. Users can set a `maxFeePerGas` and `maxPriorityFeePerGas` using standard Ethereum transaction types (EIP-1559 Type 2 transactions). ## Precompiles Specter supports all standard Ethereum precompiled contracts at the **Cancun fork level**, plus a set of Cosmos-specific precompiles from the cosmos/evm module, plus the custom ghostmint precompile. ### Standard Ethereum Precompiles | Address | Name | Purpose | |---------|------|---------| | `0x01` | ecRecover | ECDSA public key recovery | | `0x02` | SHA-256 | SHA-256 hash function | | `0x03` | RIPEMD-160 | RIPEMD-160 hash function | | `0x04` | Identity | Data copy (returns input as output) | | `0x05` | ModExp | Modular exponentiation (big integer) | | `0x06` | ecAdd | BN254 elliptic curve point addition | | `0x07` | ecMul | BN254 elliptic curve scalar multiplication | | `0x08` | ecPairing | BN254 elliptic curve pairing check | | `0x09` | Blake2f | BLAKE2b compression function | | `0x0a` | Point Evaluation | KZG point evaluation (EIP-4844) | The BN254 precompiles (`0x06`, `0x07`, `0x08`) are particularly important for Specter because Groth16 proof verification requires elliptic curve pairing operations on the BN254 curve. These precompiles make on-chain ZK proof verification gas-efficient. ### Cosmos EVM Precompiles The cosmos/evm module provides precompiles in the `0x0800–0x0807` range that allow EVM contracts to interact with Cosmos SDK modules: | Address Range | Purpose | |---------------|---------| | `0x0800–0x0807` | Cosmos SDK module interactions (staking, distribution, governance, IBC from EVM) | ### Ghostmint Precompile | Address | Name | Purpose | |---------|------|---------| | `0x0808` | ghostmint | Native GHOST token minting and burning from EVM contracts | The ghostmint precompile at `0x0808` is Specter's custom addition. It bridges EVM contract calls to the Cosmos `x/bank` module, enabling the NativeAssetHandler contract to mint GHOST tokens during reveals and burn them during commits. See the [Ghostmint Precompile](./ghostmint-precompile.md) page for full details. ## EVM-Cosmos Address Mapping Every account on Specter has both a Cosmos bech32 address and an Ethereum hex address. They represent the same underlying account: | Format | Example | Used By | |--------|---------|---------| | Cosmos bech32 | `specter19c95kr4qjnfsqldwsgen007zuv2vs3gajsasdz` | Cosmos SDK transactions, staking, governance | | Ethereum hex | `0x2E0B4b0Ea094d3007dae823337BfC2e314C8451D` | EVM transactions, MetaMask, smart contract calls | The conversion is deterministic — the same secp256k1 key pair produces both addresses. Users can interact with the chain using either format, and balances are shared between the Cosmos and EVM representations. ## Developer Experience Deploying and interacting with contracts on Specter works identically to Ethereum: **JSON-RPC compatibility.** Specter exposes standard Ethereum JSON-RPC endpoints (`eth_sendTransaction`, `eth_call`, `eth_getBalance`, `eth_estimateGas`, etc.). Any Ethereum client library — ethers.js, viem, web3.js — works out of the box by pointing at the Specter RPC URL with chain ID `5445`. **MetaMask and wallet support.** Users can add Specter as a custom network in MetaMask or any EVM-compatible wallet using the chain ID and RPC URL. Token balances, transaction history, and contract interactions work as expected. **Contract verification.** Contracts deployed on Specter can be verified using standard Solidity source verification tools. The same compiler version and optimization settings used on Ethereum apply on Specter. --- # Ghostmint Precompile The ghostmint precompile is the bridge between the EVM smart contract layer and the Cosmos SDK native token system. It lives at a fixed address (`0x0808`) and allows authorized EVM contracts to mint and burn native GHOST tokens by calling into the Cosmos `x/bank` module through the `x/ghostmint` keeper. This precompile enables the **GHOST token use case** of the broader Specter data protocol. When a user commits GHOST tokens into the privacy system, the tokens are burned via this precompile. When they reveal (withdraw), the tokens are minted back via this precompile. The net effect is supply-neutral: every token that enters the privacy system exits it, with the privacy protocol acting as an intermediary that breaks the on-chain link between sender and receiver. ## Precompile Address ``` 0x0000000000000000000000000000000000000808 ``` The address `0x0808` was chosen to sit immediately after the cosmos/evm module's reserved precompile range (`0x0800–0x0807`). This avoids conflicts with both standard Ethereum precompiles (`0x01–0x0a`) and the Cosmos EVM precompiles. ## ABI The precompile exposes four functions and two events: ### Functions | Function | Signature | Mutability | Gas Cost | |----------|-----------|------------|----------| | `mintNativeTo` | `mintNativeTo(address recipient, uint256 amount) returns (bool success)` | nonpayable | 50,000 | | `burnNativeFrom` | `burnNativeFrom(address from, uint256 amount) returns (bool success)` | payable | 50,000 | | `totalMinted` | `totalMinted() returns (uint256 total)` | view | 200 | | `totalBurned` | `totalBurned() returns (uint256 total)` | view | 200 | ### Events | Event | Signature | |-------|-----------| | `NativeMinted` | `NativeMinted(address indexed recipient, uint256 amount)` | | `NativeBurned` | `NativeBurned(address indexed from, uint256 amount)` | ### Function Details **`mintNativeTo(address recipient, uint256 amount)`** Mints `amount` of native GHOST (in aghost, the smallest unit at 18 decimals) and sends it to `recipient`. The mint operation goes through the `x/bank` module: tokens are minted to the ghostmint module account, then sent to the recipient's account. Returns `true` on success, reverts on failure. Emits `NativeMinted`. **`burnNativeFrom(address from, uint256 amount)`** Burns `amount` of native GHOST from the `from` address. The burn operation sends tokens from the `from` address to the ghostmint module account, then burns them via `x/bank`. Returns `true` on success, reverts on failure. Emits `NativeBurned`. The `payable` modifier is required because the caller sends native value with the call to fund the burn. **`totalMinted()`** Returns the cumulative total of all GHOST tokens ever minted through this precompile, stored in the module's KVStore. This is a monotonically increasing counter — it never decreases. **`totalBurned()`** Returns the cumulative total of all GHOST tokens ever burned through this precompile, stored in the module's KVStore. Also monotonically increasing. The difference `totalMinted() - totalBurned()` represents the net tokens currently "in flight" within the privacy system (committed but not yet revealed). ## Gas Costs | Operation | Gas | Rationale | |-----------|-----|-----------| | `mintNativeTo` | 50,000 | Involves two `x/bank` operations: mint to module + send to recipient | | `burnNativeFrom` | 50,000 | Involves two `x/bank` operations: send to module + burn from module | | `totalMinted` | 200 | Single KVStore read | | `totalBurned` | 200 | Single KVStore read | The 50,000 gas cost for mint/burn operations reflects the underlying Cosmos SDK state changes (module account balance updates, supply tracking, event emission). These costs are fixed regardless of the amount being minted or burned. ## Authorization The precompile enforces strict caller authorization. Only addresses registered as **authorized callers** in the precompile's configuration can invoke `mintNativeTo` or `burnNativeFrom`. Unauthorized calls revert immediately. In practice, the authorized callers are NativeAssetHandler contract instances — one per vault that handles native GHOST: | Authorized Caller | Role | |-------------------|------| | NativeAssetHandler (core) | Handles mint/burn for the main BatchCommitRevealVault | | NativeAssetHandler (forking) | Handles mint/burn for the ForkingVault | The authorized caller list is configured at chain initialization and can be updated through **governance proposals**. This means adding a new vault that can mint/burn GHOST requires an on-chain governance vote — no single party can unilaterally authorize a new minter. ### Authorization Flow ## End-to-End Flow The ghostmint precompile sits at a specific point in the commit/reveal pipeline. Here is the full sequence for a GHOST token commit (burn) and reveal (mint): ### Commit (Burn) Flow ### Reveal (Mint) Flow ## Supply Invariant The ghostmint module enforces a supply invariant that runs **every block** at the consensus level, independent of EVM execution: 1. At the end of each block, the module reads the current total GHOST supply from `x/bank` 2. It compares this against the `LastTotalSupply` stored from the previous block 3. If the current supply exceeds the previous supply, it logs an error (since Specter is configured for zero inflation — supply should only change through balanced ghostmint mint/burn operations) 4. It stores the current supply as `LastTotalSupply` for the next block's comparison Additionally, a **hard cap of 1 billion GHOST** (10^27 aghost) is enforced at the keeper level. Any mint operation that would push cumulative `totalMinted` above this cap is rejected, regardless of what the calling contract requests. This dual-layer protection — per-operation hard cap plus per-block invariant checking — ensures that the GHOST token supply remains auditable and predictable, even if a bug exists in the smart contract layer. ## Relationship to the Data Protocol The ghostmint precompile is specific to the **GHOST token use case** of the Specter data protocol. It is not involved in generic data commitments. | Operation | Uses ghostmint? | Description | |-----------|----------------|-------------| | Token commit (CommitRevealVault) | Yes — burns GHOST | Tokens are destroyed when entering the privacy system | | Token reveal (CommitRevealVault) | Yes — mints GHOST | Tokens are created when exiting the privacy system | | Data commit (OpenGhostVault) | No | Only stores a commitment hash; no tokens burned | | Data reveal (OpenGhostVault) | No | Only spends a nullifier; no tokens minted | | Persistent key access (PersistentKeyVault) | No | Access proofs do not involve token operations | The data protocol's core primitives — Poseidon hashing, Merkle trees, nullifiers, ZK proofs — are entirely independent of the ghostmint precompile. The precompile is the mechanism by which one specific data type (GHOST token balances) enters and exits the privacy system. --- # Poseidon Hash Function Poseidon is the core hash function used throughout the Specter data protocol. It was chosen because it is an **algebraic hash function** designed specifically for efficient computation inside zero-knowledge circuits. Every commitment, nullifier, token identifier, access tag, and Merkle tree node in the protocol is computed using a variant of Poseidon. ## Why Poseidon? Traditional hash functions like SHA-256 and Keccak-256 are designed for hardware and software efficiency on standard processors. They use bitwise operations (XOR, AND, rotations, shifts) that are cheap on CPUs but expensive to represent in the arithmetic circuits used by ZK proof systems. ZK circuits operate over finite fields — they represent computation as systems of polynomial equations over large prime numbers. Each bitwise operation in SHA-256 or Keccak must be decomposed into field arithmetic, which multiplies the constraint count dramatically. Poseidon takes the opposite approach. It operates natively on finite field elements using only field multiplications and additions. Its round function is a simple power map (S-box: x^5) followed by a linear mixing layer — operations that translate directly into a small number of R1CS constraints. | Hash Function | Approximate R1CS Constraints | Circuit Efficiency | |---------------|-----------------------------|--------------------| | SHA-256 | ~25,000 per hash | Baseline | | Keccak-256 | ~150,000 per hash | 6x worse than SHA-256 | | **Poseidon (t=3)** | **~3,000 per hash** | **~8x better than SHA-256** | This ~8x improvement in constraint count translates directly to faster proof generation, smaller proof sizes, and lower verification gas costs. For a protocol that computes multiple hashes per proof (commitment hash, nullifier hash, 20 Merkle tree node hashes), the cumulative savings are substantial. ## BN254 Scalar Field All Poseidon computations in Specter operate over the **BN254 scalar field**: ``` p = 21888242871839275222246405745257275088548364400416034343698204186575808495617 ``` This is a ~254-bit prime. Every input, output, and intermediate value in a Poseidon hash must be a valid element of this field — meaning it must be a non-negative integer less than `p`. The BN254 curve was chosen because it is the curve supported by Ethereum's `ecPairing` precompile (`0x08`), which is required for on-chain Groth16 proof verification. Using the same field for both hashing and proving avoids costly field conversions. When values from other domains (e.g., Keccak-256 hashes, which produce 256-bit outputs) need to enter the Poseidon field, they are reduced modulo `p`: ``` fieldValue = keccak256(data) % p ``` This reduction is performed identically on-chain (in Solidity) and off-chain (in circuits and client code) to ensure consistency. ## Poseidon Variants Specter uses three Poseidon configurations, differing in the number of inputs they accept. The naming convention uses "T" (for "t-value"), which equals the number of inputs plus one (the internal state width). ### PoseidonT3 (2 inputs) ``` output = PoseidonT3(input0, input1) ``` The workhorse of the protocol. Used for all binary operations: | Use | Input 0 | Input 1 | Output | |-----|---------|---------|--------| | Merkle tree nodes | Left child hash | Right child hash | Parent node hash | | Nullifier (inner) | nullifierSecret | commitment | Inner hash | | Nullifier (outer) | Inner hash | leafIndex | Final nullifier | | Token ID | tokenAddress | 0 | tokenId | | Access tag | nullifierSecret | sessionNonce | accessTag | **On-chain deployment.** PoseidonT3 is the only Poseidon variant deployed on-chain, as a Solidity library at approximately **55 KB** of bytecode. This large size is why Specter increases the EVM's MaxCodeSize to 64 KB. The on-chain library costs approximately **30,000 gas per hash** invocation and is used by the CommitmentTree for computing Merkle tree nodes during root verification. ### PoseidonT5 (4 inputs) ``` output = PoseidonT5(input0, input1, input2, input3) ``` Used for **generic data commitments** — the data-agnostic commitment format: | Use | Input 0 | Input 1 | Input 2 | Input 3 | Output | |-----|---------|---------|---------|---------|--------| | Data commitment | secret | nullifierSecret | dataHash | blinding | commitment | PoseidonT5 is computed **off-chain only** (in the browser, in the client application, or inside ZK circuits). It is never executed on-chain because: - On-chain deployment would require ~179,000+ gas per hash - The smart contracts only need to verify ZK proofs that attest to the commitment's correctness — they never recompute the commitment themselves ### PoseidonT8 (7 inputs) ``` output = PoseidonT8(input0, input1, input2, input3, input4, input5, input6) ``` Used for **token commitments** — the full commitment format with token and policy binding: | Use | Input 0 | Input 1 | Input 2 | Input 3 | Input 4 | Input 5 | Input 6 | Output | |-----|---------|---------|---------|---------|---------|---------|---------|--------| | Token commitment | secret | nullifierSecret | tokenId | amount | blinding | policyId | policyParamsHash | commitment | Like PoseidonT5, PoseidonT8 is computed **off-chain only**. The on-chain contracts receive only the final commitment hash and verify it through ZK proofs. ## All Hashes in the Protocol The following table catalogs every Poseidon hash computation in the Specter protocol: | Hash Purpose | Variant | Inputs | Computed Where | |-------------|---------|--------|----------------| | **Token commitment** | PoseidonT8 (7 inputs) | secret, nullifierSecret, tokenId, amount, blinding, policyId, policyParamsHash | Off-chain (client + circuit) | | **Data commitment** | PoseidonT5 (4 inputs) | secret, nullifierSecret, dataHash, blinding | Off-chain (client + circuit) | | **Nullifier (inner hash)** | PoseidonT3 (2 inputs) | nullifierSecret, commitment | Off-chain (circuit) | | **Nullifier (outer hash)** | PoseidonT3 (2 inputs) | innerHash, leafIndex | Off-chain (circuit) | | **Token ID** | PoseidonT3 (2 inputs) | tokenAddress, 0 | Off-chain (client) | | **Access tag** | PoseidonT3 (2 inputs) | nullifierSecret, sessionNonce | Off-chain (circuit) | | **Merkle tree nodes** | PoseidonT3 (2 inputs) | leftChild, rightChild | Off-chain (indexer) + On-chain (tree contract) | | **Change commitment** | PoseidonT8 (7 inputs) | secret, nullifierSecret, tokenId, changeAmount, newBlinding, policyId, policyParamsHash | Off-chain (circuit) | ## On-Chain vs. Off-Chain The design principle is clear: **minimize on-chain Poseidon computation**. Only PoseidonT3 is deployed on-chain, and only for Merkle tree operations that the smart contracts must perform directly (root tracking, proof verification during root updates). All multi-input hashes (4-input and 7-input commitments) are computed off-chain and verified on-chain through ZK proofs — the chain never sees the hash inputs, only the final hash output and a proof that it was correctly computed. ## Security Properties Poseidon's security has been analyzed extensively in the academic literature. Key properties relevant to Specter: **Collision resistance.** It is computationally infeasible to find two distinct inputs that produce the same Poseidon hash. This ensures that each commitment is unique and that Merkle tree nodes cannot be forged. **Preimage resistance.** Given a Poseidon hash output, it is computationally infeasible to recover the inputs. This ensures that observing a commitment hash on-chain reveals nothing about the underlying data (secret, nullifierSecret, dataHash, blinding, etc.). **Algebraic soundness.** Poseidon's round structure (full rounds + partial rounds with the x^5 S-box over a large prime field) provides security margins against algebraic attacks including Grobner basis attacks, interpolation attacks, and differential/linear cryptanalysis. The number of rounds is chosen to provide a security margin of at least 2x above the theoretical minimum. **Field-native operations.** Because Poseidon operates directly on BN254 field elements, there are no bit-decomposition or domain-conversion steps that could introduce subtle vulnerabilities. Every operation is a native field multiplication or addition. --- # Commitment Structure A commitment in the Specter protocol is a single Poseidon hash that binds a user to a piece of data without revealing what that data is. The commitment hash is stored on-chain (in a Merkle tree); the inputs to the hash — the **phantom key** — are kept privately by the user. Later, the user can prove knowledge of the inputs via a ZK proof without ever disclosing them. Specter defines two commitment variants: a **generic data commitment** for arbitrary data (credentials, documents, API keys, encrypted secrets) and a **token commitment** for GHOST token amounts. Both variants use the same cryptographic structure — they differ only in the number and semantics of their inputs. ## Generic Data Commitment (4-Input) The data commitment is the protocol's most general-purpose primitive. It accepts any data hash and makes no assumptions about what the data represents. ``` commitment = Poseidon4(secret, nullifierSecret, dataHash, blinding) ``` ### Field Descriptions | Field | Size | Purpose | |-------|------|---------| | `secret` | 254-bit random | **Ownership proof.** Only the holder of this value can generate a valid ZK proof for this commitment. Acts as the "private key" for the commitment. | | `nullifierSecret` | 254-bit random | **Nullifier derivation.** Used to compute the deterministic nullifier that prevents double-reveal. Separated from `secret` so that nullifier computation does not leak ownership information. | | `dataHash` | 254-bit | **Data binding.** The Poseidon or Keccak hash of the actual data being committed. Intentionally generic — this can be the hash of an encrypted credential, an image, an API key, a message, or any other data. The protocol imposes no schema. | | `blinding` | 254-bit random | **Brute-force protection.** Prevents an attacker who knows (or can guess) the `dataHash` from testing all possible commitments to find a match. Even if the data is known, the random blinding factor makes the commitment unpredictable. | ### Why Four Inputs? Each input serves a distinct security purpose that cannot be combined: - `secret` and `nullifierSecret` must be separate because the nullifier computation is public (the nullifier is published on-chain during reveal), while the secret must remain permanently private. If the same value were used for both, publishing the nullifier would leak information about the ownership secret. - `dataHash` is the payload — without it, the commitment would not be bound to any data. - `blinding` is necessary because `dataHash` alone may have low entropy. If the committed data is one of a small set of known values (e.g., a credential type), an attacker could hash each candidate and compare against on-chain commitments. The random blinding factor makes this infeasible. ### Use Cases The 4-input commitment is used by: - **OpenGhostVault** — for one-time data reveals (publish a secret, prove a credential, share an API key) - **PersistentKeyVault** — for reusable data access (encrypted storage keys that can be accessed repeatedly without spending the nullifier) ## Token Commitment (7-Input) The token commitment extends the data commitment with three additional fields specific to token transfer: an identifier for which token, an amount, and policy binding parameters. ``` commitment = Poseidon7(secret, nullifierSecret, tokenId, amount, blinding, policyId, policyParamsHash) ``` ### Field Descriptions | Field | Size | Purpose | |-------|------|---------| | `secret` | 254-bit random | Same as data commitment — ownership proof. | | `nullifierSecret` | 254-bit random | Same as data commitment — nullifier derivation. | | `tokenId` | 254-bit | **Token identification.** Computed as `Poseidon2(tokenAddress, 0)` where `tokenAddress` is the ERC-20 contract address (or a sentinel value for native GHOST). Hashing the address ensures it fits in the BN254 field. | | `amount` | Up to 252-bit | **Token quantity.** The number of tokens (in the token's smallest unit, e.g., aghost for GHOST) being committed. Must be > 0 at commit time. Used for amount conservation checks during reveal. | | `blinding` | 254-bit random | Same as data commitment — brute-force protection. | | `policyId` | 254-bit | **Policy contract address.** The Ethereum address of the policy contract that must validate this commitment's reveal. Set to 0 if no policy is applied. Because it is an input to the commitment hash, the policy is **cryptographically bound** — it cannot be changed or removed after commit. | | `policyParamsHash` | 254-bit | **Policy parameters.** Computed as `keccak256(policyParams) % BN254_FIELD_PRIME`. The policy parameters (timelock windows, recipient allowlists, etc.) are also cryptographically bound to the commitment. | ### Token ID Computation Token IDs are computed deterministically from the token's contract address: ``` tokenId = Poseidon2(tokenAddress, 0) ``` The second input is always `0` (reserved for future use, such as chain identifiers for cross-chain tokens). This hash serves two purposes: 1. **Field compatibility.** Ethereum addresses are 160-bit values, which fit in the BN254 field, but hashing them produces a uniform distribution over the field — preventing any special-case behavior based on address structure. 2. **Circuit efficiency.** The circuit can verify token binding with a single Poseidon hash rather than address parsing. ### Policy Binding The `policyId` and `policyParamsHash` fields are baked into the commitment hash at commit time. This means: - The policy contract address is fixed when the commitment is created - The policy parameters (encoded as an ABI-encoded byte array, then hashed) are fixed when the commitment is created - During reveal, the ZK circuit enforces that the `policyId` and `policyParamsHash` in the proof match what was committed - The on-chain verifier independently confirms `keccak256(policyParams) % p == policyParamsHash` - The policy contract is called with the decoded parameters and must return `true` for the reveal to succeed This ensures that policies cannot be stripped, modified, or bypassed after commitment. If you commit tokens with a timelock policy, that timelock is permanent — there is no way to reveal them outside the specified time window, even by the original committer. ### Use Cases The 7-input commitment is used by: - **CommitRevealVault / BatchCommitRevealVault** — for private GHOST token transfers with optional policy constraints ## Comparing the Two Variants | Property | Data Commitment (4-input) | Token Commitment (7-input) | |----------|---------------------------|----------------------------| | Hash function | PoseidonT5 | PoseidonT8 | | Input count | 4 | 7 | | Data binding | `dataHash` (any data) | `tokenId` + `amount` (specific token and quantity) | | Policy support | No | Yes (`policyId` + `policyParamsHash`) | | Token operations | None (no mint/burn) | Burn on commit, mint on reveal | | Vault | OpenGhostVault, PersistentKeyVault | CommitRevealVault | | Reveal type | One-time (OpenGhostVault) or repeatable (PersistentKeyVault) | One-time (nullifier spent) | | Partial reveal | No | Yes (change commitment for remainder) | ## BN254 Field Constraint All commitment inputs must be valid **BN254 scalar field elements**. This means every value must be a non-negative integer strictly less than the field prime: ``` p = 21888242871839275222246405745257275088548364400416034343698204186575808495617 ``` This is a ~254-bit prime. In practice: - Random secrets, nullifier secrets, and blinding factors are generated as random 254-bit integers and checked to be less than `p` - Token amounts are naturally small enough (max 252-bit to allow non-negative range checks in the circuit) - Ethereum addresses (160-bit) fit easily within the field - Keccak-256 outputs (256-bit) must be reduced modulo `p` before use as circuit inputs — this is how `policyParamsHash` is derived The smart contracts validate this constraint at commit time, rejecting any commitment that is not a valid field element. The ZK circuits enforce field membership implicitly through their arithmetic — any out-of-field value would cause the proof to fail. ## Commitment Lifecycle The phantom key — the set of input values to the commitment hash — is the user's private credential. Whoever possesses the phantom key controls the commitment. The key is never transmitted to the chain; only the hash output appears on-chain. Proving knowledge of the key (via a ZK proof) is the only way to reveal or access the committed data. --- # Merkle Trees The Specter protocol uses Merkle trees to efficiently prove that a commitment exists in a set without revealing which commitment is being referenced. The tree is an **off-chain incremental binary Merkle tree** with **on-chain root tracking**. The full tree is maintained by off-chain indexers; the blockchain stores only the current root hash and a history of recent roots. ## Tree Parameters | Parameter | Value | |-----------|-------| | **Tree depth** | 20 levels | | **Max commitments** | 2^20 = 1,048,576 | | **Hash function** | PoseidonT3 (2-input Poseidon over BN254) | | **Root history size** | 100 roots (ring buffer) | | **Tree type** | Append-only (no deletion, no modification) | | **Storage model** | Full tree off-chain, root + history on-chain | ## Tree Structure A Merkle tree of depth 20 has 2^20 leaves at the bottom level. Each leaf holds either a commitment hash or a **zero hash** (indicating an empty slot). Internal nodes are computed by hashing their two children: ``` node = PoseidonT3(leftChild, rightChild) ``` The diagram shows a simplified view. The actual tree has 20 levels and over 1 million leaf positions. In practice, only a small fraction of leaves contain commitments — the rest are empty (zero hashes). ## Zero Hashes Empty leaves and their ancestor nodes use **precomputed zero hashes**. The zero hash at each level is deterministic: ``` zeroHash[0] = 0 // Empty leaf zeroHash[1] = PoseidonT3(zeroHash[0], zeroHash[0]) // Two empty leaves zeroHash[2] = PoseidonT3(zeroHash[1], zeroHash[1]) // Two empty subtrees ... zeroHash[20] = PoseidonT3(zeroHash[19], zeroHash[19]) // Root of empty tree ``` These values are computed once at initialization and stored as constants. When building or verifying a Merkle proof, any path through empty subtrees uses the appropriate precomputed zero hash rather than recomputing it. ## Append-Only Insertion The tree is **append-only**: new commitments are inserted at the next available leaf position, and no leaf can ever be modified or deleted. This property is critical for security: - **No rewriting history.** Once a commitment is in the tree, it stays there permanently. A proof of membership against a past root remains valid. - **No censorship via deletion.** A root operator cannot remove a commitment from the tree to prevent its reveal. - **Deterministic ordering.** Each commitment gets a unique `leafIndex` that monotonically increases. When a commitment is inserted: 1. The commitment hash is placed at `leafIndex = nextLeafIndex` 2. All ancestor nodes along the path from the new leaf to the root are recomputed 3. The new root hash is submitted on-chain 4. `nextLeafIndex` is incremented ## Root Management ### On-Chain Root Storage The CommitmentTree contract stores: - **`currentRoot`** — the most recently published root hash - **`rootHistory[100]`** — a ring buffer of the 100 most recent roots - **`nextLeafIndex`** — the index where the next commitment will be inserted When a new root is published via `updateRoot()`, it is written to both `currentRoot` and the next position in the ring buffer. ### Root History (100-Root Buffer) The 100-root history serves a critical usability purpose. Between the time a user commits data and the time they generate a ZK proof, new commitments may be added to the tree, changing the root. If only the current root were valid, any proof generated against a slightly older root would be rejected. The ring buffer allows proofs against any of the 100 most recent roots: During reveal, the contract checks: `isKnownRoot(proof.root)` — this returns `true` if the root is in the ring buffer. If the root has been rotated out (more than 100 root updates have occurred since the proof was generated), the proof is rejected and must be regenerated against a newer root. ### Root Operator The root operator is a trusted off-chain service that: 1. Monitors the blockchain for `CommitmentAdded` events 2. Maintains a complete local copy of the Merkle tree 3. Recomputes the root after each new commitment 4. Submits the new root on-chain via `updateRoot()` The root operator role is controlled by access control on the CommitmentTree contract. Only the designated operator address can call `updateRoot()`. ## Separate Trees The protocol maintains separate Merkle trees for different commitment types: | Tree | Contract | Used By | Purpose | |------|----------|---------|---------| | **CommitmentTree** | `CommitmentTree` | CommitRevealVault / BatchCommitRevealVault | Token commitments (7-input Poseidon) | | **OpenCommitmentTree** | `OpenCommitmentTree` | OpenGhostVault, PersistentKeyVault | Data commitments (4-input Poseidon) | Separate trees provide clean isolation between token and data privacy operations. They use separate root operators, separate root histories, and separate leaf index counters. A token commitment in CommitmentTree cannot be confused with a data commitment in OpenCommitmentTree. The PersistentKeyVault **shares** the OpenCommitmentTree with OpenGhostVault. This means a commitment inserted via OpenGhostVault can later be used for persistent access via PersistentKeyVault (and vice versa). They also share the OpenNullifierRegistry, so revoking a persistent key spends the same nullifier that a one-time reveal would have used. ## Merkle Proofs To prove a commitment exists in the tree, the prover provides a **Merkle proof**: the 20 sibling hashes along the path from the commitment's leaf to the root. The proof consists of: - **`pathElements[20]`** — the 20 sibling hashes (one per level) - **`pathIndices[20]`** — the 20 direction bits (0 = leaf is left child, 1 = leaf is right child) Verification starts at the leaf and hashes upward: ``` currentHash = commitment for i = 0 to 19: if pathIndices[i] == 0: currentHash = PoseidonT3(currentHash, pathElements[i]) else: currentHash = PoseidonT3(pathElements[i], currentHash) assert(currentHash == root) ``` This computation is performed **inside the ZK circuit** as private computation. The circuit takes the path elements and path indices as private inputs, computes the root, and checks it against the public root input. The on-chain verifier only sees the root (public) — not the path, not the leaf position, and not the commitment itself. ## Liveness Dependency The Merkle tree design introduces a **liveness dependency** on the root operator. If the root operator goes offline: - **New commitments** can still be submitted to the smart contract (they are queued with assigned leaf indices) - **Reveals and access proofs** stall because the on-chain root does not advance to include recent commitments - **Existing reveals** against already-published roots continue to work (within the 100-root history window) This is a deliberate trade-off. Computing Merkle tree updates on-chain (with 20 levels of Poseidon hashing per insertion) would cost approximately 600,000 gas per commitment — prohibitively expensive. The off-chain root operator reduces this to a single `updateRoot()` transaction per batch of commitments. Mitigations for root operator downtime: - **Decentralized root operators** — multiple independent operators can compute and submit roots - **Anyone can run an operator** — the operator role only requires read access to on-chain events and a funded wallet for submitting root updates - **100-root history** — proofs remain valid against recent roots even during temporary operator outages - **Root operator is not trusted with privacy** — the operator sees only commitment hashes (opaque), never the underlying data ## Gas Costs | Operation | Gas Cost | Frequency | |-----------|----------|-----------| | Insert commitment (on-chain) | ~50,000 | Per commit | | Update root (on-chain) | ~30,000 | Per batch of commits | | PoseidonT3 hash (on-chain) | ~30,000 | Per Merkle node during root updates | | Merkle proof verification | 0 (off-chain, inside ZK circuit) | Per reveal/access | The on-chain cost per commitment is dominated by the storage write for the commitment hash and event emission. The expensive Merkle path computation happens off-chain (in the indexer, the root operator, and the ZK prover), keeping on-chain gas costs manageable. --- # Nullifier System The nullifier system is the mechanism that prevents double-reveal (double-spending) in the Specter data protocol. Every commitment has exactly one valid nullifier. When a commitment is revealed, its nullifier is published on-chain and marked as spent. Any subsequent attempt to reveal the same commitment will be rejected because the nullifier has already been used. The critical privacy property of nullifiers is that **an observer cannot link a nullifier to the commitment it corresponds to**. The nullifier is derived from the commitment using private inputs known only to the commitment holder. To everyone else, the nullifier appears as an opaque 254-bit value with no discernible connection to any on-chain commitment. ## Nullifier Derivation The nullifier is computed using two nested PoseidonT3 (2-input Poseidon) hashes: ``` innerHash = Poseidon2(nullifierSecret, commitment) nullifier = Poseidon2(innerHash, leafIndex) ``` This two-step derivation uses three inputs total: | Input | Source | Purpose | |-------|--------|---------| | `nullifierSecret` | Part of the phantom key (kept private) | Provides secrecy — only the key holder can compute the nullifier | | `commitment` | The commitment hash stored in the Merkle tree | Binds the nullifier to a specific commitment | | `leafIndex` | The position of the commitment in the Merkle tree | Makes the nullifier position-dependent | ## Why Three Inputs? Each input to the nullifier derivation serves a distinct security purpose. Removing any one of them would create a vulnerability: ### nullifierSecret Provides Secrecy Without `nullifierSecret`, anyone who can see the commitment hash and knows the leaf index (both are public on-chain data) could compute the nullifier. This would allow an observer to precompute all possible nullifiers for all on-chain commitments and immediately link any published nullifier to its source commitment — completely destroying privacy. The `nullifierSecret` is a random 254-bit value known only to the phantom key holder. Even though the commitment and leaf index are public, the nullifier is unpredictable without this secret. ### commitment Binds to a Specific Deposit Without binding to the `commitment`, a single `nullifierSecret` could be used to construct a nullifier for a different commitment. By including the commitment hash as an input, the nullifier is cryptographically tied to exactly one committed data entry or token deposit. ### leafIndex Makes It Position-Dependent Without the `leafIndex`, two commitments with identical content (same secret, same data or amount) would produce identical nullifiers. This would mean: - The second commitment could never be revealed (its nullifier would already be marked as spent) - An observer could detect that two commitments share the same underlying data By including the leaf index, even identical commitments at different tree positions produce different nullifiers. This is especially important for **change commitments** in partial withdrawals — when a user partially reveals a token commitment, the remaining balance is re-committed to the tree. The change commitment inherits the same `secret` and `nullifierSecret` but gets a new `leafIndex`, so it gets a fresh nullifier. ## On-Chain Registry The on-chain component of the nullifier system is deliberately simple. The NullifierRegistry contract maintains a single mapping: ```solidity mapping(bytes32 => bool) public nullifiers; ``` When a nullifier is checked and marked as spent: 1. The vault contract calls `checkAndMarkNullifier(nullifier)` 2. The registry checks: `nullifiers[nullifier] == false` (not previously spent) 3. If not spent: sets `nullifiers[nullifier] = true` and returns success 4. If already spent: reverts the transaction Once set to `true`, a nullifier can **never** be unset. There is no admin function, no governance override, and no expiration. This is by design — the irreversibility of nullifier spending is what makes double-reveal impossible. Only the vault contract (set at deployment) can write to the registry. External callers can read nullifier status but cannot mark them as spent. ## Separate Registries Different vaults use different nullifier registries to maintain clean separation: | Registry | Used By | Purpose | |----------|---------|---------| | **NullifierRegistry** | CommitRevealVault / BatchCommitRevealVault | Token reveal nullifiers | | **OpenNullifierRegistry** | OpenGhostVault, PersistentKeyVault (revocation only) | Data reveal and key revocation nullifiers | | **ForkingNullifierRegistry** | ForkingVault | Forking commitment nullifiers | This separation means a nullifier spent in the token privacy system cannot interfere with a nullifier in the data privacy system, even if they happen to have the same value (which is astronomically unlikely but theoretically possible). ## Privacy Property The fundamental privacy property of nullifiers is **unlinkability**: an observer who sees a nullifier published on-chain cannot determine which commitment it corresponds to. An observer sees: - A set of commitments added to the tree over time - A set of nullifiers published during reveals But they **cannot determine** which nullifier corresponds to which commitment. Every possible mapping is equally likely from the observer's perspective. This is because: 1. The `nullifierSecret` is never revealed — it is a private input to the ZK proof 2. The ZK proof proves the nullifier was correctly derived without revealing any of the three inputs 3. Poseidon's preimage resistance ensures the inputs cannot be recovered from the nullifier value ### What an Observer Sees vs. What They Cannot Determine | Observable (Public) | Hidden (Private) | |---------------------|------------------| | Commitment hashes in the tree | Which commitment was revealed | | Nullifier values when spent | Which commitment a nullifier corresponds to | | Reveal transaction (recipient, amount for tokens) | The sender or source commitment | | Timing of commits and reveals | Connection between any specific commit and reveal | ## Nullifiers in the ZK Circuit The nullifier is not simply published and trusted — the ZK proof cryptographically guarantees that the nullifier was correctly derived from the commitment being revealed. Inside the `redemption.circom` circuit (for token reveals): 1. The prover provides `nullifierSecret`, `commitment`, and `leafIndex` as **private inputs** 2. The circuit computes `innerHash = Poseidon2(nullifierSecret, commitment)` 3. The circuit computes `nullifier = Poseidon2(innerHash, leafIndex)` 4. The circuit constrains the computed `nullifier` to equal the **public input** `nullifier` 5. The leaf index is not provided directly — it is **reconstructed from the Merkle path indices** (`pathIndices[0..19]` interpreted as a binary number), ensuring consistency with the Merkle proof This means: - The prover cannot publish a fake nullifier (the circuit would reject the proof) - The prover cannot reuse a nullifier from a different commitment (the circuit binds it to the specific commitment being proven) - The prover cannot reveal the same commitment twice (the same inputs always produce the same nullifier, which will be rejected by the on-chain registry) ## Nullifiers vs. Access Tags For **persistent phantom keys** (PersistentKeyVault), the protocol uses a different anti-replay mechanism called **access tags** instead of nullifiers. This is because persistent keys are designed to be accessed multiple times — spending a nullifier would destroy the key after one use. | Property | Nullifier | Access Tag | |----------|-----------|------------| | **Derivation** | `Poseidon2(Poseidon2(nullifierSecret, commitment), leafIndex)` | `Poseidon2(nullifierSecret, sessionNonce)` | | **Effect on commitment** | Permanently spent (commitment is consumed) | Not spent (commitment remains active) | | **Reusability** | One-time only | New tag per session (different sessionNonce) | | **Anti-replay scope** | Permanent (nullifier can never be reused) | Per-session (each accessTag is unique but commitment persists) | | **Used by** | CommitRevealVault, OpenGhostVault | PersistentKeyVault | Access tags replace the `commitment` and `leafIndex` binding with a `sessionNonce` — a fresh random value per access session. This means the same phantom key produces a different access tag every time, preventing session replay without consuming the underlying commitment. When a persistent key needs to be permanently destroyed, the PersistentKeyVault falls back to the standard nullifier mechanism: a reveal proof is generated (spending the nullifier via OpenNullifierRegistry), which permanently prevents any further access. ## Security Considerations **Nullifier entropy.** Nullifiers are 254-bit Poseidon hashes with three random-or-derived inputs. The probability of a nullifier collision (two different commitments producing the same nullifier) is negligible — approximately 2^(-127), well below any practical attack threshold. **Determinism.** For any given commitment, there is exactly one valid nullifier. This is enforced by the ZK circuit — the prover cannot choose or manipulate the nullifier value. This determinism is what makes the simple boolean mapping sufficient for anti-replay protection. **Registry immutability.** The nullifier registry has no admin functions, no upgrade mechanism for the mapping itself, and no way to reset a spent nullifier. This is a deliberate security property — the simplicity of the contract minimizes the attack surface. The contract's only write function is `checkAndMarkNullifier`, callable only by the associated vault. --- # Zero-Knowledge Proofs ## What Is a Zero-Knowledge Proof? A zero-knowledge proof (ZK proof) lets you prove a statement is true without revealing *why* it is true. The classic analogy: you can prove you are over 21 years old without showing your driver's license. The verifier learns the fact (you meet the age threshold) but learns nothing else — not your name, not your birthdate, not your address. More precisely, a ZK proof satisfies three properties: | Property | Meaning | |---|---| | **Completeness** | If the statement is true, an honest prover can always convince the verifier. | | **Soundness** | If the statement is false, no cheating prover can convince the verifier (except with negligible probability). | | **Zero-knowledge** | The verifier learns nothing beyond the truth of the statement itself. | In the context of blockchain, ZK proofs allow on-chain smart contracts to verify that a computation was performed correctly without having access to the underlying data. The proof is a small, constant-size object that can be checked cheaply on-chain, while the private data never leaves the prover's device. ## How Specter Uses Zero-Knowledge Proofs Specter is a data privacy protocol. Its core primitive — the Ghost Protocol — allows users to commit any data to a Merkle tree as a Poseidon hash, then prove ownership of that data later without revealing what the data was, when it was committed, or who committed it. The ZK proof is what makes this possible. When you want to reveal or access committed data, you generate a proof off-chain that demonstrates: 1. **You know the preimage** — you possess the secret values that hash to a specific commitment in the tree. 2. **The commitment exists** — the commitment is a leaf in the Merkle tree at a known root. 3. **You are authorized** — the proof binds to session-specific or recipient-specific values that prevent front-running and replay. The on-chain verifier checks the proof in constant time. If it passes, the protocol executes the reveal or access operation. At no point does the chain learn which commitment you are proving against, what the original data was, or any link between the commit and reveal transactions. ## Two Circuits, Two Purposes Specter uses two distinct ZK circuits, each designed for a different access pattern: ### Redemption Circuit (Token Reveals) The Redemption Circuit is the specialized circuit for the **GHOST token use case**. It proves that the prover committed a specific amount of tokens to the tree and is entitled to withdraw some or all of them. This circuit handles amount conservation, partial withdrawals with change commitments, nullifier derivation (to prevent double-spending), and policy enforcement. | Property | Value | |---|---| | Public inputs | 8 (root, nullifier, withdrawAmount, recipient, changeCommitment, tokenId, policyId, policyParamsHash) | | Private inputs | 7 (secret, nullifierSecret, amount, blinding, pathElements[20], pathIndices[20], newBlinding) | | Source | `redemption.circom` (174 LOC) | | Use case | One-time token reveals and private transfers | ### Access Proof Circuit (Persistent Data Access) The Access Proof Circuit is the **core data access circuit** — the circuit that makes Specter a data protocol, not just a token mixer. It proves that the prover knows a commitment preimage and that the commitment exists in the tree, without computing a nullifier. Because no nullifier is spent, the same proof can be generated repeatedly. This enables persistent, reusable access to sealed data. | Property | Value | |---|---| | Public inputs | 4 (root, dataHash, sessionNonce, accessTag) | | Private inputs | 5 (secret, nullifierSecret, blinding, pathElements[20], pathIndices[20]) | | Source | `accessProof.circom` (93 LOC) | | Use case | Persistent credential access, Phantom Identity, sealed data verification | ## Why Groth16? Specter uses **Groth16**, a pairing-based zero-knowledge proof system, for all on-chain verification. The choice is deliberate: | Property | Groth16 | PLONK | STARKs | |---|---|---|---| | **Proof size** | ~128 bytes (constant) | ~400-500 bytes | ~50-200 KB | | **Verification time** | ~3 pairings (constant) | ~20 group operations | Logarithmic in circuit size | | **On-chain gas cost** | ~200K gas | ~300-500K gas | ~1-2M gas | | **Trusted setup** | Required (circuit-specific) | Universal (updatable) | None | | **Tooling maturity** | Excellent (circom, snarkjs) | Good | Growing | Groth16 has the **smallest proof size** and the **fastest verification time** of any deployed ZK proof system. For a protocol that verifies proofs on every reveal and access operation, these properties directly translate to lower gas costs and better user experience. The trade-off is a **trusted setup**: Groth16 requires a one-time ceremony to generate proving and verification keys for each circuit. If the ceremony is compromised, an attacker could forge proofs. Specter mitigates this through a planned multi-party computation ceremony (see [Trusted Setup](./trusted-setup.md)) and defense-in-depth via a quantum commitment layer. For single-application circuits like Specter's redemption and access proof circuits, the trusted setup is a practical and acceptable trade-off. The circuits are fixed at deployment time, so the setup ceremony only needs to happen once per circuit version. --- # Groth16 on BN254 This section provides a technical deep dive into the proof system that underpins all zero-knowledge operations in Specter: Groth16 over the BN254 elliptic curve. ## The BN254 Curve Specter's ZK proofs operate over **BN254** (also known as `alt_bn128` or `bn256`), a Barreto-Naehrig pairing-friendly elliptic curve. BN254 is the de facto standard for on-chain ZK verification because Ethereum provides native precompiled contracts for its operations. | Parameter | Value | |---|---| | Curve equation | $y^2 = x^3 + 3$ | | Base field prime $p$ | $21888242871839275222246405745257275088696311157297823662689037894645226208583$ | | Scalar field prime $r$ | $21888242871839275222246405745257275088548364400416034343698204186575808495617$ | | Field size | 254 bits | | Security level | ~100-128 bits (see note below) | | Embedding degree | 12 | | Pairing type | Optimal Ate pairing | BN254 defines two groups used in Groth16 proofs: - **$\mathbb{G}_1$**: points on the curve over the base field $\mathbb{F}_p$. Elements are 64 bytes (two 256-bit coordinates). - **$\mathbb{G}_2$**: points on a degree-2 twist of the curve over $\mathbb{F}_{p^2}$. Elements are 128 bytes (four 256-bit coordinates). - **$\mathbb{G}_T$**: the target group of the pairing, a subgroup of $\mathbb{F}_{p^{12}}^*$. The Number Field Sieve attacks against BN curves have improved over the years, and BN254's concrete security is estimated at ~100-110 bits rather than the originally claimed 128 bits. This remains sufficient for Specter's threat model. A future curve migration (e.g., to BLS12-381 at ~120 bits) is possible without changing the circuit logic, only the trusted setup and verification contract. ## Groth16 Proof Structure A Groth16 proof consists of exactly **three group elements**: $$ \pi = (A, B, C) $$ where: | Element | Group | Size | Description | |---|---|---|---| | $A$ | $\mathbb{G}_1$ | 64 bytes | Encodes the prover's commitment to the witness | | $B$ | $\mathbb{G}_2$ | 128 bytes | Encodes the verification challenge | | $C$ | $\mathbb{G}_1$ | 64 bytes | Encodes the prover's response | **Total proof size: 256 bytes uncompressed, ~128 bytes compressed** (using point compression on $\mathbb{G}_1$ and $\mathbb{G}_2$). This is the smallest proof size of any deployed ZK proof system. The proof size is **constant** — it does not grow with the number of constraints in the circuit. Whether the circuit has 1,000 constraints or 1,000,000 constraints, the proof is always three group elements. ## Verification Equation The core of Groth16 verification is a single pairing equation. Given a proof $\pi = (A, B, C)$, public inputs $x_1, \ldots, x_\ell$, and verification key parameters $(\alpha, \beta, \gamma, \delta, \{IC_i\})$, the verifier checks: $$ e(A, B) = e(\alpha, \beta) \cdot e\!\left(\sum_{i=0}^{\ell} x_i \cdot IC_i,\; \gamma\right) \cdot e(C, \delta) $$ where $e : \mathbb{G}_1 \times \mathbb{G}_2 \to \mathbb{G}_T$ is the bilinear pairing. Breaking this down: | Term | Purpose | |---|---| | $e(A, B)$ | The prover's claim | | $e(\alpha, \beta)$ | A constant from the verification key (precomputed) | | $\sum_{i=0}^{\ell} x_i \cdot IC_i$ | Linear combination of public inputs with the verification key's IC (input commitments) points | | $e(C, \delta)$ | Binds the proof to the circuit-specific setup | The verifier performs: 1. **$\ell + 1$ scalar multiplications** in $\mathbb{G}_1$ to compute $\sum x_i \cdot IC_i$ 2. **3 pairing evaluations** (or 4, with $e(\alpha, \beta)$ if not precomputed) 3. **1 equality check** in $\mathbb{G}_T$ For Specter's Redemption Circuit ($\ell = 8$), this means 9 scalar multiplications and 3-4 pairings. For the Access Proof Circuit ($\ell = 4$), it is 5 scalar multiplications and 3-4 pairings. Both complete in constant time regardless of circuit complexity. ## On-Chain Verification Specter verifies Groth16 proofs on-chain using Ethereum's precompiled contracts for BN254 operations. These precompiles are available at fixed addresses and provide gas-efficient implementations of the curve arithmetic: | Address | Precompile | Operation | Gas Cost | |---|---|---|---| | `0x06` | `ecAdd` | Point addition in $\mathbb{G}_1$ | 150 gas | | `0x07` | `ecMul` | Scalar multiplication in $\mathbb{G}_1$ | 6,000 gas | | `0x08` | `ecPairing` | Bilinear pairing check | 34,000 + 45,000 per pair | The verification flow on-chain: ### Gas Cost Breakdown For the Redemption Circuit (8 public inputs): | Operation | Count | Per-Op Gas | Total Gas | |---|---|---|---| | `ecMul` (IC computation) | 9 | 6,000 | 54,000 | | `ecAdd` (IC accumulation) | 8 | 150 | 1,200 | | `ecPairing` (base) | 1 | 34,000 | 34,000 | | `ecPairing` (per pair) | 4 | 45,000 | 180,000 | | Calldata + overhead | — | — | ~30,000 | | **Total** | | | **~200,000 gas** | At typical gas prices, this makes on-chain ZK verification affordable for every commit/reveal operation. The cost scales linearly only with the number of public inputs (which is fixed per circuit), not with the circuit's internal complexity. ## Why Not PLONK or STARKs? ### PLONK PLONK offers a **universal trusted setup** — one ceremony works for all circuits. This is appealing for general-purpose ZK platforms, but Specter has a fixed set of circuits (Redemption + Access Proof) that change infrequently. The universal setup advantage is less relevant when you only need to run a ceremony once or twice. PLONK proofs are ~3-4x larger than Groth16 proofs and verification requires more group operations, translating to higher gas costs. For a protocol that verifies proofs on every reveal, this matters. ### STARKs STARKs require **no trusted setup** and are post-quantum secure. However, STARK proofs are orders of magnitude larger (50-200 KB vs. 128 bytes) and verification is significantly more expensive on-chain. The absence of EVM precompiles for STARK-friendly hash functions means verification requires custom Solidity logic, further increasing gas costs. Specter addresses the trusted setup concern through a rigorous multi-party computation ceremony and quantum defense-in-depth (see [Trusted Setup](./trusted-setup.md)). For the specific requirements of on-chain data privacy — small proofs, fast verification, low gas — Groth16 on BN254 remains the optimal choice. ### Summary | Criteria | Groth16 | PLONK | STARKs | |---|---|---|---| | Proof size | ~128 B | ~400-500 B | ~50-200 KB | | Verification gas | ~200K | ~300-500K | ~1-2M | | Trusted setup | Per-circuit | Universal | None | | Post-quantum | No | No | Yes | | EVM precompile support | Native | Partial | None | | **Best for Specter?** | **Yes** | No | No | --- # Redemption Circuit The Redemption Circuit (`redemption.circom`, 174 LOC) is the **specialized circuit for the GHOST token use case**. It proves that a user committed a specific amount of tokens to the Merkle tree and is entitled to withdraw some or all of them — without revealing which commitment is being spent, how much was originally deposited, or who deposited it. This circuit handles the full complexity of private token operations: commitment verification, nullifier derivation (double-spend prevention), amount conservation, partial withdrawals with change commitments, and policy binding. ## Public Inputs The Redemption Circuit has **8 public inputs** — values that are visible on-chain and verified by the smart contract: | # | Input | Type | Description | |---|---|---|---| | 0 | `root` | Field element | The Merkle tree root being proven against. The contract checks this matches a known historical root. | | 1 | `nullifier` | Field element | Unique identifier derived from the commitment. Recorded on-chain to prevent double-spending. | | 2 | `withdrawAmount` | Field element | The amount of tokens being withdrawn in this transaction. | | 3 | `recipient` | Field element | The recipient's address, bound to the proof to prevent front-running. | | 4 | `changeCommitment` | Field element | A new commitment for any remaining funds (0 if full withdrawal). | | 5 | `tokenId` | Field element | Hash identifying the token type (e.g., GHOST, gUSDC). | | 6 | `policyId` | Field element | Policy contract address (0 for no policy). | | 7 | `policyParamsHash` | Field element | Hash of the policy parameters (0 for no policy). | ## Private Inputs The circuit has **7 private inputs** — values known only to the prover that never appear on-chain: | Input | Type | Description | |---|---|---| | `secret` | Field element | The user's secret, part of the commitment preimage. | | `nullifierSecret` | Field element | A separate secret used to derive the nullifier. | | `amount` | Field element | The original deposit amount. | | `blinding` | Field element | Random blinding factor that makes the commitment hiding. | | `pathElements[20]` | Field element[20] | Sibling hashes along the Merkle proof path. | | `pathIndices[20]` | Bit[20] | Left/right direction bits for each level of the tree. | | `newBlinding` | Field element | Fresh blinding factor for the change commitment. | ## Constraint Walkthrough The circuit enforces correctness through seven sequential steps, each adding constraints to the R1CS system. ### Step 1: Compute Commitment from Preimage The circuit computes the commitment as a 7-input Poseidon hash of all preimage fields: $$ \text{commitment} = \text{Poseidon}_7(\text{secret},\; \text{nullifierSecret},\; \text{tokenId},\; \text{amount},\; \text{blinding},\; \text{policyId},\; \text{policyParamsHash}) $$ This uses the `Commitment()` sub-circuit. The 7-input structure ensures that the commitment is bound to the token type, amount, policy, and the user's secrets. Knowing the commitment hash alone reveals nothing about its preimage — the blinding factor provides computational hiding. ### Step 2: Verify Merkle Tree Membership The circuit uses `MerkleTreeChecker(20)` to verify that the computed commitment exists as a leaf in the Merkle tree with the claimed root. The proof walks 20 levels of the tree: - At each level, a `DualMux` orders the current hash and the sibling (`pathElements[i]`) based on the direction bit (`pathIndices[i]`). - A `Poseidon2` hash computes the parent node. - After 20 levels, the computed root must equal the public input `root`. This proves membership without revealing *which* leaf the commitment occupies. The verifier sees only the root, not the leaf index or any intermediate hashes. ### Step 3: Compute Leaf Index from Path Indices The `LeafIndex(20)` sub-circuit reconstructs the leaf's position in the tree from the binary path indices: $$ \text{leafIndex} = \sum_{i=0}^{19} \text{pathIndices}[i] \cdot 2^i $$ This value is used in the next step for nullifier derivation. It ensures the nullifier is bound to a specific tree position, preventing certain classes of nullifier collision attacks. ### Step 4: Derive and Verify Nullifier The nullifier is derived as a nested Poseidon hash: $$ \text{nullifier} = \text{Poseidon}_2\!\big(\text{Poseidon}_2(\text{nullifierSecret},\; \text{commitment}),\; \text{leafIndex}\big) $$ The circuit computes this value and constrains it to equal the public input `nullifier`. This serves two purposes: 1. **Double-spend prevention**: once the nullifier is recorded on-chain, the same commitment cannot be spent again. 2. **Unlinkability**: the nullifier reveals nothing about the commitment or the user's secrets. An observer cannot determine which tree leaf was spent by examining the nullifier. The nested hash structure (binding to both the commitment and the leaf index) prevents an attacker from constructing a different commitment at the same leaf position that yields the same nullifier. ### Step 5: Range Check Amounts (252-bit) The circuit enforces amount conservation through range checks: ``` changeAmount = amount - withdrawAmount ``` Three `Num2Bits(252)` decompositions enforce that: - `amount` fits in 252 bits (non-negative, within field range) - `withdrawAmount` fits in 252 bits (non-negative) - `changeAmount` fits in 252 bits (non-negative, meaning `withdrawAmount <= amount`) The 252-bit bound (rather than 256-bit) leaves headroom below the BN254 scalar field prime (~254 bits), preventing overflow attacks where an attacker could wrap around the field to create tokens from nothing. ### Step 6: Compute Change Commitment For partial withdrawals (when `withdrawAmount < amount`), the circuit computes a change commitment for the remaining funds: $$ \text{changeCommitment} = \text{Poseidon}_7(\text{secret},\; \text{nullifierSecret},\; \text{tokenId},\; \text{changeAmount},\; \text{newBlinding},\; \text{policyId},\; \text{policyParamsHash}) $$ The circuit handles two cases: | Case | Condition | changeCommitment | |---|---|---| | **Full withdrawal** | `changeAmount == 0` | Must be `0` | | **Partial withdrawal** | `changeAmount > 0` | Must match computed `Poseidon7(...)` | An `IsZero` check on `changeAmount` selects between these cases. The change commitment is inserted into the Merkle tree by the contract, creating a new spendable UTXO for the remaining balance. Policy fields carry forward into the change commitment, ensuring that policy restrictions survive partial withdrawals. ### Step 7: Bind Recipient and Policy The final step creates **dummy quadratic constraints** that bind the `recipient`, `policyId`, and `policyParamsHash` public inputs to the proof: ``` recipientSquare = recipient * recipient policyIdSquare = policyId * policyId policyParamsHashSquare = policyParamsHash * policyParamsHash ``` These constraints do not perform logical checks — their purpose is to make the proof invalid if anyone changes these public inputs after proof generation. Without these constraints, an attacker could take a valid proof and substitute a different recipient address (front-running the reveal transaction). The quadratic form ensures the Groth16 prover commits to these values during proof generation. ## Circuit Instantiation The circuit is instantiated at the bottom of `redemption.circom` with a tree depth of 20 (supporting ~1 million commitments): ``` component main {public [root, nullifier, withdrawAmount, recipient, changeCommitment, tokenId, policyId, policyParamsHash]} = GhostRedemption(20); ``` The `{public [...]}` declaration tells the Groth16 compiler which inputs are public (visible to the verifier) versus private (known only to the prover). ## Relationship to the Broader Protocol The Redemption Circuit is one of two circuits in Specter. It handles the **token-specific** use case: burning GHOST tokens into a commitment and later minting fresh tokens from a ZK proof. The [Access Proof Circuit](./access-proof-circuit.md) handles the **general data access** use case, which requires no nullifier, no amounts, and no recipient binding — just pure authentication against committed data. Together, these two circuits cover the full spectrum of Specter's privacy operations: one-time value transfers (Redemption) and persistent data access (Access Proof). --- # Access Proof Circuit The Access Proof Circuit (`accessProof.circom`, 93 LOC) is **the core data access circuit — the circuit that makes Specter a data protocol, not just a token mixer**. It proves that the prover knows a commitment preimage and that the commitment exists in the Merkle tree, without computing a nullifier and without spending anything. This is pure authentication. The same proof can be generated indefinitely. The committed data is never consumed, never moved, never altered. The prover simply demonstrates: "I own this commitment." ## Design Philosophy The Redemption Circuit is designed for one-time operations: withdraw tokens, spend the nullifier, done. But most data access patterns are not one-time. A credential needs to be verified repeatedly. An API key needs to be authenticated on every request. An encryption key needs to be proven without being revealed, over and over. The Access Proof Circuit strips away everything that makes the Redemption Circuit one-time: | Feature | Redemption Circuit | Access Proof Circuit | |---|---|---| | Nullifier computation | Yes (one-time spend) | **No** (repeatable) | | Amount fields | Yes (withdraw + change) | **No** (no value) | | Recipient binding | Yes (prevents front-running) | **No** (session-bound instead) | | Policy binding | Yes (policyId + params) | **No** (pure data access) | | Anti-replay mechanism | Nullifier (permanent) | **accessTag** (session-scoped) | | Can be used again? | No (nullifier spent) | **Yes (indefinitely)** | What remains is the minimum necessary for cryptographic proof of data ownership: commitment verification, Merkle membership, and a session-bound tag for anti-replay. ## Public Inputs The Access Proof Circuit has **4 public inputs**: | # | Input | Type | Description | |---|---|---|---| | 0 | `root` | Field element | The Merkle tree root being proven against. Must match a known on-chain root. | | 1 | `dataHash` | Field element | Hash of the sealed data. Binds the proof to specific data content. | | 2 | `sessionNonce` | Field element | A random nonce generated for this access session. Prevents proof reuse across sessions. | | 3 | `accessTag` | Field element | Derived tag: $\text{Poseidon}_2(\text{nullifierSecret},\; \text{sessionNonce})$. Recorded on-chain for anti-replay. | ## Private Inputs The circuit has **5 private inputs**: | Input | Type | Description | |---|---|---| | `secret` | Field element | The user's secret, part of the commitment preimage. | | `nullifierSecret` | Field element | Used to derive the accessTag (but NOT to compute a nullifier). | | `blinding` | Field element | Random blinding factor for the commitment. | | `pathElements[20]` | Field element[20] | Sibling hashes along the Merkle proof path. | | `pathIndices[20]` | Bit[20] | Left/right direction bits for each level of the tree. | ## Constraint Walkthrough The circuit has three steps — significantly simpler than the seven-step Redemption Circuit. ### Step 1: Compute Commitment from Preimage The commitment is a 4-input Poseidon hash: $$ \text{commitment} = \text{Poseidon}_4(\text{secret},\; \text{nullifierSecret},\; \text{dataHash},\; \text{blinding}) $$ Note the structural difference from the Redemption Circuit's 7-input commitment. The Access Proof commitment includes `dataHash` directly (instead of tokenId and amount) and omits policy fields. This makes the commitment data-agnostic — the `dataHash` can represent any piece of data: a credential, an image hash, an API key, an encryption key, or any other arbitrary value. ### Step 2: Verify Merkle Tree Membership Identical to the Redemption Circuit. The `MerkleTreeChecker(20)` sub-circuit walks 20 levels of the tree, verifying that the computed commitment is a leaf at the claimed root. The prover supplies the sibling hashes and direction bits; the circuit recomputes the root and constrains it to match the public input. ### Step 3: Compute and Verify accessTag The anti-replay mechanism. Instead of a nullifier (which would make the proof one-time), the circuit computes a session-bound tag: $$ \text{accessTag} = \text{Poseidon}_2(\text{nullifierSecret},\; \text{sessionNonce}) $$ The circuit constrains this computed value to equal the public input `accessTag`. **Why this works for anti-replay**: the `sessionNonce` is a fresh random value generated by the verifier (or the protocol) for each access session. The prover must include this nonce in their proof, binding the proof to a specific session. The on-chain contract records the `accessTag` to prevent the same proof from being replayed within the same session. **Why this does not consume the commitment**: the `accessTag` is derived from `nullifierSecret` and `sessionNonce`, not from the commitment or leaf index. A new session with a new nonce produces a new `accessTag`. The commitment remains in the tree, unspent, available for future access proofs. ## Key Difference: No Nullifier This is the critical design decision. In the Redemption Circuit, the nullifier is: $$ \text{nullifier} = \text{Poseidon}_2\!\big(\text{Poseidon}_2(\text{nullifierSecret},\; \text{commitment}),\; \text{leafIndex}\big) $$ This value is deterministic — it will be the same every time for the same commitment. Once recorded on-chain, the commitment is permanently spent. The Access Proof Circuit **deliberately omits this computation**. There is no leaf index reconstruction, no nullifier derivation, no nullifier check. The commitment cannot be "accidentally spent" by using the Access Proof Circuit. The two circuits operate on entirely separate planes: Redemption consumes; Access Proof authenticates. ## Use Cases The Access Proof Circuit enables Specter's data protocol capabilities: | Use Case | dataHash Contains | Access Pattern | |---|---|---| | **Credential verification** | Hash of credential payload | Verify on each login, indefinitely | | **API key authentication** | Hash of API key material | Authenticate on each request | | **Image provenance** | Hash of original image | Prove ownership without revealing the image | | **Encryption key proof** | Hash of encryption key | Prove you hold the key without exposing it | | **Phantom Identity** | Hash of identity material | Authenticate as a persistent pseudonymous identity | | **Sealed document access** | Hash of document content | Prove document ownership for access control | ## Circuit Instantiation ``` component main {public [root, dataHash, sessionNonce, accessTag]} = AccessProof(20); ``` The tree depth of 20 matches the Redemption Circuit, supporting ~1 million commitments. Both circuits operate against the same Merkle tree infrastructure, though the Open Ghost system maintains a separate `OpenCommitmentTree` for data-only commitments. ## Relationship to Phantom Identity Phantom Identity extends the Access Proof Circuit into a full identity system. A Phantom Identity is a persistent pseudonym backed by a commitment in the tree. The holder generates Access Proofs repeatedly to authenticate as that identity — proving they know the secrets without ever revealing them or consuming the commitment. The `accessTag` mechanism provides anti-replay per session while allowing unlimited future sessions. This is the foundation that enables Specter to function as a persistent data privacy protocol, not just a one-time reveal system. --- # Trusted Setup Groth16 requires a **trusted setup** — a one-time ceremony that generates the proving and verification keys for each circuit. This is the primary trade-off for Groth16's unmatched proof size and verification speed. If the setup is compromised, an attacker can forge proofs. Specter takes this trade-off seriously and plans a rigorous multi-party computation ceremony for mainnet deployment. ## What Is a Trusted Setup? A Groth16 trusted setup produces two artifacts: | Artifact | Purpose | Size | |---|---|---| | **Proving key** (`circuit.zkey`) | Used by the prover to generate proofs. Contains encoded circuit structure and setup randomness. | Large (tens of MB) | | **Verification key** (`verification_key.json`) | Used by the on-chain verifier to check proofs. Contains curve points derived from the circuit and setup. | Small (a few KB) | The setup process involves generating random values (collectively called **toxic waste**) that must be destroyed after the ceremony. If any participant retains this toxic waste, they can forge proofs — creating valid-looking proofs for false statements. The setup has two phases: ### Phase 1: Powers of Tau Phase 1 is **curve-generic** — it depends only on the elliptic curve (BN254), not on any specific circuit. The output is a structured reference string containing powers of a secret value $\tau$ encoded as curve points: $$ [1]_1, [\tau]_1, [\tau^2]_1, \ldots, [\tau^n]_1, \quad [1]_2, [\tau]_2, \ldots $$ The parameter $n$ determines the maximum circuit size the setup can support. Specter uses the **Hermez `pot16_final.ptau`** file, which supports circuits with up to $2^{16} = 65{,}536$ constraints. This Phase 1 output was generated by the Hermez team's public multi-party ceremony with dozens of independent contributors. | Parameter | Value | |---|---| | Source | Hermez Cryptographic Setup | | File | `pot16_final.ptau` | | Max constraints | $2^{16}$ (65,536) | | Contributors | 50+ independent participants | | Curve | BN254 | The security guarantee: if **at least one** contributor in the Phase 1 ceremony properly destroyed their toxic waste, the output is secure. With 50+ contributors from diverse organizations and geographies, this is a strong assumption. ### Phase 2: Circuit-Specific Phase 2 takes the Phase 1 output and specializes it for a specific circuit (Redemption or Access Proof). This phase: 1. Compiles the circom circuit into an R1CS constraint system. 2. Combines the R1CS with the Phase 1 Powers of Tau. 3. Generates the circuit-specific proving key and verification key. Phase 2 introduces its own toxic waste. Like Phase 1, it should be performed as a multi-party computation where each contributor adds their own randomness. The security guarantee is the same: if at least one participant destroys their contribution, the setup is secure. ## Current Testnet Status Specter's testnet uses **development setup keys** generated with a single contributor and a known delta value ($\delta = 1$). This means: The testnet setup uses `delta=1`, meaning the toxic waste is trivially known. Proofs on testnet can be forged by anyone who understands the Groth16 math. This is acceptable because testnet tokens (GHOST) have no monetary value and the testnet exists for integration testing, not security validation. | Property | Testnet | Mainnet (Planned) | |---|---|---| | Phase 1 | `pot16_final.ptau` (Hermez) | `pot16_final.ptau` (Hermez) | | Phase 2 | Single contributor, $\delta = 1$ | Multi-party ceremony, 3-5+ contributors | | Proofs forgeable? | Yes (by design) | No (if ceremony is honest) | | Token value | None (testnet tokens) | Real economic value | | Purpose | Integration testing | Production security | ## Mainnet Ceremony Plan For mainnet, Specter will conduct a **multi-party computation (MPC) ceremony** for Phase 2 of both circuits (Redemption and Access Proof). The ceremony design: ### Participants A minimum of **3-5 independent contributors**, ideally more. Contributors should: - Come from different organizations and jurisdictions - Use different hardware and operating systems - Generate entropy from diverse sources (hardware RNGs, physical randomness, etc.) - Publicly attest to their participation and entropy destruction ### Process 1. **Initialization**: Start from the Phase 1 `pot16_final.ptau` and the compiled circuit R1CS. 2. **Sequential contributions**: Each participant adds their randomness to the setup, producing an updated parameters file. Each contribution is publicly logged with a hash for verification. 3. **Finalization**: The final parameters file is used to extract the proving key and verification key. 4. **Verification**: Anyone can verify the contribution chain using snarkjs tooling, confirming that each contribution was correctly applied. 5. **Deployment**: The verification key is embedded in the on-chain verifier contract. The proving key is distributed to provers. ### Entropy Sources Contributors are encouraged to use diverse entropy sources: - Hardware random number generators (e.g., Intel RDRAND, dedicated HSMs) - Physical randomness (dice rolls, radioactive decay measurements, atmospheric noise) - Combined sources (XOR of multiple independent generators) - Keyboard/mouse entropy from extended interaction The key property is **independence**: even if $n-1$ contributors collude or are compromised, as long as one contributor genuinely destroys their toxic waste, the setup is secure. ## What Happens If the Setup Is Compromised? If a Groth16 trusted setup is compromised (all contributors collude or fail to destroy toxic waste), an attacker gains the ability to **forge proofs** — they can create a valid proof for any statement, including false ones. In Specter's context, this means: | Attack | Impact | |---|---| | Forge a redemption proof | Mint GHOST tokens without having committed them | | Forge an access proof | Authenticate as a data owner without knowing the secrets | | Forge a change commitment proof | Create tokens from nothing via invalid partial withdrawals | This is the worst-case scenario for any Groth16 system. Specter's defenses: 1. **Multi-party ceremony**: the 1-of-N honesty assumption makes compromise difficult in practice. 2. **Quantum commitment layer**: Specter's roadmap includes a post-quantum commitment scheme that provides defense-in-depth. Even if Groth16 proofs can be forged, the quantum-resistant commitment layer adds an independent verification path. 3. **Monitoring**: on-chain monitoring can detect anomalous mint/reveal patterns that might indicate proof forgery (e.g., total reveals exceeding total commits for a given token). 4. **Circuit upgrades**: if a compromise is suspected, new circuits with a fresh setup can be deployed. Existing commitments can be migrated via a governance-controlled upgrade path. ## Verification Tools The setup can be verified by anyone using standard tooling: ``` # Verify Phase 1 (Powers of Tau) snarkjs powersoftau verify pot16_final.ptau # Verify Phase 2 contributions snarkjs zkey verify circuit.r1cs pot16_final.ptau circuit_final.zkey # Export verification key snarkjs zkey export verificationkey circuit_final.zkey verification_key.json ``` All ceremony artifacts (contribution hashes, participant attestations, final parameters) will be published for public audit. --- # Phantom Keys A Phantom Key is a **bearer instrument for data**. It is a set of secret numbers that encode the preimage of a commitment in Specter's Merkle tree. Whoever knows the numbers can generate a zero-knowledge proof of ownership and access or reveal the committed data. No account, no wallet, no private key in the Ethereum sense — just numbers. ## The Core Idea When data is committed to Specter's Merkle tree, the commitment is a Poseidon hash of several secret values (secret, nullifierSecret, blinding, etc.). These values are the "keys" to the commitment. Anyone who possesses them can: 1. Recompute the commitment hash. 2. Prove it exists in the Merkle tree via a ZK proof. 3. Reveal the committed data or access it persistently. A Phantom Key packages these secrets into a **human-readable numeric format** that can be written on paper, spoken aloud, printed on a card, or transmitted digitally. It is a self-contained bearer credential — the numbers *are* the access. ## Format Phantom Keys are displayed as groups of four digits, similar to a credit card number: ``` 9473 0018 7376 9372 0484 1273 ``` Under the hood, this numeric string encodes: | Field | Description | |---|---| | Version byte | Format version (V1 through V7, supporting progressive feature additions) | | Seed (128 bits) | The root entropy from which all secrets are derived | | Amount encoding | Scientific notation representation of the committed value (significand + exponent) | | Leaf index | Variable-length encoding of the commitment's position in the tree | | Checksum (4 digits) | Error detection code for the full key | The encoding is compact — a full Phantom Key fits in 24-30 digits depending on the version and amount size. ## Seed-Based Derivation The key insight is that all cryptographic secrets can be **deterministically derived from a single 128-bit seed** using HKDF-SHA256. This keeps the Phantom Key short while maintaining full cryptographic strength. The derivation process: 1. **Generate seed**: 16 bytes (128 bits) from a cryptographic random number generator. 2. **Derive secret**: `HKDF-SHA256(seed, info="ghostcoin-secret-v1", length=32)` reduced mod BN254 field prime. 3. **Derive nullifierSecret**: `HKDF-SHA256(seed, info="ghostcoin-nullifier-v1", length=32)` reduced mod BN254 field prime. 4. **Derive blinding**: `HKDF-SHA256(seed, info="ghostcoin-blinding-v1", length=32)` reduced mod BN254 field prime. 5. **Derive changeBlinding** (for partial withdrawals): `HKDF-SHA256(seed, info="ghostcoin-change-blinding-v1", length=32)` reduced mod BN254 field prime. Each derivation uses a different HKDF `info` string, producing cryptographically independent values from the same seed. The 32-byte HKDF output is reduced modulo the BN254 scalar field prime ($r \approx 2^{254}$) to produce a valid field element. Later versions of the Phantom Key format (V4+) also derive a `quantumSecret` — a 256-bit value reserved for the planned quantum-resistant commitment layer. This value is included in the numeric encoding but is not used by the current ZK circuits. ## Two Modes of Operation Phantom Keys operate in two distinct modes depending on the vault they interact with: ### Standard Phantom Keys Standard Phantom Keys are **one-time bearer instruments**. They are backed by commitments in the `CommitRevealVault` (for tokens) or `OpenGhostVault` (for data). When the key is used to reveal, the commitment's nullifier is spent and the key is consumed. It cannot be used again. Use cases: private token transfers, gift cards, one-time credential sharing, sealed document delivery. See [Standard Phantom Keys](./standard-keys.md) for the full flow. ### Open Ghost Keys Open Ghost Keys are a specialized variant for **sealed data sharing**. They use the `OpenGhostVault` and `OpenGhostKeyVault` contracts to enable one-time encrypted data retrieval. The commitment contains a `dataHash` rather than a token amount, and the `OpenGhostKeyVault` stores an encrypted key part that is returned once and then permanently deleted. Use cases: encrypted message delivery, sealed documents, one-time secret sharing, credential issuance. See [Open Ghost Protocol](./open-ghost.md) for the full specification. ## Phantom Identity (Extended Concept) Both Standard Phantom Keys and Open Ghost Keys are **one-time** instruments — the commitment is consumed on reveal. **Phantom Identity** extends the Phantom Key concept to create **persistent, reusable identities**. A Phantom Identity uses the same seed-based derivation as a Phantom Key, but instead of the Redemption Circuit (which spends a nullifier), it uses the [Access Proof Circuit](../zk-proofs/access-proof-circuit.md) (which does not). This means the commitment is never consumed, and the holder can authenticate repeatedly using session-bound `accessTag` values. Phantom Identity is covered in its own section of the whitepaper. ## Security Model The security of a Phantom Key reduces to a single principle: > **Knowing the numbers is equivalent to holding the data or tokens.** There is no password, no account recovery, no custodian. If the numbers are lost, the committed data or tokens are permanently inaccessible. If the numbers are stolen, the thief can reveal the commitment before the legitimate holder. | Threat | Mitigation | |---|---| | Key interception during sharing | Share physically (printed card, spoken aloud) or via encrypted channel | | Key theft from storage | Store securely (encrypted device, physical safe, split storage) | | Key loss | No recovery possible — this is a feature, not a bug. Bearer instruments are final. | | Brute-force seed guessing | 128-bit seed = $2^{128}$ possibilities. Computationally infeasible. | | HKDF output prediction | HKDF-SHA256 is a standardized KDF with proven security bounds. Outputs are indistinguishable from random. | | Checksum forgery | 4-digit checksum detects accidental transcription errors, not adversarial modification. | The Phantom Key is designed to be the simplest possible interface to Specter's privacy system: a number you can hold, share, and redeem. No blockchain knowledge required. --- # Standard Phantom Keys Standard Phantom Keys are **one-time bearer instruments** backed by the `CommitRevealVault` (for GHOST tokens and other fungible assets) or the `OpenGhostVault` (for arbitrary data). When the key is used to reveal, the commitment's nullifier is spent on-chain and the key is permanently consumed. The lifecycle is: generate, commit, share, reveal, done. ## Lifecycle ## Step-by-Step ### Step 1: Generate Secrets The sender generates a 128-bit random seed and derives all cryptographic secrets using HKDF-SHA256: | Derived Value | HKDF Info String | Purpose | |---|---|---| | `secret` | `ghostcoin-secret-v1` | Primary secret in commitment preimage | | `nullifierSecret` | `ghostcoin-nullifier-v1` | Used for nullifier derivation | | `blinding` | `ghostcoin-blinding-v1` | Randomizes the commitment (hiding property) | | `changeBlinding` | `ghostcoin-change-blinding-v1` | Used if recipient does a partial withdrawal | All values are reduced modulo the BN254 scalar field prime to produce valid field elements. ### Step 2: Vanish (Commit to Tree) The sender computes the commitment and submits it to the `CommitRevealVault`: $$ \text{commitment} = \text{Poseidon}_7(\text{secret},\; \text{nullifierSecret},\; \text{tokenId},\; \text{amount},\; \text{blinding},\; \text{policyId},\; \text{policyParamsHash}) $$ For a standard key with no policy, `policyId = 0` and `policyParamsHash = 0`. The contract: 1. Burns the deposited tokens (removes them from the sender's balance). 2. Inserts the commitment as a new leaf in the Merkle tree. 3. Returns the `leafIndex` (the commitment's position in the tree). After this transaction, the tokens exist only as a hash in the tree. There is no on-chain record linking the commitment to the sender's address, the amount, or the token type. ### Step 3: Encode the Phantom Key The sender encodes the seed, amount, and leaf index into a numeric string: ``` [version][seed digits][significand length][significand][exponent][index length][leaf index][checksum] ``` The amount is stored in scientific notation (significand and exponent) to save digits. The leaf index uses variable-length encoding to avoid wasting digits on leading zeros. A 4-digit checksum provides error detection. The result is displayed in groups of four digits for readability: ``` 9473 0018 7376 9372 0484 1273 ``` ### Step 4: Share the Key The Phantom Key can be shared through any channel: | Channel | Properties | |---|---| | **Printed card** | Physical bearer instrument. No digital trail. | | **Spoken aloud** | Ephemeral transfer. Groups of four digits are easy to dictate. | | **Encrypted message** | Digital transfer with forward secrecy (Signal, etc.) | | **QR code** | Scan to redeem. Convenient for point-of-sale or gift cards. | | **Written on paper** | Low-tech, high-security for cold storage. | The key contains everything needed to reveal the commitment. No additional context, no account credentials, no network state is required (beyond access to the Specter chain to submit the proof). ### Step 5: Reveal (Redeem) The recipient decodes the Phantom Key, re-derives the secrets from the seed, fetches the current Merkle proof from the chain, and generates a Groth16 proof using the [Redemption Circuit](../zk-proofs/redemption-circuit.md). The proof demonstrates (without revealing any private inputs): - The recipient knows the preimage of a commitment in the tree. - The nullifier is correctly derived from that commitment. - The withdrawal amount does not exceed the original deposit. - The proof is bound to the recipient's address (preventing front-running). The on-chain verifier: 1. Checks the proof against the verification key. 2. Verifies the Merkle root is a known historical root. 3. Records the nullifier to prevent double-spending. 4. Mints fresh tokens to the recipient's address. The key is now consumed. The nullifier is permanently recorded. Attempting to use the same key again will fail because the nullifier has already been spent. ## Partial Withdrawals A Phantom Key holder can withdraw less than the full amount. The Redemption Circuit handles this via a **change commitment**: The change commitment uses the same `secret` and `nullifierSecret` (derived from the original seed) but a different blinding factor (`changeBlinding`, also derived from the seed via a different HKDF info string). This means the change Phantom Key (V3 format) can be derived from the original seed — the holder does not need to generate a new seed for the change. ## Token-Backed vs. Data-Backed Keys Standard Phantom Keys can be backed by either tokens or data, depending on which vault they commit to: | Property | Token-Backed (CommitRevealVault) | Data-Backed (OpenGhostVault) | |---|---|---| | Commitment structure | 7-input Poseidon (includes tokenId, amount) | 4-input Poseidon (includes dataHash) | | Deposit | Burn tokens into vault | Pay commitment fee | | Reveal | Mint tokens to recipient | Return sealed data | | Partial withdrawal | Yes (change commitment) | No (data is atomic) | | Circuit | Redemption (8 public inputs) | Redemption variant for Open Ghost | ## Use Cases | Use Case | Description | |---|---| | **Private transfers** | Send GHOST tokens without linking sender and recipient on-chain. | | **Gift cards** | Print Phantom Keys on physical cards. Redeemable by anyone. | | **Offline redemption** | Keys work without internet until the reveal step. Useful for in-person transactions. | | **One-time credential sharing** | Commit a credential hash, share the key, recipient reveals and verifies. | | **Escrow without intermediary** | Commit tokens, share the key with conditions. The key itself is the escrow. | | **Cross-border value transfer** | A number that carries value. No bank, no intermediary, no ID. | ## Security Properties | Property | Guarantee | |---|---| | **Bearer semantics** | Whoever knows the numbers controls the commitment. | | **Unlinkability** | The reveal transaction cannot be linked to the commit transaction by any on-chain observer. | | **Front-running resistance** | The proof binds to the recipient's address. A miner/validator who sees the proof in the mempool cannot redirect the tokens. | | **Double-spend prevention** | The nullifier is deterministically derived and recorded on-chain. A second reveal with the same key fails. | | **Amount privacy** | The on-chain reveal shows only the withdrawal amount, not the original deposit amount (in partial withdrawal cases, the change is re-committed privately). | --- # Open Ghost Protocol The Open Ghost Protocol is Specter's system for **sealed data sharing** — committing arbitrary data to the Merkle tree and enabling one-time retrieval by a Phantom Key holder. While Standard Phantom Keys were originally designed for the GHOST token use case, Open Ghost extends the same commit/reveal paradigm to any data: credentials, images, API keys, encryption keys, documents, or anything else that can be hashed. Open Ghost uses a dedicated set of contracts (`OpenGhostVault`, `OpenGhostKeyVault`, `OpenGhostReveal`) with a **separate Merkle tree** and **separate nullifier registry** from the token system. This separation ensures that data operations cannot interfere with token operations and vice versa. ## Architecture ## Commitment Structure Open Ghost uses a **4-input Poseidon commitment**, distinct from the 7-input commitment used by the token system: $$ \text{commitment} = \text{Poseidon}_4(\text{secret},\; \text{nullifierSecret},\; \text{dataHash},\; \text{blinding}) $$ | Field | Description | |---|---| | `secret` | User's secret (derived from seed via HKDF) | | `nullifierSecret` | Nullifier derivation secret (derived from seed via HKDF) | | `dataHash` | Hash of the sealed data (credential, image, API key, etc.) | | `blinding` | Random blinding factor (derived from seed via HKDF) | The `dataHash` is **generic** — it is simply the hash of whatever data the user wants to seal. The protocol does not interpret or constrain the data type. This is what makes Open Ghost a general-purpose data protocol: | Data Type | dataHash Computation | |---|---| | Credentials | Hash of credential JSON/payload | | Images | Hash of image bytes | | API keys | Hash of API key string | | Encryption keys | Hash of key material | | Documents | Hash of document content | | Arbitrary bytes | Hash of raw byte array | ## Fee-Based Commits Unlike the token system (where the user deposits tokens that are burned into the commitment), Open Ghost uses a **fee-based model**: - The user pays a commitment fee (in native GHOST) to insert their commitment into the tree. - No tokens are locked or burned — the commitment represents data, not value. - The minimum fee is configurable by the vault owner and serves as spam prevention. - Collected fees are withdrawable by the designated fee recipient. ```solidity function commit(bytes32 commitment) external payable returns (uint256 leafIndex) ``` The commit function validates the commitment (non-zero, valid field element), checks the fee, inserts the commitment into the `OpenCommitmentTree`, and returns the leaf index. ## Separate Infrastructure Open Ghost maintains its own Merkle tree and nullifier registry, independent from the token system: | Component | Token System | Open Ghost | |---|---|---| | Vault | `CommitRevealVault` | `OpenGhostVault` | | Merkle tree | `CommitmentTree` | `OpenCommitmentTree` | | Nullifier registry | `NullifierRegistry` | `OpenNullifierRegistry` | | Proof verifier | `RedemptionVerifier` | `OpenGhostRevealVerifier` | | Key storage | N/A | `OpenGhostKeyVault` | This separation provides several guarantees: - **No cross-contamination**: a data nullifier cannot interfere with a token nullifier. - **Independent scaling**: the data tree can grow independently of the token tree. - **Different security properties**: token commits require deposit verification; data commits require only fee payment. - **Upgradability**: each system can be upgraded independently. ## One-Time Key Retrieval: OpenGhostKeyVault The `OpenGhostKeyVault` is the on-chain component that enables sealed data sharing with **one-time retrieval** semantics. It stores encrypted key material during the seal phase and returns it exactly once during the reveal phase, then permanently deletes it. ### Key Lifecycle ### Storage Structure Each key entry in the vault contains: | Field | Type | Description | |---|---|---| | `commitment` | `bytes32` | The associated commitment hash | | `encKeyPartB` | `bytes` | The encrypted key part B | | `exists` | `bool` | Whether the entry has been retrieved yet | ### Security Properties - **Nullifier-gated retrieval**: the `retrieveAndDeleteKeyPartB` function checks that the provided nullifier has been spent in the `OpenNullifierRegistry`. This means the ZK proof must be verified and the reveal transaction must be confirmed before the key part can be retrieved. - **Atomic deletion**: the key entry is deleted in the same transaction that returns it. There is no window where the key exists after retrieval. - **No second retrieval**: once `exists` is set to `false` (via `delete`), subsequent calls revert with `KeyNotFound`. The data is gone permanently. - **Split-key security**: neither `keyPartA` (in the Phantom Key) nor `keyPartB` (on-chain) is sufficient alone to decrypt the data. An attacker who compromises only the vault or only the Phantom Key learns nothing. ## Use Cases ### Encrypted Message Delivery Alice wants to send Bob a private message. She seals the message into an Open Ghost commitment, shares the Phantom Key with Bob (via any channel), and Bob reveals it to retrieve the message. The message content never appears on-chain — only its hash is committed. After retrieval, the decryption key is deleted and the message cannot be accessed again through the protocol. ### Sealed Document Sharing A company issues a legal document to a counterparty. The document is sealed into Open Ghost, and the Phantom Key is delivered via certified mail or secure courier. The counterparty reveals the document exactly once. The one-time retrieval ensures the document was accessed by the intended recipient and cannot be re-accessed by a compromised system. ### One-Time Secret Sharing A DevOps engineer needs to share an API key with a colleague. Instead of sending it over Slack (where it persists in logs), they seal it into Open Ghost and share the Phantom Key. The colleague reveals it, the key material is decrypted, and the on-chain key part is permanently deleted. No persistent record of the secret exists on any server. ### Credential Issuance An identity provider issues a credential (e.g., KYC attestation, diploma, certification) as a sealed Open Ghost commitment. The credential holder receives a Phantom Key and can reveal the credential to any verifier. The one-time retrieval model works for credentials that should be claimed once and then held locally by the recipient. ### Privacy-Preserving Data Drops A whistleblower seals documents into Open Ghost and distributes Phantom Keys to journalists. Each key reveals a different set of documents. The whistleblower's identity is never linked to the commitments (the commit transaction can be submitted via a relayer). The documents can only be retrieved once per key, limiting uncontrolled distribution. ## Comparison with Standard Phantom Keys | Property | Standard Phantom Key (Token) | Open Ghost Key (Data) | |---|---|---| | Commitment inputs | 7 (includes tokenId, amount, policy) | 4 (includes dataHash) | | Deposit | Token burn | Fee payment | | Reveal output | Token mint to recipient | Decryption key retrieval | | On-chain key storage | None | `OpenGhostKeyVault` (one-time) | | Partial withdrawal | Yes (change commitment) | No (data is atomic) | | Merkle tree | Shared with token system | Separate (`OpenCommitmentTree`) | | Nullifier registry | Shared with token system | Separate (`OpenNullifierRegistry`) | | Primary use | Private value transfer | Sealed data sharing | --- # Phantom Identity A Phantom Identity is a **persistent, reusable anonymous identity** encoded as a PNG bearer object. Unlike a Phantom Key — which is consumed on first use — a Phantom Identity survives indefinitely. You download it once and authenticate with it forever. Each "connect" recovers a full secp256k1 private key via a zero-knowledge access proof and split-key recombination. No wallet. No account. No metadata trail. Just a PNG file that *is* your identity. This is the mechanism that turns Specter from a one-time data commitment system into a **persistent data privacy protocol**. ## The Core Innovation Phantom Keys solve one-time data operations: commit, share, reveal, done. But most real-world data access patterns are not one-time: - A credential needs to be verified on every login. - An API key needs to authenticate on every request. - A subscription key needs to be checked on every access. - A pseudonymous account needs to sign transactions over months or years. Phantom Identity addresses these patterns by **separating authentication from consumption**. The [Access Proof Circuit](../zk-proofs/access-proof-circuit.md) proves knowledge of a commitment without spending its nullifier. The commitment stays in the Merkle tree permanently. The identity holder can authenticate an unlimited number of times, each time with a fresh, unlinkable session. ## What Is a Phantom Identity? A Phantom Identity is a PNG image file with cryptographic metadata embedded in its `tEXt` chunk. When you "download your identity," you receive a standard PNG that can be stored on disk, backed up to cold storage, or printed as a QR code. Inside it: | Component | Description | |---|---| | **secp256k1 keypair** | A full elliptic curve keypair (private key + public key). The private key is split-encrypted — only half lives in the PNG. | | `secret` | BN254 field element, derived from the keypair. Part of the commitment preimage. | | `nullifierSecret` | BN254 field element. Used for accessTag derivation (authentication) and nullifier derivation (revocation). | | `blinding` | Random blinding factor for the commitment. | | `dataHash` | Hash of the identity's associated data payload. | | `quantumSecret` | 256-bit value for post-quantum commitment rotation. | | `encKeyPartA` | The local half of the split private key (XOR-encrypted). | | `commitment` | The Poseidon commitment hash: $\text{Poseidon}_4(\text{secret}, \text{nullifierSecret}, \text{dataHash}, \text{blinding})$ | | `quantumCommitment` | $\text{keccak256}(\text{quantumSecret})$ — stored on-chain, rotated on each access. | The PNG is the identity. Whoever holds the file — and optionally knows the passphrase — controls the identity. ## Key Properties ### Persistent The on-chain commitment is never consumed. The `PersistentKeyVault` stores the second half of the split key indefinitely. Unlike `OpenGhostKeyVault` (which deletes key material after one retrieval), the persistent vault **returns key material without deleting it**. The identity survives across sessions, devices, and time. ### Reusable Each "connect" generates a new Access Proof with a fresh `sessionNonce` and unique `accessTag`. There is no limit on the number of times a Phantom Identity can be used. The access proof circuit has no nullifier computation — it authenticates without consuming. ### Self-Sovereign The PNG file is a bearer instrument. There is no account server, no recovery email, no custodian. If you lose the PNG, the identity is gone. If the PNG is stolen (and unprotected by a passphrase), the thief controls the identity. This is the same security model as a hardware wallet — but the "hardware" is a file. ### Pseudonymous The secp256k1 public key derived from the Phantom Identity serves as the identity's public address. On-chain, this address has no connection to the holder's real identity. Transactions signed by this key are linked to the pseudonym, not to the human behind the PNG. ### Quantum-Resistant Each access rotates the `quantumCommitment` on-chain. Even if the Groth16/BN254 proving system is broken by a future quantum computer, an attacker would still need to provide a valid keccak256 preimage — which is believed to be quantum-resistant — to access the key vault. ## Passphrase Protection Phantom Identity supports optional AES-256-GCM encryption of the PNG metadata: 1. The user chooses a passphrase during identity generation. 2. A 256-bit encryption key is derived via **PBKDF2-SHA256** with 100,000 iterations. 3. All sensitive metadata fields in the PNG `tEXt` chunk are encrypted with AES-256-GCM. 4. On each "connect," the user enters the passphrase to decrypt the metadata before proof generation. Without the passphrase, the PNG is opaque — the encrypted fields are indistinguishable from random bytes. This adds a knowledge factor (passphrase) to the possession factor (PNG file), creating two-factor security for the identity. ## Use Cases | Use Case | Description | |---|---| | **Persistent anonymous accounts** | Maintain a pseudonymous on-chain identity over months or years. Sign transactions, interact with contracts, build reputation — all without revealing who you are. | | **Long-term credential access** | A credential (KYC attestation, diploma, certification) committed once and verified indefinitely. Each verification generates a fresh access proof. | | **Recurring authentication** | Authenticate to a service on every login using the same Phantom Identity. The service verifies the access proof without learning anything about the holder. | | **Subscription keys** | Access a subscription service by proving ownership of a committed key. The key is never revealed, never consumed, and can be verified on every access. | | **Multi-step privacy workflows** | Complex operations that span multiple transactions: commit data, verify it, update it, share it — all from the same persistent pseudonym without breaking the privacy chain. | | **Private fund management** | Use the recovered secp256k1 key to sign transactions directly. Move funds, interact with DeFi, manage assets — all from a pseudonymous identity with no wallet connection. | ## Relationship to Phantom Keys Phantom Identity is not a replacement for Phantom Keys — it is an extension. Phantom Keys remain the right tool for one-time operations: send a gift card, share a secret, transfer tokens. Phantom Identity is for everything that needs to persist. Both systems share the same cryptographic foundation: Poseidon commitments, Merkle tree membership, and Groth16 proofs on BN254. The difference is in the circuit and the vault: | | Phantom Key | Phantom Identity | |---|---|---| | Circuit | Redemption (spends nullifier) | Access Proof (does not spend nullifier) | | Vault | CommitRevealVault / OpenGhostKeyVault | PersistentKeyVault | | Lifetime | Single use | Indefinite | See [Phantom Identity vs. Phantom Keys](./vs-phantom-keys.md) for the full comparison. --- # How Phantom Identity Works The Phantom Identity lifecycle has six phases: **Generate**, **Seal**, **Connect**, **Use**, **Disconnect**, and **Revoke**. The first two happen once. Connect/Use/Disconnect repeat indefinitely. Revoke is optional and irreversible. ## Phase 1: Generate Identity generation happens entirely client-side. No network requests, no server interaction, no on-chain transactions. ### Step 1: Create the Keypair A fresh secp256k1 keypair is generated using a cryptographic random number generator: ``` privateKey = random(256 bits) // secp256k1 scalar publicKey = privateKey * G // secp256k1 point multiplication ``` This keypair is the identity's signing key. The public key becomes the pseudonymous address; the private key is what gets split and protected. ### Step 2: Derive Commitment Secrets From the keypair (or associated entropy), the following BN254 field elements are derived: | Secret | Derivation | Purpose | |---|---|---| | `secret` | HKDF-SHA256 from keypair entropy | Primary secret in commitment preimage | | `nullifierSecret` | HKDF-SHA256 from keypair entropy | accessTag derivation (connect) and nullifier derivation (revoke) | | `blinding` | HKDF-SHA256 from keypair entropy | Randomizes the commitment (hiding property) | | `dataHash` | Hash of identity-associated data | Binds the commitment to specific data content | | `quantumSecret` | Random 256 bits | Post-quantum protection (keccak256 preimage) | ### Step 3: Compute Commitments The primary commitment is a 4-input Poseidon hash: $$ \text{commitment} = \text{Poseidon}_4(\text{secret},\; \text{nullifierSecret},\; \text{dataHash},\; \text{blinding}) $$ The quantum commitment is a keccak256 hash: $$ \text{quantumCommitment} = \text{keccak256}(\text{quantumSecret}) $$ ### Step 4: Split the Private Key The secp256k1 private key is split into two parts via XOR: ``` encKeyPartA = random(256 bits) encKeyPartB = privateKey XOR encKeyPartA ``` Part A stays in the PNG. Part B goes on-chain. Neither part alone reveals the private key. See [Split-Key Architecture](./split-key-architecture.md) for the full security analysis. ### Step 5: Render the PNG All metadata is embedded in the PNG `tEXt` chunk using standard PNG metadata fields. If the user chose a passphrase, sensitive fields are encrypted with AES-256-GCM (key derived via PBKDF2-SHA256, 100,000 iterations) before embedding. The resulting PNG file is the Phantom Identity. The user downloads it and stores it securely. ## Phase 2: Seal Sealing publishes the commitment on-chain and stores the split key material in the `PersistentKeyVault`. ### Step 1: Commit to the Merkle Tree The commitment is submitted to the `OpenGhostVault`: ```solidity uint256 leafIndex = openGhostVault.commit{value: fee}(commitment); ``` The vault inserts the commitment into the `OpenCommitmentTree` (a depth-20 Merkle tree, shared with the Open Ghost data system) and returns the leaf index. A commitment fee is paid in native GHOST to prevent spam. ### Step 2: Store Key Part B The encrypted key part B, along with the quantum commitment, is stored in the `PersistentKeyVault`: ```solidity persistentKeyVault.storeKeyPartB(keyId, commitment, encKeyPartB, quantumCommitment); ``` The `keyId` is deterministically computed as: $$ \text{keyId} = \text{keccak256}(\text{commitment},\; \text{issuerAddress}) $$ Including `issuerAddress` in the key ID prevents front-running: an attacker who observes a commitment in the mempool cannot race to store their own key part B at the same key ID, because they would need to submit from the same address. The `PersistentKeyVault` stores: | Field | Type | Description | |---|---|---| | `commitment` | `bytes32` | The associated commitment hash | | `encKeyPartB` | `bytes` | Encrypted second half of the private key | | `quantumCommitment` | `bytes32` | Current quantum commitment (rotated on each access) | | `exists` | `bool` | Whether the key entry is active | Unlike `OpenGhostKeyVault`, the `PersistentKeyVault` **never deletes key material on access**. The entry persists until explicitly revoked. ## Phase 3: Connect (Repeatable) Connect is the core operation of Phantom Identity. It recovers the secp256k1 private key from the PNG and the on-chain vault, using a zero-knowledge proof to authorize access. This phase can be repeated an unlimited number of times. ### Step 1: Upload and Extract The user uploads the PNG file. The client extracts all metadata from the `tEXt` chunk. If the PNG is passphrase-protected, the user enters the passphrase and the client decrypts the metadata using AES-256-GCM. ### Step 2: Generate Access Proof The client generates a Groth16 proof using the [Access Proof Circuit](../zk-proofs/access-proof-circuit.md): **Public inputs (4):** | # | Input | Value | |---|---|---| | 0 | `root` | Current Merkle tree root (fetched from chain) | | 1 | `dataHash` | From PNG metadata | | 2 | `sessionNonce` | Fresh random value generated for this session | | 3 | `accessTag` | $\text{Poseidon}_2(\text{nullifierSecret},\; \text{sessionNonce})$ | **Private inputs (5):** | Input | Source | |---|---| | `secret` | From PNG metadata | | `nullifierSecret` | From PNG metadata | | `blinding` | From PNG metadata | | `pathElements[20]` | Merkle proof fetched from chain | | `pathIndices[20]` | Merkle proof fetched from chain | The proof demonstrates: 1. The prover knows the preimage of a commitment in the Merkle tree. 2. The accessTag is correctly derived from the prover's nullifierSecret and the session nonce. The proof does **not** compute or reveal a nullifier. The commitment is not consumed. ### Step 3: Access the Vault The client calls `accessKeyPartB` on the `PersistentKeyVault`: ```solidity bytes memory encKeyPartB = persistentKeyVault.accessKeyPartB( keyId, proof, publicInputs, quantumSecret, newQuantumCommitment ); ``` The vault performs the following checks: 1. **Proof verification**: forwards the proof and public inputs to the on-chain Groth16 verifier. Reverts if invalid. 2. **Anti-replay**: checks that the `accessTag` has not been previously recorded. Reverts if it has. Records the new `accessTag`. 3. **Quantum verification**: checks that `keccak256(quantumSecret) == stored quantumCommitment`. Reverts if it does not match. 4. **Quantum rotation**: updates the stored quantum commitment to `newQuantumCommitment` (provided by the client). 5. **Key return**: returns `encKeyPartB` **without deleting it**. ### Step 4: Recover the Private Key The client recombines the split key: ``` privateKey = encKeyPartA XOR encKeyPartB ``` The full secp256k1 private key is now available in the client's memory. The identity is "connected." ## Phase 4: Use With the recovered private key, the user can perform any on-chain operation: | Operation | Description | |---|---| | **Sign transactions** | Standard secp256k1 ECDSA signatures. No MetaMask, no external wallet. | | **Vanish/Summon** | Commit and reveal data or tokens through the Ghost Protocol. | | **Fund transfers** | Send native GHOST or ERC-20 tokens from the pseudonymous address. | | **Contract interactions** | Call any smart contract function. Deploy contracts. | | **Multi-step workflows** | Perform complex sequences of operations across multiple blocks, all from the same persistent pseudonym. | The pseudonymous address (derived from the secp256k1 public key) can hold balances, own NFTs, maintain state in contracts, and accumulate reputation — all without any link to the holder's real identity. ## Phase 5: Disconnect When the user is done, the client zeroes all sensitive key material from memory: - `privateKey` is overwritten with zeros. - `encKeyPartB` is overwritten with zeros. - Any derived signing keys or session material are cleared. The PNG file is unchanged. The on-chain vault entry is unchanged. The identity persists in both locations, ready for the next connect. ## Phase 6: Revoke (Optional) Revocation permanently destroys the on-chain key material, making the identity unrecoverable. This is an irreversible operation — it is the Phantom Identity equivalent of "burning the key." ### Step 1: Spend the Nullifier The user generates a **Redemption Proof** (not an Access Proof) using the standard redemption circuit. This proof computes the commitment's nullifier: $$ \text{nullifier} = \text{Poseidon}_2\!\big(\text{Poseidon}_2(\text{nullifierSecret},\; \text{commitment}),\; \text{leafIndex}\big) $$ The proof is submitted to `OpenGhostReveal`, which verifies it and records the nullifier in the `OpenNullifierRegistry`. The nullifier is now permanently spent. ### Step 2: Delete Key Material The user calls `revokeKey` on the `PersistentKeyVault`: ```solidity persistentKeyVault.revokeKey(keyId, nullifier); ``` The vault verifies that the nullifier is spent in the `OpenNullifierRegistry`, then permanently deletes the key entry (commitment, encKeyPartB, quantumCommitment). The `exists` flag is set to `false`. After revocation: - The on-chain key part B is gone. No one can recover the private key, even with the PNG. - The nullifier is spent. No new access proofs can authorize vault access (the vault checks `exists` before returning key material). - The identity is dead. The PNG is now a useless file. ### Why Revocation Uses a Different Circuit Revocation deliberately uses the **Redemption Circuit** (which spends a nullifier) rather than the Access Proof Circuit (which does not). This ensures that revocation is a one-time, irreversible operation — the nullifier can only be spent once, so the identity can only be revoked once. There is no way to "accidentally" revoke by using the wrong circuit during a normal connect, because the connect flow uses the Access Proof Circuit exclusively. --- # Split-Key Architecture The split-key architecture is the mechanism that makes Phantom Identity secure at rest. The secp256k1 private key — the identity's signing authority — is never stored in one place. It is split into two parts using XOR encryption. One part lives offline in the PNG bearer object. The other lives on-chain in the `PersistentKeyVault`. Neither part alone reveals anything about the private key. An attacker must compromise **both** to recover the identity. ## XOR Split-Key Encryption The split is a one-time pad construction: ``` encKeyPartA = random(256 bits) encKeyPartB = privateKey XOR encKeyPartA ``` Recovery is the inverse: ``` privateKey = encKeyPartA XOR encKeyPartB ``` ### Why XOR? XOR split is information-theoretically secure when one part is uniformly random (which `encKeyPartA` is, by construction). This means: - **encKeyPartA alone** is indistinguishable from random noise. It reveals nothing about the private key. - **encKeyPartB alone** is also indistinguishable from random noise, because it is the XOR of a random value with the private key. - **Both together** recover the private key with a single XOR operation — no key derivation, no decryption rounds, no computational overhead. This is not a threshold scheme or a Shamir split. It is a 2-of-2 XOR split — both parts are required, and each is exactly 256 bits. The simplicity is intentional. There are no parameters to misconfigure, no threshold to choose, and no reconstruction algorithm that could introduce implementation bugs. ## Storage Locations | Part | Location | Access Control | Persistence | |---|---|---|---| | `encKeyPartA` | PNG `tEXt` chunk (offline, user-held) | Physical possession of the file + optional passphrase | Indefinite (file on disk) | | `encKeyPartB` | `PersistentKeyVault` smart contract (on-chain) | Valid Access Proof (Groth16) + quantum commitment preimage | Until explicit revocation | The two storage locations have fundamentally different threat models: - The PNG is vulnerable to **local threats**: device compromise, malware, physical theft, accidental deletion. - The on-chain vault is vulnerable to **cryptographic threats**: ZK proof forgery, smart contract bugs, quantum attacks on the proving system. An attacker must succeed against **both** threat models simultaneously to compromise the identity. ## On-Chain Access Control The `encKeyPartB` stored on-chain is not freely readable. The `PersistentKeyVault` enforces three layers of access control before returning key material: ### Layer 1: Zero-Knowledge Proof The caller must provide a valid Groth16 Access Proof with 4 public inputs (root, dataHash, sessionNonce, accessTag). The on-chain verifier checks the proof against the trusted verification key. This proves the caller knows the commitment preimage — the secrets embedded in the PNG — without revealing them. An attacker without the PNG cannot generate a valid proof. The proof requires knowledge of `secret`, `nullifierSecret`, and `blinding`, which are private inputs to the circuit. ### Layer 2: Anti-Replay (accessTag) Each access must include a unique `accessTag`: $$ \text{accessTag} = \text{Poseidon}_2(\text{nullifierSecret},\; \text{sessionNonce}) $$ The vault records every accessTag it has seen. If the same accessTag is submitted twice, the transaction reverts. This prevents an attacker from replaying a previously observed proof. The `sessionNonce` is a fresh random value for each session. Because `nullifierSecret` is private (known only to the PNG holder), an attacker cannot compute a valid accessTag for a new session nonce without knowing the secret. ### Layer 3: Quantum-Resistant Commitment Each access requires the caller to provide the `quantumSecret` — the preimage of the currently stored `quantumCommitment`: $$ \text{keccak256}(\text{quantumSecret}) \stackrel{?}{=} \text{stored quantumCommitment} $$ If the check passes, the vault replaces the stored quantum commitment with a new one provided by the caller (`newQuantumCommitment`). This rotation means that even if an attacker somehow compromises the Groth16 proving system (e.g., via a future quantum computer that can forge BN254 proofs), they would also need to provide a valid keccak256 preimage — which requires breaking keccak256, a fundamentally different and harder problem. The rotation also means that each valid quantum secret is single-use. After a successful access, the previous quantum secret is no longer valid, and only the current holder (who chose the new quantum commitment) knows the next valid preimage. ## Passphrase Protection The PNG metadata can be optionally encrypted with a user-chosen passphrase, adding a knowledge factor to the possession factor. ### Encryption Scheme | Parameter | Value | |---|---| | Algorithm | AES-256-GCM | | Key derivation | PBKDF2-SHA256 | | Iterations | 100,000 | | Salt | Random 16 bytes (stored in PNG metadata, unencrypted) | | IV | Random 12 bytes (stored in PNG metadata, unencrypted) | | Auth tag | 128 bits (appended to ciphertext) | The passphrase is processed through PBKDF2 to produce a 256-bit AES key. All sensitive metadata fields (`encKeyPartA`, `secret`, `nullifierSecret`, `blinding`, `dataHash`, `quantumSecret`) are encrypted as a single AES-256-GCM payload. The salt and IV are stored in the clear alongside the ciphertext — they do not need to be secret. ### Security Analysis Without the passphrase, an attacker who obtains the PNG has: - An encrypted blob that is indistinguishable from random data (AES-256-GCM provides IND-CCA2 security). - The salt and IV, which do not help without the passphrase. - No way to generate a valid Access Proof (they cannot extract the commitment secrets). - No access to `encKeyPartA` (they cannot recover the private key even if they somehow obtain `encKeyPartB`). PBKDF2 with 100,000 iterations provides reasonable resistance against offline brute-force attacks on the passphrase. For high-security use cases, users should choose strong passphrases (high entropy). Passphrase protection does not encrypt the PNG image itself — only the metadata. The visual content of the PNG is unchanged. Do not embed sensitive visual information in the image. ## Threat Model | Threat | Attacker Has | Result | |---|---|---| | **PNG stolen, no passphrase** | `encKeyPartA` + all secrets | Attacker can generate Access Proofs and recover the private key. **Identity compromised.** | | **PNG stolen, passphrase set** | Encrypted blob | Attacker must brute-force the passphrase. With a strong passphrase, this is infeasible. | | **On-chain vault compromised** | `encKeyPartB` | Useless without `encKeyPartA`. Cannot reconstruct private key. Cannot generate Access Proofs (no secrets). | | **Both PNG + vault compromised** | `encKeyPartA` + `encKeyPartB` | Private key recovered via XOR. **Identity compromised.** (Requires simultaneous compromise of offline file and on-chain access control.) | | **Proof system broken (quantum)** | Ability to forge Groth16 proofs | Still needs keccak256 preimage (quantum commitment). Still needs `encKeyPartA` from PNG. Attack mitigated by quantum rotation. | | **Proof replayed** | Previously valid proof | accessTag already recorded. Transaction reverts. | | **PNG lost** | Nothing | Identity is permanently inaccessible. No recovery mechanism. This is a feature of bearer instruments. | | **Observer watches vault access** | Access transaction metadata | Sees the keyId and accessTag, but cannot link to the holder's real identity. Cannot recover the private key from the encrypted key parts. | ## Comparison with Other Approaches | Approach | Key Storage | Trust Assumption | Recovery | |---|---|---|---| | **MetaMask / Browser wallet** | Single location (browser storage) | Browser security | Seed phrase (centralized backup) | | **Hardware wallet** | Single location (secure element) | Hardware integrity | Seed phrase | | **MPC wallet** | Distributed across parties | Threshold of parties honest | Distributed recovery | | **Specter Split-Key** | XOR split: offline PNG + on-chain vault | Neither location alone is sufficient | No recovery. Bearer instrument. | The split-key model has no recovery mechanism by design. If the PNG is lost, the identity is gone. This is the same security philosophy as physical bearer instruments (cash, bearer bonds) — possession is control, and loss is final. --- # Phantom Identity vs. Phantom Keys Phantom Keys and Phantom Identity share the same cryptographic foundation — Poseidon commitments, Merkle tree membership proofs, and Groth16 on BN254 — but they serve fundamentally different purposes. Phantom Keys are **one-time bearer instruments** designed for send-and-forget data operations. Phantom Identity is a **persistent pseudonymous identity** designed for long-term, repeated data access. This page provides a detailed side-by-side comparison to help determine which mechanism is appropriate for a given use case. ## Feature Comparison | Property | Phantom Key | Phantom Identity | |---|---|---| | **On-chain storage** | Commitment deleted on reveal (nullifier spent) | Commitment persists indefinitely (never consumed) | | **Access model** | One-time: reveal consumes the key | Unlimited: each connect is independent | | **Nullifier behavior** | Spent on access (commitment consumed) | Spent only on revocation (optional, irreversible) | | **Primary use case** | Send-and-forget: transfers, one-time sharing, gift cards | Long-term identity: accounts, recurring access, workflows | | **ZK circuit** | Redemption Circuit (8 public inputs) | Access Proof Circuit (4 public inputs) | | **Bearer format** | Numeric key (24-30 digits) or NFC tag | PNG with embedded keypair + secrets | | **Transaction signing** | N/A (key is redeemed, not used for signing) | Direct secp256k1 signing from recovered private key | | **Quantum protection** | Optional (`quantumSecret` in V4+ keys, not enforced) | Built-in rotation (quantum commitment rotated on every access) | | **Anti-replay mechanism** | Nullifier (permanent, one-time) | accessTag (session-scoped, per-access) | | **Key recovery** | Decode numeric key, derive secrets from seed | Upload PNG, generate Access Proof, recombine split key | | **Passphrase protection** | Not supported (key is a number) | Optional AES-256-GCM encryption of PNG metadata | | **On-chain key material** | None (Standard) or one-time retrieval (Open Ghost) | Persistent split key in PersistentKeyVault | | **Revocation** | Automatic on reveal (nullifier spent) | Explicit revocation required (separate transaction) | | **Anonymity set** | All commitments in the Merkle tree | All commitments in the Merkle tree | ## Circuit Comparison The two systems use different ZK circuits optimized for their respective access patterns. ### Redemption Circuit (Phantom Keys) 8 public inputs. Designed for one-time reveal with front-running protection and value accounting. | # | Public Input | Purpose | |---|---|---| | 0 | `root` | Merkle tree root | | 1 | `nullifierHash` | Deterministic nullifier (spent on-chain, prevents double-reveal) | | 2 | `recipient` | Binds proof to recipient address (prevents front-running) | | 3 | `withdrawAmount` | Amount being withdrawn | | 4 | `changeCommitment` | Commitment for remaining balance (partial withdrawals) | | 5 | `tokenId` | Identifies the token type | | 6 | `policyId` | Policy binding (compliance, time-locks, etc.) | | 7 | `policyParamsHash` | Hash of policy parameters | ### Access Proof Circuit (Phantom Identity) 4 public inputs. Designed for repeatable authentication with no value fields and no nullifier. | # | Public Input | Purpose | |---|---|---| | 0 | `root` | Merkle tree root | | 1 | `dataHash` | Generic data identifier (credential, key, image, etc.) | | 2 | `sessionNonce` | Fresh random value per session (prevents proof reuse) | | 3 | `accessTag` | Anti-replay binding: $\text{Poseidon}_2(\text{nullifierSecret}, \text{sessionNonce})$ | ## Vault Comparison | Property | CommitRevealVault / OpenGhostKeyVault | PersistentKeyVault | |---|---|---| | Key storage | One-time (deleted after retrieval) | Persistent (returned without deletion) | | Access authorization | Nullifier spent in registry | Access Proof verified + accessTag recorded | | Quantum commitment | Not stored | Stored and rotated on every access | | Revocation | Automatic (nullifier spent = key gone) | Explicit (`revokeKey` deletes entry) | | Anti-replay | Nullifier registry (permanent) | accessTag registry (per-session) | ## When to Use Phantom Keys Phantom Keys are the right choice when: - **The operation is inherently one-time.** Sending tokens to someone. Sharing a secret once. Delivering a sealed document. Gift cards, one-time access passes, whistleblower drops. - **Simplicity matters.** A numeric key is easy to dictate over the phone, write on paper, or encode in a QR code. No file management, no passphrase to remember. - **No ongoing identity is needed.** The sender and recipient do not need a persistent relationship. The key is the entire interaction — share it, reveal it, done. - **Value transfer is involved.** The Redemption Circuit supports token amounts, partial withdrawals, and change commitments. Phantom Identity does not handle value directly. ### Example: Private Token Transfer Alice wants to send 100 GHOST to Bob without linking their addresses on-chain: 1. Alice vanishes 100 GHOST into a commitment. 2. Alice encodes the commitment secrets as a Phantom Key: `9473 0018 7376 9372 0484 1273`. 3. Alice hands Bob the number on a slip of paper. 4. Bob reveals the commitment. 100 GHOST is minted to his address. 5. The key is consumed. No ongoing relationship. ## When to Use Phantom Identity Phantom Identity is the right choice when: - **The interaction is ongoing.** A user needs to authenticate repeatedly. A credential needs to be verified over time. A subscription needs to be checked on every access. - **Signing authority is needed.** The user needs to sign transactions from a persistent pseudonymous address. Phantom Identity provides a full secp256k1 keypair; Phantom Keys do not. - **Multi-step workflows span multiple sessions.** Commit data in one session, verify it in another, update it in a third — all from the same identity, all unlinkable to the real user. - **Stronger security is required.** Split-key storage, optional passphrase protection, and quantum-resistant commitment rotation provide defense-in-depth that a simple numeric key cannot offer. ### Example: Persistent Anonymous Account A user wants to participate in an on-chain governance system without revealing their real identity: 1. The user generates a Phantom Identity (downloads a PNG). 2. The user seals the identity on-chain (commitment + split key stored). 3. Before each governance vote, the user connects: uploads the PNG, enters the passphrase, generates an Access Proof, recovers the signing key. 4. The user signs and submits a vote from the pseudonymous address. 5. The user disconnects. The signing key is zeroed from memory. 6. Over months, the pseudonymous address builds a voting history — but it is never linked to the user's real identity. ## Decision Matrix | Scenario | Phantom Keys | Phantom Identity | |---|---|---| | Send tokens privately | Best fit | Overkill | | One-time secret sharing | Best fit | Overkill | | Gift card / event ticket | Best fit | Overkill | | Credential issued once, verified once | Best fit | Overkill | | Credential verified repeatedly | Not possible (consumed on first use) | Best fit | | Persistent pseudonymous account | Not possible (no signing key) | Best fit | | Recurring API authentication | Not possible (consumed on first use) | Best fit | | Long-term encrypted data access | Not possible (key deleted on retrieval) | Best fit | | Multi-step privacy workflow | Not possible (identity doesn't persist) | Best fit | | Subscription/membership key | Not possible (consumed on first use) | Best fit | | Physical bearer instrument (NFC card) | Best fit | Not ideal (PNG format) | | Offline, air-gapped sharing | Best fit (numeric format) | Possible (file transfer) | ## Using Both Together Phantom Keys and Phantom Identity are complementary. A common pattern is to use a Phantom Identity as the persistent account and Phantom Keys for individual transactions within that account: The identity persists. The keys are ephemeral. The identity issues keys; the keys are consumed by recipients. This separation keeps the persistent pseudonym clean while enabling one-time sharing operations. --- # Access Proofs An Access Proof is a Groth16 zero-knowledge proof that demonstrates knowledge of a commitment preimage and Merkle tree membership **without consuming the commitment**. It is the cryptographic primitive that enables persistent, repeatable data access in Specter. Unlike the Redemption Proof — which spends a nullifier and permanently consumes the commitment — an Access Proof authenticates and leaves the commitment intact. This is the mechanism that separates authentication from consumption. A user can prove "I own this data" an unlimited number of times without ever destroying the proof of ownership. ## Why Access Proofs Exist The Specter data protocol supports two fundamentally different access patterns: | Pattern | Mechanism | Example | |---|---|---| | **One-time access** | Redemption Proof (spends nullifier) | Gift card redemption, secret sharing, token transfer | | **Persistent access** | Access Proof (no nullifier) | Credential verification, recurring authentication, Phantom Identity | Most real-world data operations are persistent. A diploma is verified on every job application. An API key authenticates on every request. A subscription key is checked on every access. Building these patterns on top of one-time redemption would require re-committing data after every access — an expensive, complex, and fragile approach. Access Proofs solve this cleanly: the commitment stays in the tree, and the proof can be regenerated for every session. ## The Access Proof Circuit The [Access Proof Circuit](../zk-proofs/access-proof-circuit.md) (`accessProof.circom`) is deliberately minimal. It has 4 public inputs and 5 private inputs — roughly half the complexity of the 8-input Redemption Circuit. ### Public Inputs | # | Input | Type | Description | |---|---|---|---| | 0 | `root` | Field element | The Merkle tree root being proven against. Must match a known on-chain root. | | 1 | `dataHash` | Field element | Hash of the data being accessed. Binds the proof to specific content. | | 2 | `sessionNonce` | Field element | Fresh random value generated for this access session. | | 3 | `accessTag` | Field element | Anti-replay tag: $\text{Poseidon}_2(\text{nullifierSecret},\; \text{sessionNonce})$ | ### Private Inputs | Input | Type | Description | |---|---|---| | `secret` | Field element | The user's secret (part of commitment preimage) | | `nullifierSecret` | Field element | Used for accessTag derivation (but NOT for nullifier computation) | | `blinding` | Field element | Random blinding factor | | `pathElements[20]` | Field element[20] | Sibling hashes along the Merkle proof path | | `pathIndices[20]` | Bit[20] | Left/right direction bits for each tree level | ### What the Circuit Proves The circuit enforces three constraints: 1. **Commitment computation**: The prover knows values (`secret`, `nullifierSecret`, `dataHash`, `blinding`) that hash to a commitment in the tree. The commitment is computed as $\text{Poseidon}_4(\text{secret}, \text{nullifierSecret}, \text{dataHash}, \text{blinding})$. 2. **Merkle tree membership**: The computed commitment is a leaf in the Merkle tree at the given root. The circuit walks 20 levels using the provided path elements and indices, recomputing the root and constraining it to match the public input. 3. **accessTag correctness**: The `accessTag` is correctly derived from the prover's `nullifierSecret` and the session's `sessionNonce`. This binds the proof to a specific session without revealing the `nullifierSecret`. ### What the Circuit Does NOT Prove The Access Proof Circuit deliberately omits: - **Nullifier computation**: no nullifier is derived, no commitment is consumed. - **Recipient binding**: no address is bound to the proof (session nonce replaces this role). - **Value fields**: no token amount, no token ID, no change commitment. - **Policy binding**: no policy ID, no policy parameters. These omissions are the entire point. The circuit is stripped down to pure authentication — "I know the preimage of a commitment in this tree" — with nothing that would make the operation one-time or value-bearing. ## Proof Generation and Verification Flow ## Anti-Replay: The accessTag Mechanism The accessTag prevents proof replay without consuming the commitment. It is a session-scoped binding that ensures each proof can only be used once. ### How It Works 1. The verifier (or the protocol) provides a fresh `sessionNonce` for each access session. 2. The prover computes: $\text{accessTag} = \text{Poseidon}_2(\text{nullifierSecret},\; \text{sessionNonce})$ 3. The prover includes both `sessionNonce` and `accessTag` as public inputs to the ZK proof. 4. The circuit verifies that `accessTag` is correctly derived from the prover's `nullifierSecret` and the provided `sessionNonce`. 5. The on-chain contract records the `accessTag` in a mapping. 6. If the same `accessTag` is submitted again, the transaction reverts. ### Why This Is Sufficient | Property | Guarantee | |---|---| | **Uniqueness** | A new `sessionNonce` produces a new `accessTag` (Poseidon collision resistance). | | **Binding** | The `accessTag` is bound to the prover's `nullifierSecret` (ZK circuit enforces this). An attacker cannot compute a valid accessTag without knowing the secret. | | **Non-replayability** | Each `accessTag` is recorded on-chain. Submitting the same proof twice fails. | | **Non-consumption** | The `accessTag` does not involve the commitment hash or leaf index. The commitment is untouched. | ### accessTag vs. Nullifier | | Nullifier | accessTag | |---|---|---| | Derivation | $\text{Poseidon}_2(\text{Poseidon}_2(\text{nullifierSecret}, \text{commitment}), \text{leafIndex})$ | $\text{Poseidon}_2(\text{nullifierSecret}, \text{sessionNonce})$ | | Deterministic for same commitment? | Yes (always the same) | No (different per session) | | Effect on commitment | Permanently consumed | No effect | | Reusability of underlying data | None (one-time) | Unlimited | | Storage growth | Bounded (one per commitment) | Unbounded (one per session) | The trade-off is storage growth. Nullifiers are bounded — there is at most one nullifier per commitment, so the nullifier registry grows linearly with the number of commitments. Access tags are unbounded — every session adds a new entry. In practice, the `PersistentKeyVault` can implement pruning strategies (e.g., expiring old access tags after a sufficient time window) to manage storage growth. ## Quantum-Resistant Layer Access Proofs in the `PersistentKeyVault` include an additional quantum-resistant verification layer: 1. The stored `quantumCommitment` is $\text{keccak256}(\text{quantumSecret})$. 2. On each access, the caller provides the `quantumSecret` (preimage) and a `newQuantumCommitment`. 3. The vault verifies: $\text{keccak256}(\text{quantumSecret}) \stackrel{?}{=} \text{stored quantumCommitment}$ 4. If valid, the vault replaces the stored commitment with `newQuantumCommitment`. This rotation means that even if the Groth16/BN254 proving system is compromised (e.g., by a quantum computer), an attacker must also provide a valid keccak256 preimage to access the vault. keccak256 preimage resistance is believed to hold against quantum adversaries (Grover's algorithm provides at most a quadratic speedup, reducing 256-bit security to 128-bit — still computationally infeasible). ## Where Access Proofs Are Used | System | How Access Proofs Are Used | |---|---| | **Phantom Identity** | Each "connect" generates an Access Proof to authorize split-key recombination from the `PersistentKeyVault`. | | **Persistent credential verification** | A credential holder proves ownership of a committed credential on each verification request. | | **Recurring API authentication** | An API key holder generates an Access Proof on each API call to prove key ownership. | | **Subscription access** | A subscriber proves commitment ownership on each content access. | | **Any persistent data pattern** | Any use case where data needs to be proven repeatedly without being consumed. | Access Proofs are the foundation that makes Specter a persistent data privacy protocol. Without them, every data operation would be one-time — commit, reveal, done. With them, committed data becomes a permanent, reusable, privately-accessible resource. --- # Bearer Formats Specter's data protocol is designed around **bearer instruments** — objects where possession equals access. The cryptographic secrets needed to generate a ZK proof and access committed data are encoded directly into a physical or digital artifact. There is no account to log into, no server to authenticate against, no custodian to trust. You hold the object; you control the data. The protocol supports four bearer formats, each optimized for a different use case and threat model. ## Overview | Format | Data Capacity | Key Type | Persistence | Best For | |---|---|---|---|---| | **Numeric Key** | Seed (128 bits) + metadata | Phantom Key (one-time) | Paper, memory, voice | Simple transfers, gift cards, offline sharing | | **PNG Bearer Object** | Full keypair + all secrets | Phantom Identity (persistent) | Digital file | Persistent accounts, recurring access | | **PDF Voucher** | Metadata + QR + numeric fallback | Phantom Key (one-time) | Printable document | Branded vouchers, formal issuance | | **NFC Tag** | Encrypted seed + metadata | Phantom Key (one-time) | Physical tag | Gift cards, access cards, event tickets | ## Numeric Keys Numeric Keys are the simplest bearer format: a human-readable string of digits that encodes a 128-bit seed and associated metadata. They are the primary format for [Phantom Keys](../phantom-keys/overview.md). ### Format ``` 9473 0018 7376 9372 0484 1273 ``` Displayed in groups of four digits for readability. The full encoding contains: | Field | Description | Size | |---|---|---| | Version byte | Format version (V1-V7) | 1 digit | | Seed | Root entropy (128 bits) | ~39 digits | | Amount | Scientific notation (significand + exponent) | Variable | | Leaf index | Variable-length encoding of tree position | Variable | | Checksum | Error detection (CRC-based) | 4 digits | Total length: 24-30 digits depending on version and amount encoding. ### Seed-Based Derivation All cryptographic secrets are deterministically derived from the 128-bit seed using HKDF-SHA256: | Secret | HKDF Info String | Purpose | |---|---|---| | `secret` | `ghostcoin-secret-v1` | Primary commitment preimage input | | `nullifierSecret` | `ghostcoin-nullifier-v1` | Nullifier / accessTag derivation | | `blinding` | `ghostcoin-blinding-v1` | Commitment randomization | | `changeBlinding` | `ghostcoin-change-blinding-v1` | Partial withdrawal change commitment | | `quantumSecret` | `ghostcoin-quantum-v1` | Post-quantum commitment (V4+) | Each HKDF output is 32 bytes, reduced modulo the BN254 scalar field prime to produce a valid field element. The seed is the single source of entropy — everything else is derived. ### Properties | Property | Value | |---|---| | **Human-readable** | Groups of four digits. Can be spoken aloud, read over the phone, or dictated. | | **Paper-friendly** | Short enough to write on a card, receipt, or sticky note. | | **Error detection** | 4-digit checksum catches transcription errors. | | **Self-contained** | The number encodes everything needed to reveal the commitment (plus chain access for the Merkle proof). | | **No encryption** | The numbers are the secret. Anyone who reads them controls the commitment. | ### When to Use Numeric Keys are ideal for one-time transfers where simplicity and universality matter more than multi-factor security. Gift cards, printed vouchers, verbal sharing, low-tech environments. ## PNG Bearer Objects PNG Bearer Objects are the format for [Phantom Identity](../phantom-identity/overview.md). They encode a full secp256k1 keypair, all commitment secrets, and the local half of the split key — all embedded in a standard PNG image file. ### Structure The PNG file uses the standard `tEXt` metadata chunk (defined in the PNG specification) to store identity data. The visual content of the image can be anything — a logo, an abstract pattern, a QR code — the cryptographic payload is in the metadata, not the pixels. | Metadata Field | Description | Encrypted? | |---|---|---| | `publicKey` | secp256k1 public key (hex) | No | | `encKeyPartA` | Local half of split private key | Yes (if passphrase set) | | `secret` | BN254 field element | Yes (if passphrase set) | | `nullifierSecret` | BN254 field element | Yes (if passphrase set) | | `blinding` | BN254 field element | Yes (if passphrase set) | | `dataHash` | Hash of associated data | Yes (if passphrase set) | | `quantumSecret` | 256-bit quantum commitment preimage | Yes (if passphrase set) | | `commitment` | Poseidon commitment hash | No | | `quantumCommitment` | keccak256(quantumSecret) | No | | `leafIndex` | Position in the Merkle tree | No | | `salt` | PBKDF2 salt (if passphrase set) | No | | `iv` | AES-GCM IV (if passphrase set) | No | | `encrypted` | Boolean flag | No | ### Passphrase Encryption When a passphrase is set, all sensitive fields are encrypted as a single AES-256-GCM payload: Without the passphrase, the encrypted fields are indistinguishable from random data. The salt and IV are stored in the clear — they do not need to be secret. ### Properties | Property | Value | |---|---| | **Persistent** | Supports unlimited connect/disconnect cycles via Access Proofs. | | **Self-sovereign** | No server, no account, no recovery. The file is the identity. | | **Two-factor** (optional) | Possession (file) + knowledge (passphrase). | | **Portable** | Standard PNG file. Works on any device that can read files. | | **Backup-friendly** | Copy the file to cold storage, USB drive, or print the QR code. | ### When to Use PNG Bearer Objects are the format for persistent anonymous identities, recurring authentication, and any workflow that requires a signing key over multiple sessions. ## PDF Vouchers PDF Vouchers embed Phantom Key metadata in PDF document properties (`setProperties`), combined with a visual presentation that includes QR codes and a printed numeric key for offline recovery. ### Structure | Component | Location | Purpose | |---|---|---| | Metadata payload | PDF `setProperties` (document properties) | Machine-readable key data | | QR code | Visual page content | Scan-to-redeem for mobile | | Numeric key | Printed text on page | Human-readable fallback for offline recovery | | Branding | Visual page content | Issuer logo, denomination, instructions | ### Dual Recovery Paths PDF Vouchers provide two independent recovery paths: 1. **QR code**: scan with a phone camera. The QR encodes the same data as the numeric key. Fastest path for mobile users. 2. **Numeric key**: printed in plain text. The ultimate fallback — works even if the QR is damaged, the PDF is printed in low resolution, or the user has no camera. 3. **PDF metadata**: programmatic extraction via `setProperties`. Used by automated systems that process vouchers in bulk. ### Properties | Property | Value | |---|---| | **Printable** | Standard PDF. Print on any printer. | | **Branded** | Issuer can customize visual design, add logos, instructions. | | **Redundant** | Three independent recovery paths (QR, numeric, metadata). | | **Offline-capable** | Printed voucher works without any device until the reveal step. | | **One-time** | Backed by Phantom Keys (nullifier spent on reveal). | ### When to Use PDF Vouchers are ideal for formal issuance: employee credentials, loyalty rewards, branded gift cards, event tickets, institutional certificate delivery. The printable format and branding support make them suitable for non-technical recipients. ## NFC Tags NFC Tags encode Phantom Key data on **NTAG 424 DNA** chips — tamper-evident, cryptographically authenticated physical bearer instruments. ### Hardware: NTAG 424 DNA The NTAG 424 DNA is an NFC chip manufactured by NXP Semiconductors with hardware-level security features: | Feature | Description | |---|---| | **SUN Authentication** | Secure Unique NFC. Each tap generates a unique, cryptographically signed response. Prevents cloning. | | **AES-128 encryption** | On-chip symmetric encryption. Data is encrypted at the hardware level. | | **Tamper detection** | The chip can detect physical tampering attempts. | | **Read counters** | Hardware counter increments on each read. Detects unauthorized reads. | | **Multiple file areas** | Separate storage regions with independent access controls. | ### Data Layout | Region | Content | Access Control | |---|---|---| | **Public URL** | NDEF URL record pointing to the Specter redemption page. Includes SUN authentication parameters (PICC data + MAC). | Read: open. Write: locked. | | **Encrypted payload** | Phantom Key seed + metadata, AES-128 encrypted. | Read: requires authentication. Write: locked after issuance. | | **SUN parameters** | Encrypted PICC data + CMAC. Verified server-side to confirm tag authenticity. | Generated by hardware on each tap. | ### Tap-to-Redeem Flow ### Anti-Cloning The NTAG 424 DNA's SUN authentication makes cloning practically impossible: 1. Each tap generates a fresh AES-CMAC using the chip's internal key (never exposed). 2. The CMAC covers the chip's unique ID (UID) and the current read counter. 3. The verification server holds the chip's AES key (provisioned during manufacturing/issuance). 4. A cloned chip cannot produce valid CMACs because it does not have the internal key. 5. A replayed CMAC will fail because the read counter has advanced. ### Properties | Property | Value | |---|---| | **Physical bearer** | Tangible object. Hand it to someone. | | **Tamper-evident** | Hardware detection of physical intrusion. | | **Clone-resistant** | SUN authentication prevents duplication. | | **Encrypted at rest** | AES-128 on-chip encryption. | | **Tap-to-redeem** | Single NFC tap initiates the reveal flow. | | **One-time** | Backed by Phantom Keys (nullifier spent on reveal). | ### Use Cases | Use Case | Description | |---|---| | **Gift cards** | Physical cards with NFC chips. Tap to redeem GHOST tokens or access data. | | **Access cards** | Prove credential ownership by tapping an NFC-enabled reader. | | **Event tickets** | Tamper-proof, non-clonable tickets. SUN authentication prevents counterfeits. | | **Physical key distribution** | Distribute encryption keys or API credentials as NFC tags. One tap, one retrieval, key material deleted. | | **Supply chain verification** | Attach NFC tags to physical goods. Each tap proves authenticity via on-chip cryptography. | ## Format Selection Guide | Requirement | Recommended Format | |---|---| | Simplest possible sharing | Numeric Key | | Persistent identity | PNG Bearer Object | | Printable, branded voucher | PDF Voucher | | Physical tamper-proof card | NFC Tag | | Verbal/phone sharing | Numeric Key | | Multi-factor security | PNG Bearer Object (with passphrase) | | Automated batch processing | PDF Voucher (metadata extraction) | | Anti-counterfeiting | NFC Tag (SUN authentication) | | Air-gapped environment | Numeric Key or printed PDF | | Mobile-first redemption | NFC Tag or PDF Voucher (QR scan) | --- # Encryption & Key Management Every cryptographic operation in the Specter data protocol happens **client-side**. The chain stores commitments, nullifiers, and encrypted key fragments. It never sees plaintext data, unencrypted private keys, or user passphrases. This page documents every cryptographic primitive used in the protocol, how they compose, and why each was chosen. ## Cryptographic Primitives | Primitive | Algorithm | Use | Security Level | |---|---|---|---| | Commitment hashing | Poseidon (BN254) | Merkle tree commitments, nullifier derivation, accessTag computation | ~127-bit (BN254 field) | | Zero-knowledge proofs | Groth16 on BN254 | Proof of commitment knowledge + Merkle membership | 128-bit (BN254 pairing) | | Symmetric encryption | AES-256-GCM | Passphrase protection of PNG metadata, voucher encryption | 256-bit | | Password-based KDF | PBKDF2-SHA256 | Passphrase to AES key derivation | Configurable (iterations) | | Seed-based KDF | HKDF-SHA256 | Seed to commitment secrets derivation | 256-bit (SHA-256) | | Split-key encryption | XOR (one-time pad) | Private key splitting for Phantom Identity | Information-theoretic | | Quantum commitment | keccak256 | Post-quantum commitment rotation | ~128-bit post-quantum | | Key binding | keccak256 | keyId derivation (commitment + issuer binding) | 256-bit | | NFC encryption | AES-128 (NTAG 424 DNA) | On-chip tag encryption | 128-bit | | NFC authentication | AES-CMAC (SUN) | Tag authenticity verification | 128-bit | ## AES-256-GCM: Symmetric Encryption AES-256-GCM is used wherever data needs to be encrypted at rest with a known key: passphrase-protected PNG metadata and encrypted voucher payloads. ### Parameters | Parameter | Value | Notes | |---|---|---| | Key size | 256 bits | Derived via PBKDF2 (passphrase) or HKDF (seed) | | IV size | 96 bits (12 bytes) | Random, unique per encryption | | Auth tag size | 128 bits | Appended to ciphertext | | Mode | GCM (Galois/Counter Mode) | Authenticated encryption (confidentiality + integrity) | | Implementation | Browser WebCrypto API | `crypto.subtle.encrypt({name: "AES-GCM", ...})` | ### Why GCM? GCM provides **authenticated encryption** — it protects both confidentiality (the data is unreadable without the key) and integrity (any modification of the ciphertext is detected). This prevents an attacker from tampering with encrypted PNG metadata without detection. A non-authenticated mode like AES-CTR would encrypt the data but would not detect if an attacker flipped bits in the ciphertext. ### Security Properties | Property | Guarantee | |---|---| | **IND-CCA2** | Ciphertext is indistinguishable from random data under adaptive chosen-ciphertext attack. | | **Integrity** | Any modification to the ciphertext (including truncation) is detected via the auth tag. | | **IV uniqueness** | Random 96-bit IV. Probability of collision is negligible for practical usage volumes ($< 2^{32}$ encryptions per key). | AES-GCM is catastrophically broken if the same IV is used twice with the same key. The protocol generates a fresh random IV for every encryption operation and stores it alongside the ciphertext. Never reuse an IV. ## PBKDF2-SHA256: Password-Based Key Derivation PBKDF2 transforms a user-chosen passphrase into a 256-bit AES key suitable for encryption. It is used exclusively for Phantom Identity passphrase protection. ### Parameters | Parameter | Value | |---|---| | Hash function | SHA-256 | | Iterations | 100,000 | | Salt | 16 bytes (random, stored in PNG metadata) | | Output length | 256 bits (32 bytes) | | Implementation | Browser WebCrypto API (`crypto.subtle.deriveKey`) | ### Why 100,000 Iterations? The iteration count is a trade-off between user experience (derivation time) and brute-force resistance. At 100,000 iterations: | Attacker Capability | Time to Crack (40-bit passphrase entropy) | Time to Crack (80-bit passphrase entropy) | |---|---|---| | Single CPU core | ~35 years | ~$10^{12}$ years | | GPU cluster (10,000 cores) | ~1.3 days | ~$10^{8}$ years | | ASIC farm | Hours | ~$10^{6}$ years | For high-value identities, users should choose passphrases with at least 80 bits of entropy (approximately 6+ random words from a large dictionary). The iteration count provides a speed bump, not a wall — passphrase strength is the primary defense. ### Salt Purpose The random salt ensures that identical passphrases produce different AES keys. Without a salt, an attacker could precompute a rainbow table mapping common passphrases to AES keys. With a 16-byte random salt, each PNG has a unique derivation path, forcing the attacker to brute-force each PNG independently. ## HKDF-SHA256: Seed-Based Key Derivation HKDF (HMAC-based Key Derivation Function) deterministically derives multiple independent secrets from a single 128-bit seed. It is the core derivation function for [Phantom Keys](../phantom-keys/overview.md). ### Derivation Table | Output | HKDF Info String | Output Size | Reduction | |---|---|---|---| | `secret` | `ghostcoin-secret-v1` | 32 bytes | mod BN254 field prime | | `nullifierSecret` | `ghostcoin-nullifier-v1` | 32 bytes | mod BN254 field prime | | `blinding` | `ghostcoin-blinding-v1` | 32 bytes | mod BN254 field prime | | `changeBlinding` | `ghostcoin-change-blinding-v1` | 32 bytes | mod BN254 field prime | | `quantumSecret` | `ghostcoin-quantum-v1` | 32 bytes | None (raw 256-bit value) | ### Why HKDF? HKDF is a standardized KDF (RFC 5869) with formal security proofs. Each derivation uses a unique `info` string, which acts as a domain separator — the outputs for different `info` strings are cryptographically independent even from the same seed. This means compromising `secret` reveals nothing about `nullifierSecret`, and vice versa. ### BN254 Field Reduction HKDF outputs 32 bytes (256 bits), but BN254 field elements must be less than the scalar field prime ($r \approx 2^{254}$). The 256-bit output is reduced modulo $r$. Because the output is approximately uniformly distributed over 256 bits, the bias introduced by modular reduction is negligible ($< 2^{-252}$). ## Split-Key XOR Architecture The [split-key architecture](../phantom-identity/split-key-architecture.md) protects the secp256k1 private key in Phantom Identity by splitting it across two storage locations. ### Construction ``` encKeyPartA = random(256 bits) // stored in PNG encKeyPartB = privateKey XOR encKeyPartA // stored on-chain ``` ### Recovery ``` privateKey = encKeyPartA XOR encKeyPartB ``` ### Security Analysis | Property | Guarantee | |---|---| | **Information-theoretic security** | When `encKeyPartA` is uniformly random, `encKeyPartB` is also uniformly random regardless of `privateKey`. Neither part alone leaks any information. | | **No computational assumptions** | XOR security does not depend on the hardness of any mathematical problem. It is unconditionally secure for the split — no quantum computer or future algorithm can break it. | | **Both parts required** | Recovery requires both parts. An attacker with only one part has zero bits of information about the private key. | The XOR split is the simplest possible secret sharing scheme. It is a degenerate case of a 2-of-2 secret sharing scheme where the shares are exactly the same size as the secret. The simplicity eliminates implementation risk — there are no parameters to misconfigure, no threshold to set, and no reconstruction algorithm beyond a single XOR. ## Quantum-Resistant keccak256 Commitments The quantum commitment layer provides defense against a future adversary with a quantum computer capable of breaking the BN254 pairing (and thus forging Groth16 proofs). ### Mechanism | Step | Operation | When | |---|---|---| | **Initial** | $\text{quantumCommitment}_0 = \text{keccak256}(\text{quantumSecret}_0)$ | Identity creation (seal) | | **Access $n$** | Verify: $\text{keccak256}(\text{quantumSecret}_n) == \text{quantumCommitment}_n$ | Each connect | | **Rotation** | $\text{quantumCommitment}_{n+1} = \text{keccak256}(\text{quantumSecret}_{n+1})$ | After verification | ### Why keccak256? keccak256 (SHA-3 family) is a hash function based on the sponge construction. Its preimage resistance relies on the security of the Keccak permutation, which is unrelated to the discrete logarithm problem or elliptic curve pairings. Grover's algorithm provides at most a quadratic speedup for hash preimage search, reducing 256-bit security to approximately 128-bit equivalent — still computationally infeasible. | Threat | BN254 (Groth16) | keccak256 | |---|---|---| | Classical computer | Secure (~128 bits) | Secure (~256 bits) | | Quantum computer (Shor) | **Broken** (polynomial time for discrete log) | Not applicable (no algebraic structure) | | Quantum computer (Grover) | N/A | Reduced to ~128 bits (still secure) | ### Rotation Benefits The rotation ensures that each quantum secret is single-use. After a successful access: 1. The previous `quantumSecret` is no longer valid (the commitment has been updated). 2. Only the current holder knows the next valid preimage (they chose `newQuantumCommitment`). 3. An attacker who records all on-chain quantum commitments learns nothing — they are all keccak256 outputs, and finding the preimage requires breaking keccak256. ## Key Binding: keyId Derivation The `keyId` that addresses entries in the `PersistentKeyVault` is derived as: $$ \text{keyId} = \text{keccak256}(\text{commitment},\; \text{issuerAddress}) $$ ### Why Include issuerAddress? Including the issuer's Ethereum address in the key ID prevents **front-running attacks** during the seal phase: | Attack | Without issuerAddress | With issuerAddress | |---|---|---| | Attacker sees commitment in mempool | Attacker races to store their own `encKeyPartB` at `keyId = keccak256(commitment)` | Attacker cannot match the `keyId` because `keccak256(commitment, attackerAddress) != keccak256(commitment, issuerAddress)` | | Result | Attacker's key part B is stored; real user's is rejected | Attacker's entry is at a different keyId; no collision | The issuer address is the `msg.sender` of the `storeKeyPartB` transaction. The vault enforces that the caller's address matches the one used in the keyId derivation. This binds the key storage to the identity creator and prevents anyone else from claiming the same key slot. ## Client-Side Execution Model All cryptographic operations that handle plaintext data or private keys execute in the user's browser or local environment. The protocol's on-chain components never see: | Never On-Chain | Where It Lives | |---|---| | Plaintext data | Client memory only | | Private keys (secp256k1) | Client memory during session, split-encrypted at rest | | Commitment secrets (secret, nullifierSecret, blinding) | PNG metadata (optionally encrypted) or derived from numeric key seed | | Passphrases | Client memory during decryption, never stored | | quantumSecret (current preimage) | Client memory during access, PNG metadata at rest | | Groth16 witness | Client memory during proof generation | The chain stores: - **Commitments**: Poseidon hashes (opaque 254-bit values). - **Nullifiers**: Poseidon hashes (opaque, unlinkable to commitments). - **Access tags**: Poseidon hashes (opaque, session-scoped). - **Encrypted key parts**: `encKeyPartB` (useless without the corresponding `encKeyPartA`). - **Quantum commitments**: keccak256 outputs (opaque without preimage). - **Proofs**: Groth16 proof data ($\pi_a$, $\pi_b$, $\pi_c$) — verifiable but zero-knowledge. This architecture means that even a fully compromised chain — validators, RPC nodes, block explorers, everything — reveals nothing about users' data. The on-chain state is a collection of opaque hashes, encrypted fragments, and zero-knowledge proofs. The plaintext exists only in the client's memory, for only as long as the client needs it. --- # 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: ```solidity 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: | Constraint | Reason | |-----------|--------| | Called via `staticcall` | No state writes permitted -- policies are pure validation logic | | 100,000 gas limit | Prevents policies from performing expensive computation or griefing | | Must return `bool` | `true` to allow the reveal, `false` to reject | | No external calls | Policy contracts should not depend on other contract state (reliability) | | No constructor / no admin | Reference 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. ```solidity 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 Type | Policy Example | Effect | |-----------|---------------|--------| | Tokens (GHOST) | TimelockExpiry | Tokens cannot be revealed before unlock time or after expiry | | Credentials | DestinationRestriction | Degree attestation can only be delivered to a specific employer | | API Keys | TimelockExpiry | Key is only valid during a specific time window | | Encrypted Documents | ThresholdWitness | 3-of-5 board members must sign before document is unsealed | | Images | DestinationRestriction | Provenance proof delivered only to the authenticated buyer | | Bearer Instruments | TimelockExpiry + DestinationRestriction | Ticket 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: | Policy | Parameters | Constraint | |--------|-----------|-----------| | [TimelockExpiry](./timelock-expiry.md) | `lockUntil`, `expiresAt` | Reveal only within a time window | | [DestinationRestriction](./destination-restriction.md) | `allowedRecipient` or Merkle allowlist | Reveal only to specific address(es) | | [ThresholdWitness](./threshold-witness.md) | Witness set, signatures, threshold | M-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. --- # Circuit-Level Policy Binding Policies in the Specter protocol are not enforced by trust or convention. They are **embedded as public inputs in the ZK proof itself**, making them cryptographically inseparable from the commitment they constrain. This page explains how `policyId` and `policyParamsHash` are bound into the circuit, why they cannot be forged, and how on-chain verification confirms them. ## Public Inputs The token redemption circuit (7-input commitment variant) produces **8 public inputs** that are visible to the on-chain verifier: | Index | Field | Type | Description | |-------|-------|------|-------------| | 0 | `root` | bytes32 | Merkle tree root the commitment belongs to | | 1 | `nullifier` | bytes32 | Deterministic nullifier derived from commitment secrets | | 2 | `amount` | uint256 | Token amount being revealed | | 3 | `recipient` | address | Address receiving the tokens/data | | 4 | `changeCommitment` | bytes32 | New commitment for any unrevealed remainder (0 if full reveal) | | 5 | `tokenId` | bytes32 | Poseidon hash of the token contract address | | 6 | `policyId` | address | Policy contract address (0 if no policy) | | 7 | `policyParamsHash` | bytes32 | Hash of policy parameters, reduced to BN254 field | Inputs 6 and 7 carry the policy binding. The prover must supply the correct `policyId` and `policyParamsHash` that were used when the original commitment was created. If the prover supplies different values, the Poseidon hash will not match the committed value in the Merkle tree, and the Merkle membership proof will fail. ## Dummy Quadratic Constraints In a ZK circuit, a public input that is declared but not constrained could theoretically be set to any value by the prover. To prevent this, the circuit includes **dummy quadratic constraints** that force the prover to include the actual policy values: ``` policyIdSquare === policyId * policyId policyParamsHashSquare === policyParamsHash * policyParamsHash ``` These constraints serve a specific purpose: they ensure the circuit's constraint system references `policyId` and `policyParamsHash` as active wires. Without them, an optimizer could treat these inputs as unconstrained, allowing a prover to substitute arbitrary values. The quadratic constraint is the simplest form that prevents this -- it requires the prover to commit to a specific value for each field. The key insight is that `policyId` and `policyParamsHash` are also inputs to the Poseidon commitment hash: ``` commitment = Poseidon7(secret, nullifierSecret, tokenId, amount, blinding, policyId, policyParamsHash) ``` Since the commitment must match a leaf in the Merkle tree, and the Merkle root is a public input verified on-chain, the prover cannot change `policyId` or `policyParamsHash` without also invalidating the Merkle proof. The dummy constraints provide a second layer of defense by ensuring these values are explicitly present in the proof's public outputs. ## Computing policyParamsHash The `policyParamsHash` is derived from the policy's ABI-encoded parameters: ``` policyParamsHash = keccak256(abi.encode(policyParams)) % BN254_SCALAR_FIELD ``` Where the BN254 scalar field prime is: ``` p = 21888242871839275222246405745257275088548364400416034343698204186575808495617 ``` The modular reduction is necessary because `keccak256` produces a 256-bit output, but the BN254 circuit operates over a ~254-bit field. Values exceeding the field prime are reduced modulo `p` by the Circom compiler. The on-chain verifier performs the same reduction to ensure the hash matches: ```solidity bytes32 reducedHash = bytes32(uint256(keccak256(policyParams)) % BN254_SCALAR_FIELD); if (reducedHash != policyParamsHash) { revert PolicyValidationFailed(); } ``` This means the policy parameters are bound twice: 1. **In the commitment** -- `policyParamsHash` is one of the 7 inputs to the Poseidon hash 2. **On-chain during reveal** -- the vault recomputes `keccak256(policyParams) % p` from the submitted parameters and checks it matches the proof's public input ## On-Chain Verification Flow When a reveal transaction is submitted, the vault contract performs the following checks related to policy binding: Note that the `ProofVerifier` only receives 6 public inputs (indices 0-5). The policy fields (indices 6-7) are verified by the vault contract's own logic, not by the ZK verifier. This separation exists because the ZK circuit ensures the policy values are correctly bound to the commitment, while the vault contract handles the actual policy execution. ## The No-Policy Case When `policyId == address(0)`, no policy is enforced. The circuit still includes the policy fields as public inputs, but both must be zero: - `publicInputs[6]` must be `0` (no policy contract) - `publicInputs[7]` must be `0` (no parameters) If `policyId` is zero but `policyParamsHash` is non-zero (or vice versa), the commitment hash will not match any valid leaf in the Merkle tree, and the proof will be invalid. The two values must be consistent with what was committed. ## Security Properties ### A malicious prover cannot forge a different policy The `policyId` and `policyParamsHash` are inputs to the Poseidon commitment hash. Changing either value changes the commitment, which changes the Merkle leaf, which invalidates the Merkle membership proof. The prover would need to find a Poseidon hash collision to substitute a different policy -- this is computationally infeasible. ### A malicious prover cannot remove a policy Attempting to set `policyId` to `address(0)` when the original commitment was created with a non-zero policy will produce a different commitment hash. The Merkle proof will fail because the leaf does not exist in the tree. ### A malicious prover cannot submit fake policy parameters The on-chain verifier recomputes `keccak256(policyParams) % p` from the raw parameters submitted in the transaction. If the prover submits parameters that hash to a different value than what is in the proof's public inputs, the verification fails before the policy contract is even called. ### Policy execution is sandboxed The policy contract is called via `staticcall` with a 100,000 gas limit. It cannot modify state, cannot call other contracts that modify state, and cannot consume unbounded gas. A buggy or malicious policy contract can only cause a reveal to fail -- it cannot affect the vault's state or any other contract. ## Summary | Property | Mechanism | |----------|-----------| | Policy bound to commitment | `policyId` and `policyParamsHash` are Poseidon hash inputs | | Prover cannot omit policy | Dummy quadratic constraints + Merkle membership | | Parameters verified on-chain | `keccak256(policyParams) % BN254_FIELD` checked against proof | | Policy logic executed on-chain | `staticcall` to `IRevealPolicy.validate()` with 100K gas cap | | No-policy default | Both fields must be zero; any other combination invalidates the proof | --- # Timelock & Expiry The `TimelockExpiry` contract is a reference policy that enforces a **time window** on reveals. A commitment bound to this policy can only be revealed after a specified unlock time and before a specified expiry time. Outside that window, the reveal is rejected. ## Contract ```solidity contract TimelockExpiry is IRevealPolicy { function validate( bytes32, // commitment (unused) bytes32, // nullifier (unused) address, // recipient (unused) uint256, // amount (unused) address, // token (unused) bytes calldata policyParams ) external view returns (bool valid) { (uint256 lockUntil, uint256 expiresAt) = abi.decode(policyParams, (uint256, uint256)); return block.timestamp >= lockUntil && block.timestamp <= expiresAt; } } ``` The contract is entirely stateless -- no constructor, no storage, no admin functions. It reads `block.timestamp` and compares it against the two decoded parameters. This simplicity is by design: the contract is pure validation logic with no attack surface beyond the EVM's timestamp mechanism. ## Parameters | Parameter | Type | Description | |-----------|------|-------------| | `lockUntil` | `uint256` | Earliest Unix timestamp at which the reveal is allowed. Before this time, `validate()` returns `false`. | | `expiresAt` | `uint256` | Latest Unix timestamp at which the reveal is allowed. After this time, `validate()` returns `false`. | The parameters are ABI-encoded as `abi.encode(uint256 lockUntil, uint256 expiresAt)` and their hash is bound to the commitment: ``` policyParamsHash = keccak256(abi.encode(lockUntil, expiresAt)) % BN254_SCALAR_FIELD ``` Because `policyParamsHash` is an input to the Poseidon commitment hash, the time window is **cryptographically fixed** at commit time. Neither the committer nor anyone else can alter the unlock or expiry time after the commitment is created. ## Validation Logic The valid reveal window is the closed interval `[lockUntil, expiresAt]`. Both boundary values are inclusive. ## Use Cases TimelockExpiry applies to any data type in the Specter protocol. The following examples illustrate the breadth of its applicability. ### Token Vesting Schedules A company commits GHOST tokens for employee vesting. Each tranche has a different `lockUntil` corresponding to the vesting date: | Tranche | lockUntil | expiresAt | Effect | |---------|-----------|-----------|--------| | Year 1 | 2027-01-01 | 2030-01-01 | Tokens unlock after 1 year, expire after 4 years | | Year 2 | 2028-01-01 | 2030-01-01 | Tokens unlock after 2 years | | Year 3 | 2029-01-01 | 2030-01-01 | Tokens unlock after 3 years | The employee can reveal (claim) each tranche only when the current time is within the window. If the employee leaves the company and does not claim before expiry, the tokens remain permanently locked in the commitment -- they can never be revealed. ### Time-Delayed Payments A buyer commits payment for goods with `lockUntil` set 7 days in the future. This gives the buyer a dispute window: if the goods are not delivered, the payment cannot be claimed. After 7 days, the seller can reveal and collect the payment. An `expiresAt` of 30 days ensures the seller must claim within a reasonable period. ### Escrow with Deadline A freelancer commits work product (credential or document hash) with a delivery window. The client knows the work can only be delivered within the agreed timeframe. If the freelancer misses the deadline (block.timestamp exceeds `expiresAt`), the commitment becomes permanently unredeemable. ### Credential Validity Windows A certification authority issues a credential with a 1-year validity period. The `lockUntil` is the issuance date and `expiresAt` is 1 year later. The credential holder can prove their certification during this window but not after it expires. Re-certification requires a new commitment with updated parameters. ### Time-Bound API Key Access A service provider commits an API key hash with a time window matching the subscription period. The key can only be revealed (and thus validated) during the active subscription. After expiry, the commitment is dead -- the key cannot be retrieved or proven valid. ### Scheduled Data Release A journalist commits the hash of an investigative report with `lockUntil` set to a future publication date. The report cannot be revealed before the embargo lifts, ensuring coordinated disclosure. The `expiresAt` can be set far in the future or omitted (set to `type(uint256).max`) to allow indefinite access after the unlock. ## Composability TimelockExpiry can be combined with other policies by creating a custom policy contract that calls multiple validators internally. For example, a contract that requires both a time window AND a specific recipient would combine TimelockExpiry logic with DestinationRestriction logic in a single `validate()` function. The policy system does not natively support policy chaining (one commitment can only have one `policyId`), but composite policies that embed multiple validation checks are straightforward to implement. ## Edge Cases | Scenario | Behavior | |----------|----------| | `lockUntil == expiresAt` | Valid for exactly one timestamp (single-block window in practice) | | `lockUntil > expiresAt` | No valid window -- the commitment can never be revealed | | `lockUntil == 0` | Effectively no lower bound -- reveal is allowed from genesis | | `expiresAt == type(uint256).max` | Effectively no upper bound -- reveal is allowed indefinitely after unlock | | Both are 0 | Reveal is allowed at timestamp 0 only -- effectively never on a live chain | --- # Destination Restriction The `DestinationRestriction` contract is a reference policy that restricts **which addresses can receive revealed data or tokens**. It supports two modes: a single allowed recipient, or a Merkle allowlist of multiple permitted recipients. ## Contract ```solidity contract DestinationRestriction is IRevealPolicy { function validate( bytes32, // commitment (unused) bytes32, // nullifier (unused) address recipient, uint256, // amount (unused) address, // token (unused) bytes calldata policyParams ) external pure returns (bool valid) { if (policyParams.length == 32) { // Single address mode address allowedRecipient = abi.decode(policyParams, (address)); return recipient == allowedRecipient; } else { // Merkle allowlist mode (bytes32 allowlistRoot, bytes32[] memory merkleProof) = abi.decode(policyParams, (bytes32, bytes32[])); bytes32 leaf = keccak256(abi.encodePacked(recipient)); return _verifyMerkleProof(merkleProof, allowlistRoot, leaf); } } } ``` The contract is stateless and `pure` -- it does not read any chain state (not even `block.timestamp`). Validation depends entirely on the submitted parameters and the recipient address from the reveal transaction. ## Two Modes ### Mode 1: Single Address When `policyParams` is exactly 32 bytes, the contract decodes a single `address` and checks whether the reveal's `recipient` matches: ``` policyParams = abi.encode(allowedRecipient) policyParamsHash = keccak256(abi.encode(allowedRecipient)) % BN254_SCALAR_FIELD ``` This is the simplest form: the committed data can only be revealed to exactly one address. Any other recipient will cause the validation to return `false`. ### Mode 2: Merkle Allowlist When `policyParams` is longer than 32 bytes, the contract decodes a `bytes32 allowlistRoot` and a `bytes32[] merkleProof`. The recipient's address is hashed as a Merkle leaf, and the proof is verified against the root: ``` leaf = keccak256(abi.encodePacked(recipient)) valid = verifyMerkleProof(proof, allowlistRoot, leaf) ``` This mode allows a set of permitted recipients to be defined at commit time. The Merkle root of the allowlist is embedded in the `policyParamsHash`, so the set of allowed recipients is fixed when the commitment is created. The actual proof is provided at reveal time. ## Parameters ### Single Address Mode | Parameter | Type | Description | |-----------|------|-------------| | `allowedRecipient` | `address` | The only address permitted to receive the revealed data/tokens | ### Merkle Allowlist Mode | Parameter | Type | Description | |-----------|------|-------------| | `allowlistRoot` | `bytes32` | Merkle root of the set of allowed recipient addresses | | `merkleProof` | `bytes32[]` | Merkle proof that the reveal's recipient is in the allowlist | In both modes, the `policyParamsHash` is computed at commit time and bound to the commitment: ``` // Single address policyParamsHash = keccak256(abi.encode(allowedRecipient)) % BN254_FIELD // Merkle allowlist (only the root is in the committed params) policyParamsHash = keccak256(abi.encode(allowlistRoot)) % BN254_FIELD ``` **Important:** For the Merkle allowlist mode, only the `allowlistRoot` is part of the committed `policyParamsHash`. The Merkle proof is provided dynamically at reveal time because it varies by recipient. The root is fixed; the proof is variable. ## Merkle Proof Verification The contract includes an internal Merkle proof verifier that follows the standard sorted-pair convention: ```solidity function _verifyMerkleProof( bytes32[] memory proof, bytes32 root, bytes32 leaf ) internal pure returns (bool) { bytes32 computedHash = leaf; for (uint256 i = 0; i < proof.length; i++) { if (computedHash <= proof[i]) { computedHash = keccak256(abi.encodePacked(computedHash, proof[i])); } else { computedHash = keccak256(abi.encodePacked(proof[i], computedHash)); } } return computedHash == root; } ``` The sorted-pair approach ensures that the proof is deterministic regardless of left/right ordering -- the smaller hash always goes first. ## Use Cases DestinationRestriction applies to any data type in the Specter protocol. ### Earmarked Payments A grant funder commits GHOST tokens designated for a specific recipient (a researcher, a nonprofit). The tokens can only be revealed to that recipient's address. Even if the phantom key is compromised, an attacker cannot redirect the funds to a different address. ### Payroll A company commits monthly salary payments for each employee. Each commitment is bound to the employee's wallet address via DestinationRestriction. The employee can claim their salary at any time, but only to their own address. Combined with TimelockExpiry, the salary can be time-locked until the pay date. ### Restricted Grants A DAO approves a grant for a specific project. The grant tokens are committed with a Merkle allowlist of authorized team member addresses. Any team member can claim, but non-team addresses cannot. ### Credential Delivery A university commits a degree attestation hash. The credential is bound to the graduate's wallet address. Only the graduate can reveal and prove the credential -- no one else can extract or present it. This prevents credential theft even if the commitment is publicly visible. ### Private Transfers to Known Recipients A user wants to send tokens privately but ensure they arrive at the correct destination. By attaching a DestinationRestriction, the sender guarantees that even if the phantom key is intercepted in transit, the tokens can only be revealed by the intended recipient. ### Multi-Recipient Distribution An airdrop organizer commits tokens with a Merkle allowlist of eligible addresses. Each eligible address can claim by providing the Merkle proof of their inclusion. Ineligible addresses cannot claim even if they obtain the phantom key. ## Single Address vs. Merkle Allowlist | Property | Single Address | Merkle Allowlist | |----------|---------------|------------------| | Permitted recipients | Exactly 1 | Any number (bounded by tree depth) | | policyParams size | 32 bytes | 32 bytes + 32 bytes per proof element | | Gas cost | Minimal (1 comparison) | Higher (Merkle proof verification) | | Flexibility | Fixed at commit time | Set defined at commit time, membership proven at reveal time | | Use case | 1:1 transfers, payroll | Airdrops, team grants, allowlists | --- # Threshold Witness The `ThresholdWitness` contract is a reference policy that requires **M-of-N witness signatures** before a reveal is permitted. A designated set of witnesses must cryptographically approve the reveal by signing the reveal parameters with their Ethereum private keys. The reveal proceeds only if the required threshold of valid signatures is met. ## Contract Overview ```solidity contract ThresholdWitness is IRevealPolicy { function validate( bytes32 commitment, bytes32 nullifier, address recipient, uint256 amount, address token, bytes calldata policyParams ) external pure returns (bool valid) { (address[] memory witnesses, bytes[] memory signatures, uint256 threshold) = abi.decode(policyParams, (address[], bytes[], uint256)); if (threshold == 0 || threshold > witnesses.length) return false; bytes32 messageHash = keccak256( abi.encodePacked(commitment, nullifier, recipient, amount, token) ); bytes32 ethSignedHash = MessageHashUtils.toEthSignedMessageHash(messageHash); // Count valid, unique witness signatures bool[] memory counted = new bool[](witnesses.length); uint256 validCount = 0; for (uint256 i = 0; i < signatures.length; i++) { (address recovered, ECDSA.RecoverError err,) = ECDSA.tryRecover(ethSignedHash, signatures[i]); if (err != ECDSA.RecoverError.NoError) continue; for (uint256 j = 0; j < witnesses.length; j++) { if (recovered == witnesses[j] && !counted[j]) { counted[j] = true; validCount++; break; } } if (validCount >= threshold) return true; } return false; } } ``` The contract is stateless and `pure` -- it performs all validation using only the submitted parameters. It uses OpenZeppelin's `ECDSA.tryRecover` for safe signature recovery (no reverts on malformed signatures) and `MessageHashUtils.toEthSignedMessageHash` for EIP-191 prefix compliance. ## Parameters | Parameter | Type | Description | |-----------|------|-------------| | `witnesses` | `address[]` | The set of N authorized witness addresses | | `signatures` | `bytes[]` | The M (or more) ECDSA signatures from witnesses | | `threshold` | `uint256` | Minimum number of valid, unique witness signatures required | The parameters are ABI-encoded as `abi.encode(witnesses, signatures, threshold)`. The `policyParamsHash` bound to the commitment is: ``` policyParamsHash = keccak256(abi.encode(witnesses, signatures, threshold)) % BN254_SCALAR_FIELD ``` **Important:** Because `signatures` are part of the `policyParams`, they are included in the `policyParamsHash` computation. This means the exact set of signatures must be known at the time the `policyParamsHash` is committed. In practice, this means the witness signatures are collected before the reveal transaction is submitted, and their hash is verified against what was bound in the commitment. ## Signature Verification Flow ## Message Format Each witness signs the following message: ``` messageHash = keccak256(abi.encodePacked(commitment, nullifier, recipient, amount, token)) ``` The hash is then prefixed with the standard Ethereum signed message prefix: ``` ethSignedHash = "\x19Ethereum Signed Message:\n32" + messageHash ``` This ensures compatibility with standard Ethereum wallets (MetaMask, hardware wallets) that automatically apply the EIP-191 prefix when signing arbitrary data. The message includes all five reveal parameters, meaning each witness explicitly approves the specific commitment, nullifier, recipient, amount, and token of the reveal. A signature for one reveal cannot be reused for a different reveal. ## Deduplication and Safety The contract includes several safety mechanisms: | Mechanism | Purpose | |-----------|---------| | **Duplicate signer detection** | Each witness address can only be counted once, even if multiple signatures from the same key are provided | | **Invalid signature handling** | `ECDSA.tryRecover` returns an error code instead of reverting on malformed signatures -- invalid signatures are silently skipped | | **Non-witness signatures ignored** | If a valid signature is recovered but the signer is not in the `witnesses` array, it is not counted | | **Early exit** | The loop terminates as soon as `validCount >= threshold`, saving gas | | **Zero threshold rejection** | `threshold == 0` immediately returns `false` (at least 1 signature is always required) | | **Threshold > N rejection** | `threshold > witnesses.length` immediately returns `false` (impossible to meet) | ## Use Cases ### Multi-Sig Escrow A buyer commits payment with a 2-of-3 ThresholdWitness policy where the witnesses are: the buyer, the seller, and a neutral arbitrator. The payment can be released only when 2 of the 3 parties sign off. This creates a trustless escrow where disputes are resolved by the arbitrator's vote. ### Corporate Treasury A company commits treasury funds with a 3-of-5 ThresholdWitness policy where the witnesses are board members. No single board member can unilaterally access the funds. Withdrawals require majority approval, enforced cryptographically rather than by corporate governance convention. ### Compliance Approval Gates A financial institution commits tokens with a ThresholdWitness policy requiring signatures from compliance officers. The KYC/AML team must sign off on each reveal, ensuring regulatory compliance is enforced at the protocol level. The institution cannot bypass its own compliance process. ### Multi-Party Credential Issuance A professional credential (e.g., medical license) requires approval from multiple authorities: the examining board, the state licensing agency, and the educational institution. The credential commitment uses a 3-of-3 ThresholdWitness policy. The credential can only be issued (revealed) when all three authorities have signed. ### Dead Man's Switch A user commits sensitive data with a ThresholdWitness policy where the witnesses are trusted family members or attorneys. The data can only be revealed with M-of-N family member signatures, creating a decentralized dead man's switch for estate planning or emergency key recovery. ### DAO Governance Execution A DAO commits funds for a proposal with a ThresholdWitness policy requiring signatures from elected council members. Even after a governance vote passes, the funds are not released until the council members cryptographically confirm execution. This adds an execution layer on top of the voting layer. ## Gas Considerations ThresholdWitness is the most gas-intensive of the three reference policies because it performs ECDSA signature recovery in a loop. The gas cost scales with the number of signatures: | Signatures | Approximate Gas | |-----------|----------------| | 1 | ~8,000 | | 2 | ~14,000 | | 3 | ~20,000 | | 5 | ~32,000 | | 10 | ~62,000 | The policy executes within a `staticcall` with a 100,000 gas limit. This practically limits the threshold to approximately 15 signatures before the gas cap is reached. For larger witness sets, a custom policy contract could use more gas-efficient signature aggregation (e.g., BLS signatures). --- # GHOST Token GHOST is **one application of the Specter protocol** -- the native token of the Specter blockchain. While the Specter protocol is a general-purpose data privacy system that handles credentials, images, API keys, and any other data type, GHOST demonstrates how private value transfer works as a specific use case built on the same cryptographic primitives. ## Role in the Network GHOST serves four functions within the Specter ecosystem: | Function | Description | |----------|-------------| | **Gas fees** | Every transaction on the Specter chain requires GHOST for gas, following the EIP-1559 dynamic fee market model | | **Validator staking** | Validators stake GHOST through the Cosmos SDK `x/staking` module to participate in CometBFT consensus | | **On-chain governance** | GHOST holders participate in governance proposals via the `x/gov` module | | **Private value transfer** | GHOST can be committed and revealed through the privacy protocol (vanish/summon) | ## Denomination GHOST follows the standard 18-decimal EVM convention: ``` 1 GHOST = 10^18 aghost ``` The base unit `aghost` (atto-ghost) is the smallest indivisible unit. All on-chain accounting, Merkle tree commitments, and ZK circuit arithmetic use `aghost` values. The `GHOST` denomination is a display-layer convenience. | Unit | Value in aghost | |------|----------------| | 1 aghost | 1 | | 1 GHOST | 1,000,000,000,000,000,000 | ## Dual Representation GHOST exists simultaneously as a Cosmos SDK native token and as an EVM-accessible asset. This dual representation is managed by the `NativeAssetHandler` contract and the `x/ghostmint` precompile. On the Cosmos side, GHOST is a standard `x/bank` denomination (`aghost`). It can be sent between accounts, staked, used for governance, and transferred via IBC. On the EVM side, GHOST is the native gas token (equivalent to ETH on Ethereum). The `NativeAssetHandler` bridges these two representations through the ghostmint precompile at address `0x0808`. ## Token Privacy as a Special Case Private GHOST transfers use the **7-input commitment variant** and the **8-input redemption circuit**, which are specialized versions of the general-purpose data protocol: | Protocol Variant | Commitment | Circuit | Used For | |-----------------|------------|---------|----------| | Generic data | `Poseidon4(secret, nullifierSecret, dataHash, blinding)` | 4-input access proof | Credentials, images, API keys, any data | | Token (GHOST) | `Poseidon7(secret, nullifierSecret, tokenId, amount, blinding, policyId, policyParamsHash)` | 8-input redemption proof | Private token transfers | The token variant extends the generic data commitment with three additional fields: - **`tokenId`** -- identifies which token (native GHOST or a GhostERC20) - **`amount`** -- the quantity of tokens being committed - **`policyId` / `policyParamsHash`** -- optional programmable constraints on the reveal The redemption circuit adds two additional public inputs beyond the generic circuit's outputs: - **`policyId`** -- the policy contract address embedded in the proof - **`policyParamsHash`** -- the hash of policy parameters embedded in the proof These additional fields enable the burn/mint model that makes private token transfers possible: the protocol needs to know the token type and amount to destroy the correct tokens on commit and create the correct tokens on reveal. ## Commit/Reveal for GHOST The private transfer flow for GHOST follows the same commit/reveal pattern as all data in the Specter protocol: 1. **Vanish (Commit):** User sends GHOST to the `CommitRevealVault`. The vault calls `NativeAssetHandler.burnNativeFrom()`, which calls the ghostmint precompile, which calls `x/bank.BurnCoins()`. The GHOST tokens are destroyed. A Poseidon commitment hash is recorded in the Merkle tree. 2. **Summon (Reveal):** User generates a Groth16 ZK proof demonstrating knowledge of the commitment preimage and Merkle tree membership. The vault verifies the proof, records the nullifier, then calls `NativeAssetHandler.mintNativeTo()`, which calls the ghostmint precompile, which calls `x/bank.MintCoins()`. Fresh GHOST tokens are created and sent to the specified recipient. The commit and reveal transactions are **cryptographically unlinkable**. The ZK proof demonstrates that a valid commitment exists in the tree without identifying which one. The anonymity set is the entire set of commitments in the tree. ## Not a Wrapper, Not a Pool GHOST's privacy model is fundamentally different from pool-based approaches: - **No custodial pool.** Tokens are destroyed on commit and created on reveal. There is no contract holding a balance of tokens that could be frozen, sanctioned, or drained. - **No wrapper token.** GHOST does not have a "private" and "public" version. The same token moves between public state and private commitments via burn and mint. - **No frozen funds risk.** Because tokens in the commitment tree are "virtual" (they exist only as Poseidon hashes, not as token balances), there is no contract balance that constitutes "frozen" or "locked" funds. The [Mint & Burn Flow](./mint-burn-flow.md) page details the complete lifecycle, and [Supply Mechanics](./supply-mechanics.md) explains how the protocol guarantees supply conservation. --- # Supply Mechanics The GHOST token has a **hard-capped supply** with **zero inflation**. All minting and burning is exclusively controlled through the ghostmint precompile, and the supply invariant is checked every block at the consensus level. This page documents the supply parameters, enforcement mechanisms, and tracking infrastructure. ## Supply Parameters | Parameter | Value | Location | |-----------|-------|----------| | **Maximum supply** | 1,000,000,000 GHOST (10^27 aghost) | `x/ghostmint/types/keys.go` (`MaxMintSupply`) | | **Inflation rate** | 0% | `x/mint` module configuration | | **Denomination** | `aghost` (18 decimals) | Native denom across Cosmos and EVM | | **Smallest unit** | 1 aghost | 10^-18 GHOST | The maximum supply is hard-coded as a Go variable in the ghostmint module: ```go // MaxMintSupply is the hard cap on total tokens mintable via ghostmint. // 1 billion GHOST = 1e27 aghost (18 decimals). MaxMintSupply = math.NewIntFromBigInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(27), nil)) ``` This value is a compile-time constant. Changing it requires a chain upgrade with validator consensus -- it cannot be modified by governance proposals, contract calls, or any runtime mechanism. ## Zero Inflation The Cosmos SDK `x/mint` module is configured with a **0% inflation rate**. No new GHOST tokens are created through block rewards or staking inflation. The only mechanism that creates GHOST tokens is the ghostmint precompile, and it is constrained by the `MaxMintSupply` cap and the net supply tracking. ## Net Supply Tracking The ghostmint module tracks cumulative minting and burning in its KVStore: ``` netSupply = totalMinted - totalBurned ``` Every mint operation checks: ``` netSupply + mintAmount <= MaxMintSupply ``` This formula accounts for burns. If 500 million GHOST are minted and 200 million are burned (through privacy commits), the net supply is 300 million. The protocol can still mint up to 700 million more GHOST before hitting the cap. Burns "free up" supply capacity. ### KVStore Keys | Key | Purpose | |-----|---------| | `TotalMinted` | Cumulative aghost minted via ghostmint precompile since genesis | | `TotalBurned` | Cumulative aghost burned via ghostmint precompile since genesis | | `LastTotalSupply` | Previous block's total `aghost` supply (for invariant checking) | These values are persisted in the module's KVStore and updated atomically with each mint or burn operation. ## Ghostmint Precompile All GHOST minting and burning flows through the ghostmint precompile at address `0x0808`. This is the only code path that can create or destroy GHOST tokens (outside of genesis allocation). | Method | Gas Cost | Description | |--------|----------|-------------| | `mintNativeTo(address, uint256)` | 50,000 | Mint aghost to recipient via x/bank | | `burnNativeFrom(address, uint256)` | 50,000 | Burn aghost from sender via x/bank | | `totalMinted()` | 200 | Read cumulative minted amount | | `totalBurned()` | 200 | Read cumulative burned amount | ### Authorization The precompile maintains an **authorized callers** map. Only contracts in this map can call `mintNativeTo` or `burnNativeFrom`. Currently, the sole authorized caller is the `NativeAssetHandler` contract: | Authorized Caller | Address | Role | |-------------------|---------|------| | NativeAssetHandler | `0x35cdaE691037fcBb3ff9D0518725F1ae98d502b7` | Bridges CommitRevealVault burn/mint calls to the precompile | If the authorized callers map is empty (misconfiguration), the precompile rejects all calls -- it does not default to permissive behavior. Additional authorized callers can be added through governance-controlled chain upgrades. ### Mint Flow When `mintNativeTo` is called: 1. Validate the caller is authorized 2. Validate amount is positive 3. Check `netSupply + amount <= MaxMintSupply` 4. Call `x/bank.MintCoins()` to create tokens in the ghostmint module account 5. Call `x/bank.SendCoinsFromModuleToAccount()` to transfer tokens to the recipient 6. Update `totalMinted` in KVStore 7. Record the balance change in the EVM StateDB journal ### Burn Flow When `burnNativeFrom` is called: 1. Validate the caller is authorized 2. Validate amount is positive 3. Call `x/bank.SendCoinsFromAccountToModule()` to transfer tokens from the precompile address to the ghostmint module account 4. Call `x/bank.BurnCoins()` to destroy the tokens 5. Update `totalBurned` in KVStore 6. Record the balance change in the EVM StateDB journal ## Supply Invariant The ghostmint module runs a **per-block supply invariant check** in the `EndBlocker`. Every block, the module: 1. Reads the current total supply of `aghost` from `x/bank` 2. Compares it with the `LastTotalSupply` stored from the previous block 3. If the current supply exceeds the previous supply unexpectedly, logs an error: ``` SUPPLY INVARIANT: unexpected supply increase previous: current: delta: denom: aghost ``` 4. Updates `LastTotalSupply` to the current value for the next block's check This invariant catches any unexpected supply creation -- whether from a bug in the ghostmint module, a compromised precompile, or any other source. Because the Cosmos SDK's `x/bank` module tracks total supply independently of the ghostmint module, the invariant provides a cross-check between two independent accounting systems. **Note:** The invariant currently logs an error rather than halting the chain. This is a deliberate design choice: halting on an invariant violation would require manual intervention to restart, which could be worse than the violation itself in some scenarios. Future versions may escalate to a chain halt for critical violations. ## Solvency Tracking At the smart contract level, the `CommitRevealVault` and `BatchCommitRevealVault` independently track total committed and total revealed amounts per token: ``` totalRevealed[token] <= totalCommitted[token] ``` This invariant ensures that the vault never mints more tokens than were burned. It operates at the EVM level and is independent of the consensus-level supply invariant -- providing defense in depth. ## Supply Summary | Layer | Invariant | Enforcement | |-------|-----------|-------------| | Consensus (Go) | `netSupply <= MaxMintSupply` | Mint rejected if cap exceeded | | Consensus (Go) | Supply does not increase unexpectedly between blocks | EndBlocker check, error logged | | EVM (Solidity) | `totalRevealed[token] <= totalCommitted[token]` | Reveal rejected if solvency violated | | EVM (Solidity) | Only authorized callers can invoke precompile | Unauthorized calls rejected | --- # Mint & Burn Flow The private GHOST token transfer lifecycle is a burn-on-commit, mint-on-reveal cycle. Real tokens are destroyed when a commitment is created and recreated when a commitment is revealed. This page documents the complete flow through every layer of the stack. ## Complete Lifecycle ## Vanish (Commit) -- Step by Step ### 1. User Prepares Commitment Off-chain, the user generates: - A random `secret` (254-bit) - A random `nullifierSecret` (254-bit) - A random `blinding` factor (254-bit) - The `tokenId` = `Poseidon2(tokenAddress, 0)` (for native GHOST, `tokenAddress = address(0)`) - The `amount` in aghost - Optional `policyId` and `policyParamsHash` The user computes the commitment: ``` commitment = Poseidon7(secret, nullifierSecret, tokenId, amount, blinding, policyId, policyParamsHash) ``` ### 2. User Calls CommitRevealVault The user sends a transaction to `commitNative()` (or `commitNativeWithPolicy()` if a policy is attached), sending the GHOST amount as `msg.value`: ```solidity vault.commitNative{value: 1 ether}(commitment, quantumCommitment); ``` ### 3. Vault Burns Tokens The vault calls `NativeAssetHandler.burnNativeFrom{value: msg.value}(msg.sender, msg.value)`. The NativeAssetHandler forwards this to the ghostmint precompile, which: 1. Verifies the caller is the authorized NativeAssetHandler 2. Calls `x/bank.SendCoinsFromAccountToModule()` to move tokens from the precompile address to the ghostmint module account 3. Calls `x/bank.BurnCoins()` to destroy the tokens from total supply 4. Increments `totalBurned` in the KVStore After this step, the GHOST tokens no longer exist. The total supply of `aghost` has decreased by the committed amount. ### 4. Vault Records Commitment The vault calls `commitmentTree.recordCommitment(commitment)`, which stores the commitment hash as a leaf in the Merkle tree. A root operator later includes this leaf in the computed Merkle root. ### 5. State After Commit | What Happened | Where | |--------------|-------| | GHOST tokens destroyed | `x/bank` total supply decreased | | Commitment hash stored | CommitmentTree leaf array | | Depositor recorded | `commitmentDepositors[commitment] = msg.sender` | | Native committed total updated | `_totalNativeCommitted += msg.value` | | Event emitted | `Committed(token, depositor, commitment, amount, leafIndex)` | The tokens are gone. The only record that anything was committed is the commitment hash -- a single 254-bit field element that reveals nothing about the sender, amount, recipient, or policy. ## Summon (Reveal) -- Step by Step ### 1. User Generates ZK Proof Off-chain, the user uses the Groth16 prover with the redemption circuit to generate a proof that: - They know the preimage of a commitment (`secret`, `nullifierSecret`, `tokenId`, `amount`, `blinding`, `policyId`, `policyParamsHash`) - That commitment exists as a leaf in the Merkle tree (valid Merkle path to a known root) - The nullifier is correctly derived: `nullifier = Poseidon2(nullifierSecret, leafIndex)` - The `amount`, `recipient`, `tokenId`, `policyId`, and `policyParamsHash` match what is encoded in the commitment The proof is a compact Groth16 proof (~256 bytes) with 8 public inputs. ### 2. User Calls CommitRevealVault ```solidity vault.reveal(token, proof, publicInputs, commitment, quantumProof, changeQuantumCommitment, policyParams); ``` ### 3. Vault Verifies the Proof The vault performs these checks in sequence: 1. **Public input count:** Exactly 8 public inputs required 2. **Token ID match:** `publicInputs[5]` must match the expected token ID for the specified token 3. **Merkle root validity:** `publicInputs[0]` must be a known root in the CommitmentTree 4. **Nullifier unspent:** `publicInputs[1]` must not exist in the NullifierRegistry 5. **Groth16 verification:** The proof must verify against the first 6 public inputs via the ProofVerifier contract ### 4. Vault Enforces Policy If `publicInputs[6] != 0` (policy is present): 1. Compute `keccak256(policyParams) % BN254_SCALAR_FIELD` 2. Verify it matches `publicInputs[7]` 3. Verify the policy address has deployed code 4. Call `policy.validate()` via `staticcall` with 100K gas 5. Require the policy returns `true` ### 5. Vault Records Nullifier The nullifier (`publicInputs[1]`) is recorded in the NullifierRegistry. This prevents the same commitment from being revealed twice. ### 6. Vault Mints Tokens The vault calls `NativeAssetHandler.mintNativeTo(recipient, amount)`. The NativeAssetHandler forwards this to the ghostmint precompile, which: 1. Verifies the caller is the authorized NativeAssetHandler 2. Checks `netSupply + amount <= MaxMintSupply` 3. Calls `x/bank.MintCoins()` to create fresh tokens in the ghostmint module account 4. Calls `x/bank.SendCoinsFromModuleToAccount()` to send tokens to the recipient 5. Increments `totalMinted` in the KVStore ### 7. State After Reveal | What Happened | Where | |--------------|-------| | GHOST tokens created | `x/bank` total supply increased | | Nullifier recorded | NullifierRegistry (prevents double-reveal) | | Recipient received GHOST | Recipient's account balance | | Event emitted | `Revealed(token, recipient, nullifier, amount)` | ## Virtual Tokens The key insight of this design is that tokens in the Merkle tree are **virtual**. Between commit and reveal, the tokens do not exist as a balance held by any account or contract. They exist only as Poseidon commitment hashes -- mathematical objects with no corresponding token balance. This has a critical consequence: **there is no custodial pool**. Unlike pool-based privacy protocols (e.g., Tornado Cash) where tokens are deposited into a contract, the Specter protocol holds zero token balance. There is no contract that can be: - **Frozen** by a regulator (no funds to freeze) - **Sanctioned** as a custodian (no custody occurs) - **Drained** by an exploit (no balance to drain) The total supply of GHOST decreases when commitments are made and increases when commitments are revealed. The conservation invariant (`totalRevealed <= totalCommitted` per token) ensures that reveals never exceed commits. ## Partial Reveals and Change Commitments The redemption circuit supports **partial reveals**. If a user committed 100 GHOST but wants to reveal only 60, the remaining 40 are automatically committed as a new "change commitment" in the same transaction: 1. Reveal 60 GHOST to the recipient (fresh tokens minted) 2. Create a new commitment for the remaining 40 GHOST (recorded in the tree) 3. The original commitment's nullifier is spent (preventing re-use) 4. The change commitment gets its own new phantom key The `changeCommitment` is `publicInputs[4]`. If it is non-zero, the vault records it as a new leaf in the Merkle tree. This enables exact-amount transfers without requiring the committed amount to match the transfer amount. ## Solvency Invariant At the smart contract level, the vault tracks: ``` totalRevealed[token] <= totalCommitted[token] ``` This ensures the vault never mints more tokens than were burned. Combined with the consensus-level supply invariant (checked every block), this provides two independent layers of supply conservation enforcement. --- # Governance Specter uses the Cosmos SDK `x/gov` module for on-chain governance. GHOST token holders and validators participate in protocol-level decisions through proposals and voting, with the validator set serving as the current governance authority. ## Governance Mechanism The Cosmos SDK governance module provides a structured process for protocol changes: 1. **Deposit Period:** A governance proposal is submitted with an initial deposit. Other token holders can add to the deposit. If the minimum deposit is not reached within the deposit period, the proposal is discarded. 2. **Voting Period:** Once the deposit threshold is met, validators and delegators vote. Each validator's voting power is proportional to their staked GHOST. 3. **Tallying:** After the voting period, the votes are tallied. The proposal passes if quorum is met and a majority votes yes. Failed proposals may result in deposit burning (to deter spam). 4. **Execution:** Passed proposals are executed automatically by the Cosmos SDK runtime -- parameter changes take effect immediately. ## What Governance Controls Governance proposals can modify a range of protocol parameters and configurations: ### Ghostmint Precompile Authorization | Parameter | Current Value | Governance Can Change | |-----------|--------------|----------------------| | Authorized callers for `mintNativeTo` / `burnNativeFrom` | NativeAssetHandler (`0x35cd...02b7`) | Add or remove authorized callers via chain upgrade | | MaxMintSupply | 1 billion GHOST (10^27 aghost) | Requires code change + chain upgrade (not a runtime parameter) | Adding new authorized callers to the ghostmint precompile requires a chain upgrade proposal. This controls which contracts can mint and burn GHOST tokens. ### Policy Registry The `PolicyRegistry` contract is permissionless (anyone can register a policy), but governance can: - Deploy updated versions of reference policy contracts - Recommend specific policies for ecosystem use - Fund audits of policy contracts ### Contract Upgrades Key protocol contracts can be upgraded through governance: | Contract | Upgrade Mechanism | |----------|------------------| | ProofVerifier | Owner (governance-controlled) deploys new verifier, vault points to it | | CommitRevealVault | New vault deployed, NativeAssetHandler updated to authorize it | | CommitmentTree | New tree deployed, vault configured to use it | | NullifierRegistry | New registry deployed, vault configured to use it | Contract upgrades are coordinated through governance proposals that include the new contract addresses and the sequence of ownership transfers required. ### Staking Parameters | Parameter | Description | |-----------|-------------| | Minimum self-delegation | Minimum GHOST a validator must stake | | Unbonding period | Time validators must wait after unstaking | | Maximum validators | Size of the active validator set | | Slashing conditions | Penalties for double-signing and downtime | ### Chain Upgrades Binary upgrades (new node software versions) are coordinated through governance: 1. Proposal specifies the upgrade name and block height 2. Validators vote to approve 3. At the specified block height, nodes halt and await the new binary 4. Validators upgrade their software and restart 5. Consensus resumes with the new version This process enables protocol evolution (new modules, new precompiles, circuit upgrades) without hard forks. ## Current Governance Structure Specter's current governance authority is the **consensus of validators**. Validators vote on proposals proportional to their staked GHOST, including delegated stake. This is the standard Cosmos SDK governance model. | Aspect | Current State | |--------|--------------| | Who votes | Validators + delegators (via their validators) | | Voting power | Proportional to staked GHOST | | Proposal deposit | Required (prevents spam) | | Execution | Automatic via Cosmos SDK runtime | | On-chain enforcement | Parameter changes take effect at end of voting period | ## Future Governance Evolution The governance structure is designed to evolve as the protocol matures: ### Community DAO A future governance layer may introduce a dedicated DAO structure with: - **Token-weighted voting** for protocol parameters - **Council elections** for operational decisions - **Treasury management** for ecosystem funding - **Grant programs** funded by governance-allocated GHOST ### Protocol Evolution Governance will be the mechanism for major protocol transitions: | Phase | Governance Role | |-------|----------------| | Scaling rollout | Approve deployment of BatchCommitRevealVault, SessionVault, ShardedTreeRegistry | | Runner network | Set runner staking requirements, slashing parameters, shard assignments | | Circuit upgrades | Approve new ZK verifier contracts with updated circuit parameters | | Cross-chain expansion | Configure IBC channels and cross-chain commitment bridging | ### Decentralization Timeline The protocol follows a progressive decentralization model: 1. **Current:** Core team operates initial validator set; governance proposals require validator consensus 2. **Near-term:** Open validator set with permissionless staking; community proposals enabled 3. **Medium-term:** DAO structure with elected council and treasury 4. **Long-term:** Fully decentralized governance with minimal core team involvement Each transition is itself governed by a governance proposal, ensuring the community has a say in every step of the decentralization process. --- # Scaling Strategy Specter's scaling strategy is a **four-phase roadmap** for increasing data throughput without compromising the protocol's privacy guarantees. Each phase is additive -- earlier phases remain active as later phases are deployed. The chain itself remains the settlement backbone; scaling happens at the execution and coordination layers. ## The Scaling Problem The base `CommitRevealVault` processes one commit or one reveal per transaction. Each reveal requires an on-chain Groth16 proof verification, Merkle root check, nullifier registration, and policy enforcement. At ~2.5-second block times with a 25 million gas limit per block, this creates a practical throughput ceiling for high-volume applications. The scaling roadmap addresses this by progressively reducing the per-operation overhead and parallelizing commitment storage. ## Four-Phase Roadmap ## Phase Summary | Phase | Contract | Key Capability | Throughput Multiplier | |-------|----------|---------------|----------------------| | **1. Batch Operations** | `BatchCommitRevealVault` | 50 commits or reveals per transaction | ~50x per transaction | | **2. Session Vaults** | `SessionVault` | Agent executes hundreds of operations, settles in batches | ~100-500x per session | | **3. Sharded Trees** | `ShardedTreeRegistry` | Multiple parallel Merkle trees (e.g., 16 shards = 16M capacity) | Linear with shard count | | **4. Runner Network** | `RunnerRegistry` | Decentralized operators manage shards independently | Self-scaling with demand | ## Additive Design Each phase builds on the previous phases rather than replacing them: - **Phase 1** deploys alongside the base `CommitRevealVault` with its own CommitmentTree and NullifierRegistry. The original vault continues to operate unchanged. - **Phase 2** uses the batch infrastructure from Phase 1 for settlement but adds session management, budgets, and dispute resolution. - **Phase 3** multiplies the Phase 1 infrastructure -- each shard is a standard CommitmentTree + NullifierRegistry pair, deployed N times. - **Phase 4** decentralizes the management of Phase 3 shards -- runners stake GHOST, get assigned shards, and run their own proof generation and root updates. A user can choose which phase of infrastructure to interact with based on their needs. Simple one-off private transfers use the base vault. High-throughput applications use batch operations. Agent-based workflows use session vaults. The protocol supports all modes simultaneously. ## Data-Agnostic Scaling The scaling infrastructure is not limited to token operations. Batch commits work for any data type committed to the Merkle tree -- credential batches, bulk document sealing, mass API key issuance. The `BatchCommitRevealVault` processes the same 7-input Poseidon commitments as the base vault, and the sharded tree registry is agnostic to what the commitment hashes represent. ## Detailed Phase Documentation - [Batch Operations](./batch-operations.md) -- Phase 1 implementation details - [Session Vaults](./session-vaults.md) -- Phase 2 implementation details - [Sharded Merkle Trees](./sharded-trees.md) -- Phase 3 and Phase 4 implementation details --- # Batch Operations Phase 1 of the scaling roadmap introduces the `BatchCommitRevealVault` -- a contract that enables **up to 50 commits or reveals per transaction**. It deploys alongside the existing `CommitRevealVault` with its own CommitmentTree and NullifierRegistry, operating in parallel without modifying or replacing the base vault. ## Architecture The batch vault shares the `ProofVerifier`, `AssetGuard`, and `NativeAssetHandler` with the base vault. It has its own commitment tree and nullifier registry, meaning the two vaults maintain independent Merkle trees and nullifier sets. ## Batch Functions ### batchCommitNative Commits up to 50 native GHOST commitments in a single transaction. ```solidity function batchCommitNative( bytes32[] calldata commitments, uint256[] calldata amounts, bytes32[] calldata quantumCommitments_ ) external payable ``` | Parameter | Description | |-----------|-------------| | `commitments` | Array of Poseidon commitment hashes | | `amounts` | Array of aghost amounts (must sum to `msg.value`) | | `quantumCommitments_` | Array of quantum-resistant commitments (`bytes32(0)` to skip) | The function performs a **single burn** for the total amount, then records each commitment individually. This is more gas-efficient than N separate transactions because the burn overhead is amortized. ### batchReveal Reveals up to 50 commitments in a single transaction. ```solidity function batchReveal( address token, bytes[] calldata proofs, uint256[][] calldata publicInputsArray, bytes32[] calldata commitments, bytes32[] calldata quantumProofs, bytes32[] calldata changeQuantumCommitments, bytes[] calldata policyParamsArray ) external ``` Each reveal within the batch has its own ZK proof. All reveals must be for the same token type. The function iterates through each reveal, performing the full verification and minting flow for each. ### batchCommitWithPolicy Commits up to 50 commitments with policies attached (combines batch commit with policy binding). ### Single-Operation Compatibility The `BatchCommitRevealVault` also supports single commits and reveals with the same interface as the base vault: | Function | Batch Equivalent | |----------|-----------------| | `commitNative()` | `batchCommitNative()` with array length 1 | | `commitNativeWithPolicy()` | `commitNativeWithPolicy()` (same interface) | | `commit()` | `batchCommit()` with array length 1 | | `reveal()` | `batchReveal()` with array length 1 | ## Constants and Limits | Constant | Value | Description | |----------|-------|-------------| | `MAX_BATCH_SIZE` | 50 | Maximum commits or reveals per batch transaction | | `PUBLIC_INPUT_COUNT` | 8 | Public inputs per reveal (same as base vault) | ## Gas Analysis Batch operations reduce per-operation gas overhead by amortizing transaction base costs, calldata overhead, and (for commits) the burn operation: | Operation | Single Tx | Batch of 50 | Per-Op in Batch | Savings | |-----------|-----------|-------------|-----------------|---------| | Native commit | ~120K gas | ~2.5M gas | ~50K gas | ~58% | | Reveal | ~350K gas | ~16M gas | ~320K gas | ~8.5% | Reveals have less savings per operation because each reveal requires its own Groth16 proof verification (~280K gas), which cannot be amortized. The savings come from amortizing transaction overhead, storage slot warmup, and the single-mint-per-batch optimization for commits. **Practical limit:** At a 25 million gas block limit, a batch of 50 reveals (~16M gas) fits within a single block with room for other transactions. This is the practical safe limit -- larger batches risk exceeding the block gas limit. ## Solvency Tracking The batch vault independently tracks solvency: ```solidity mapping(address => uint256) public totalCommitted; // Per-token committed amounts uint256 private _totalNativeCommitted; // Native GHOST committed ``` The solvency invariant (`totalRevealed <= totalCommitted`) is enforced per token, mirroring the base vault's tracking. ## No Cooldown Unlike the base `CommitRevealVault`, the batch vault has **no per-address cooldown** between commits. This design choice reflects its intended use: batch callers are typically sequencers, agents, or high-throughput applications -- not individual end users. Rate limiting is handled at the application layer rather than the contract layer. ## Use Cases ### High-Throughput Token Operations An exchange or payment processor needs to process many private transfers per block. Instead of submitting 50 separate transactions, a single `batchCommitNative()` call commits all 50 amounts in one transaction. ### Batch Credential Issuance A university graduates 500 students and wants to commit 500 credential hashes. Using batch operations, this requires 10 transactions instead of 500, reducing the issuance time from minutes to seconds. ### Bulk Data Sealing A data archive service commits hashes of thousands of documents per day. Batch commits make this economically viable by reducing the per-document gas cost. ### Airdrop Distribution A project wants to privately distribute tokens to 1,000 recipients. Batch commits create all 1,000 commitments in 20 transactions, with each recipient later revealing their allocation individually. ## Parallel Operation The batch vault operates in parallel with the base vault. Both are active simultaneously, and users can choose which to use based on their needs: | Scenario | Recommended Vault | |----------|------------------| | Single private transfer | Base CommitRevealVault | | Multiple transfers in one session | BatchCommitRevealVault | | Policy-bound single commitment | Base CommitRevealVault | | Bulk issuance / batch processing | BatchCommitRevealVault | Commitments made in the batch vault can only be revealed through the batch vault (and vice versa), because each vault has its own Merkle tree and nullifier registry. The two systems are independent. --- # Session Vaults Phase 2 of the scaling roadmap introduces the `SessionVault` -- an **agent-based execution model** that enables hundreds of interactions to be settled in a few on-chain transactions. A user opens a session with a GHOST budget, an agent performs work off-chain, and the results are settled on-chain with cryptographic proofs of correctness. ## Model The SessionVault enables the "2 transactions for 1,000 interactions" model: 1. **Open session** (1 transaction): User locks GHOST budget and assigns an agent 2. **Agent works off-chain** (0 transactions): Agent executes 100-10,000 interactions 3. **Settle batches** (1-N transactions): Agent submits settlement proofs periodically 4. **Close session** (1 transaction): Remaining budget returned to user ## Session Lifecycle ### States | State | Description | |-------|-------------| | `INACTIVE` | Default state / session not created | | `ACTIVE` | Session is open, agent can settle batches | | `CLOSED` | Session ended normally, remaining budget returned to user | | `DISPUTED` | User has disputed a settlement, under review | | `EXPIRED` | Session passed its expiry block without being closed | ### Opening a Session ```solidity function openSession( address agent, bytes32 sessionCommitment, uint64 duration ) external payable returns (uint256 sessionId) ``` | Parameter | Description | |-----------|-------------| | `agent` | Address authorized to submit settlement batches | | `sessionCommitment` | Poseidon hash of session parameters (initial state root) | | `duration` | Session duration in blocks | | `msg.value` | GHOST budget locked for the session | Constraints: | Constraint | Value | Description | |-----------|-------|-------------| | `MIN_DURATION` | 300 blocks (~5 minutes) | Minimum session length | | `MAX_DURATION` | 604,800 blocks (~7 days) | Maximum session length | | Agent address | Must not be `address(0)` or `msg.sender` | Agent must be a separate address | | Budget | Must be > 0 | Session must have funds | ### Settling Batches The agent periodically settles completed work on-chain: ```solidity function settleBatch( uint256 sessionId, uint256 interactionCount, uint256 paymentAmount, bytes32 newStateRoot, bytes calldata batchProof ) external ``` The off-chain state is tracked as an **incremental Poseidon hash chain**: ``` stateRoot_0 = sessionCommitment stateRoot_n = Poseidon(stateRoot_{n-1}, interaction_hash_n) ``` The Groth16 proof verifies that the hash chain transition from `stateRootBefore` to `stateRootAfter` was computed correctly for the claimed number of interactions. This is an **integrity proof**, not a privacy proof -- it ensures the agent did not fabricate or skip interactions. Settlement proof public inputs: | Index | Field | Description | |-------|-------|-------------| | 0 | `sessionId` | The session being settled | | 1 | `interactionCount` | Number of interactions in this batch | | 2 | `paymentAmount` | GHOST earned by agent in this batch | | 3 | `stateRootBefore` | State root before this batch | | 4 | `stateRootAfter` | State root after processing interactions | On successful settlement, the agent receives payment **immediately** (optimistic). The user has a dispute window to challenge fraudulent settlements. ### Dispute Resolution Users can dispute a settlement within the `DISPUTE_WINDOW`: ```solidity function disputeBatch( uint256 sessionId, uint256 batchIndex, bytes calldata fraudProof ) external ``` | Constant | Value | Description | |----------|-------|-------------| | `DISPUTE_WINDOW` | 3,600 blocks (~1 hour) | Time window for user to dispute a settlement | The dispute window operates per-settlement: each settled batch has its own 3,600-block window. If the user does not dispute within this window, the settlement is considered finalized. When a dispute is submitted, the session transitions to the `DISPUTED` state. In the current implementation, disputes flag the session for manual review. Future versions will implement full on-chain fraud proof verification. ### Closing a Session ```solidity function closeSession(uint256 sessionId) external ``` Both the user and agent can close a session: - **User closes:** Can close at any time (users are the party protected by disputes) - **Agent closes:** Must wait for the dispute window to pass on the latest settlement On close, remaining budget (`budgetTotal - budgetSpent`) is returned to the user. ### Expired Sessions If a session passes its `expiryBlock` without being closed, anyone can call `reclaimExpired()` to return the remaining budget to the user: ```solidity function reclaimExpired(uint256 sessionId) external ``` This prevents GHOST from being permanently locked in an abandoned session. ## Session Data Each session stores: | Field | Type | Description | |-------|------|-------------| | `sessionCommitment` | `bytes32` | Poseidon hash of session parameters (initial state root) | | `agent` | `address` | Delegated agent address | | `user` | `address` | Session creator (budget provider) | | `budgetTotal` | `uint256` | Total GHOST locked at session open | | `budgetSpent` | `uint256` | Total GHOST paid to agent so far | | `settledCount` | `uint256` | Number of settled batches | | `totalInteractions` | `uint256` | Cumulative interactions across all batches | | `expiryBlock` | `uint64` | Session expires after this block | | `currentStateRoot` | `bytes32` | Latest settled state root | | `state` | `SessionState` | Current session state | ## Security Model The SessionVault uses an **optimistic settlement** model: 1. **Agent settles immediately:** Payment is sent to the agent on settlement (no delay) 2. **User can dispute:** Within the 3,600-block dispute window, the user can submit a fraud proof 3. **Dispute freezes session:** A disputed session transitions to `DISPUTED` state, preventing further settlements This model is optimistic because it assumes the agent is honest and pays immediately. The dispute mechanism provides recourse if the agent misbehaves. The economic incentive for honest behavior comes from: - **Reputation:** Agents who are disputed lose credibility - **Bond forfeiture:** Future versions will require agent bonds that are slashed on successful dispute - **Session termination:** A disputed session is immediately frozen ## Use Cases ### AI Agent Task Execution An AI agent handles a user's trading strategy. The user opens a session with a 100 GHOST budget. The agent executes 500 trades off-chain, settling every 50 trades. Each settlement proves the hash chain of trades was computed correctly and claims the agent's fee. ### Batch Data Processing A data pipeline commits thousands of data hashes per day. A session vault allows the pipeline agent to accumulate commits off-chain and settle them in batches, reducing the on-chain footprint from thousands of transactions to a handful of settlements. ### Subscription Services A service provider opens a session for a subscriber. The agent tracks API calls off-chain, settling the usage periodically. The subscriber's budget decreases with each settlement, and the session closes when the subscription ends or the budget is exhausted. --- # Sharded Merkle Trees Phases 3 and 4 of the scaling roadmap introduce **horizontal scaling** through multiple parallel Merkle trees and a **decentralized runner network** to manage them. The `ShardedTreeRegistry` manages independent commitment tree shards, while the `RunnerRegistry` handles operator staking, shard assignment, and slashing. ## The Scaling Bottleneck A single CommitmentTree at Merkle depth 20 supports approximately 1 million commitments. As the protocol grows, a single tree becomes a bottleneck: - Root updates serialize all commits through one operator - Proof generation requires the full tree state - The 1M leaf capacity is a hard limit Sharding solves this by deploying multiple independent trees, each handling a subset of commitments. ## ShardedTreeRegistry The `ShardedTreeRegistry` contract manages multiple `CommitmentTree` + `NullifierRegistry` pairs: ### Shard Structure Each shard consists of: | Component | Description | |-----------|-------------| | `commitmentTree` | Pre-deployed CommitmentTree contract for this shard | | `nullifierRegistry` | Pre-deployed NullifierRegistry contract for this shard | | `vault` | The vault authorized to write to this shard (typically a BatchCommitRevealVault) | | `active` | Whether the shard accepts new commitments | Shards reuse the existing `CommitmentTree` and `NullifierRegistry` contracts with **zero code changes** -- they are simply deployed N times. Each shard gets its own root updater, its own vault, and its own independent state. ### Capacity Scaling | Shards | Commitment Capacity | Description | |--------|-------------------|-------------| | 1 | ~1 million | Base vault (single tree, depth 20) | | 4 | ~4 million | 4 parallel trees | | 16 | ~16 million | 16 parallel trees | | 64 | ~64 million | 64 parallel trees | Capacity scales linearly with the number of shards. Each shard handles approximately 1 million commitments at depth 20. ### Deterministic Shard Assignment Commitments are assigned to shards deterministically based on the first byte of the commitment hash: ```solidity function getShardForCommitment(bytes32 commitment) external view returns (uint256) { if (shardCount == 0) return 0; return uint256(uint8(commitment[0])) % shardCount; } ``` This ensures that off-chain aggregators and proof generators know which shard a commitment belongs to **without on-chain coordination**. The assignment is deterministic and consistent -- given a commitment hash and a shard count, the shard is always the same. ### Global Nullifier Deduplication Each shard has its own local NullifierRegistry, but the `ShardedTreeRegistry` maintains a **global nullifier mapping** to prevent cross-shard double-reveals: ```solidity // 0 = not spent, shardId+1 = spent in that shard mapping(bytes32 => uint256) public nullifierShardOrigin; ``` When a shard's vault records a nullifier, it also calls `recordGlobalNullifier(nullifier, shardId)` on the registry. This global check ensures that a nullifier spent in Shard 0 cannot be spent again in Shard 5. The `+1` offset avoids ambiguity between "not spent" (0) and "spent in shard 0" (stored as 1). ### Immutable Shard Configuration Shard configuration follows an **append-only** model: - New shards can be **added** at any time by the owner - Active shards can be **deactivated** (no new commits, but existing reveals still work) - Deactivated shards **cannot be reactivated** or modified - Shard-to-tree mapping is set once and cannot be changed This immutability ensures that commitments made in a shard will always be revealable through that shard, regardless of future configuration changes. ### Shard Management Functions | Function | Description | |----------|-------------| | `createShard(tree, registry)` | Register a pre-deployed tree + registry pair as a new shard | | `setShardVault(shardId, vault)` | Set the authorized vault for a shard (one-time) | | `setShardRootOperator(shardId, operator)` | Set the root operator for a shard's tree | | `deactivateShard(shardId)` | Freeze a shard (no new commits) | | `transferShardTreeOwnership(shardId, newOwner)` | Emergency ownership transfer | | `transferShardRegistryOwnership(shardId, newOwner)` | Emergency ownership transfer | ## RunnerRegistry (Phase 4) The `RunnerRegistry` decentralizes shard management by introducing **bonded operators** (runners) who manage shards independently: ### Runner Registration Operators register as runners by staking GHOST: ```solidity function registerRunner(string calldata endpoint) external payable returns (uint256 runnerId) ``` | Constant | Value | Description | |----------|-------|-------------| | `MIN_STAKE` | 10,000 GHOST | Minimum stake to register as a runner | | `MAX_SHARDS_PER_RUNNER` | 8 | Maximum shards a single runner can manage | | `UNBONDING_PERIOD` | 604,800 blocks (~7 days) | Cooldown before stake withdrawal | | `SLASH_PERCENT` | 50% | Percentage of stake forfeited on slashing | Each runner provides an `intentSequencerEndpoint` -- a WebSocket URL that agents connect to for submitting intents and receiving batch results. ### Shard Assignment Shards are assigned to runners by the registry owner: ```solidity function assignShard(uint256 runnerId, uint256 shardId) external onlyOwner ``` Each shard can be assigned to exactly one runner. Each runner can manage up to `MAX_SHARDS_PER_RUNNER` (8) shards. When a shard is assigned, the runner becomes responsible for: - Running the root updater (computing and publishing Merkle roots) - Running the proof farm (generating Groth16 proofs for users) - Running the intent sequencer (receiving and ordering user operations) ### Runner Responsibilities | Responsibility | Description | |---------------|-------------| | **Root updater** | Compute Merkle root updates for assigned shards and submit them on-chain | | **Proof farm** | Generate Groth16 proofs for users who need to reveal commitments in the runner's shards | | **Intent sequencer** | Accept user intents (commit/reveal requests) via WebSocket, batch them, and submit on-chain | | **Liveness** | Maintain uptime; extended downtime may result in shard reassignment | ### Slashing Runners can be slashed for misbehavior: ```solidity function slashRunner(uint256 runnerId, bytes calldata reason) external onlyOwner ``` On slashing: 1. **50% of the runner's stake** is sent to the `slashRecipient` (treasury) 2. The runner's state transitions to `SLASHED` 3. **All assigned shards are unassigned** (available for reassignment to other runners) 4. The runner retains the remaining 50% of their stake In the current implementation, slashing is owner-initiated with a reason parameter. Future versions will implement on-chain fraud proof verification, where anyone can submit a proof of misbehavior to trigger slashing. ### Exit Lifecycle Runners exit through a two-step process: 1. **`requestExit()`** -- Runner requests to exit. State transitions to `EXITING`, all assigned shards are unassigned, and the unbonding clock starts. 2. **`completeExit()`** -- After the unbonding period (604,800 blocks, ~7 days), the runner withdraws their stake. State transitions to `INACTIVE`. The unbonding period ensures that any misbehavior during the runner's active period can still be detected and slashed before the stake is returned. ### Pull-Based Withdrawal Stake returns use a **pull-based** pattern: the runner must call `completeExit()` to receive their funds. This avoids the reentrancy risks of push-based withdrawals and ensures the runner explicitly acknowledges the exit. ## Self-Scaling Economics The runner network creates a **self-scaling** system: 1. **Increased demand** leads to more commitments and reveals, generating more fees 2. **Higher fees** make running a shard more profitable 3. **More operators stake** GHOST and register as runners to capture fee revenue 4. **More runners** mean more shards can be managed, increasing total throughput 5. **Increased throughput** handles the growing demand The chain remains the settlement backbone. Runners handle the heavy lifting of Merkle root computation, proof generation, and intent sequencing. The protocol scales by adding more runners rather than increasing block size or reducing block time. --- # Relayer Network Specter's data privacy protocol relies on three off-chain services that bridge the gap between computationally constrained clients (mobile devices, browsers, light wallets) and the on-chain smart contracts that require Poseidon hashes, Merkle roots, and Groth16 proofs. These services are **computation-only** — they never see your secrets, private keys, or plaintext data. All three relayers are Node.js processes managed by PM2, bound to `localhost`, and proxied to the public internet via nginx with TLS termination. ## Architecture Overview ## Root Updater (Port 3001) The Root Updater is a background service that keeps the on-chain Merkle root synchronized with the current state of the commitment tree. ### Why It Exists The commitment Merkle tree stores leaves on-chain, but recomputing the full Merkle root from all leaves on every transaction would be prohibitively expensive in gas. Instead, the Root Updater runs off-chain: 1. **Reads** the current leaf count from the on-chain `CommitmentTree` contract. 2. **Fetches** all commitment leaves from the tree. 3. **Computes** the Merkle root off-chain using Poseidon hashing over the full tree. 4. **Publishes** the new root on-chain by calling the root update function. Without the Root Updater, **reveals (Summon operations) cannot verify Merkle membership** against the current state. A stale root means valid commitments appear absent from the tree, causing proof verification to fail. ### Operational Characteristics | Property | Value | |---|---| | Update interval | Configurable (default: every new commitment batch) | | Root history | On-chain contract stores a rolling window of recent roots | | Failure mode | Reveals fail against stale roots; commits continue unaffected | | Recovery | Stateless — restarts from current on-chain leaf count | ### Trust Implication The Root Updater computes a **deterministic** Merkle root from public on-chain data. Any party can independently verify the published root by reading the same leaves and recomputing. A compromised Root Updater could publish a stale root (denial-of-service) but **cannot publish a fraudulent root** — the on-chain verifier would reject proofs against an incorrect root. ## Commitment Relayer (Port 3002) The Commitment Relayer computes Poseidon commitment hashes on behalf of clients that cannot efficiently run the Poseidon hash function locally. ### Why It Exists Poseidon hashing operates over the BN254 scalar field (a ~254-bit prime). Computing `Poseidon4(secret, nullifierSecret, dataHash, blinding)` or the 7-input token variant requires modular arithmetic over this large field — operations that are expensive on mobile processors and WebAssembly environments. The Commitment Relayer accepts the **hashed inputs** (not the raw data) and returns the computed Poseidon commitment. Desktop clients with sufficient compute can bypass this relayer entirely and compute commitments locally. ### Request Flow ### Trust Implication The Commitment Relayer receives **derived field elements** — outputs of key derivation functions, not raw secrets. Even if the relayer is fully compromised, the attacker receives Poseidon input field elements that are computationally infeasible to reverse back to the original seed or plaintext data. The commitment output is deterministic and verifiable — a client can check the result against a local computation or a second relayer. ## Proof Relayer (Port 3003) The Proof Relayer generates Groth16 zero-knowledge proofs on behalf of clients that lack the computational resources to run the proving algorithm locally. ### Why It Exists Groth16 proof generation for the Specter redemption circuit involves: - Evaluating a constraint system with thousands of R1CS constraints - Performing multi-scalar multiplications on the BN254 curve - Computing an FFT over the evaluation domain On a modern desktop, this takes 2-5 seconds. On a mobile device, it can take 30-60 seconds or fail entirely due to memory constraints. The Proof Relayer performs this computation server-side and returns the serialized proof to the client. ### Request Flow ### Trust Implication The Proof Relayer receives a **witness** — the set of private inputs to the ZK circuit. These inputs are field elements (secret, nullifierSecret, blinding, Merkle path siblings, etc.) derived via HKDF from a seed the relayer never sees. The relayer computes a proof that these values satisfy the circuit constraints, but: - It cannot reverse the field elements back to the original seed. - It cannot determine which commitment in the Merkle tree the proof corresponds to (the commitment index is not directly exposed). - It cannot alter the proof to redirect funds — the recipient address is bound into the public inputs verified on-chain. A compromised Proof Relayer could **refuse to generate proofs** (denial-of-service) or **log witnesses for future analysis**, but it cannot steal data or redirect value. ## Faucet (Port 3005) The Faucet is a testnet-only service that distributes GHOST tokens to developers and testers. It is not part of the production protocol and will be decommissioned at mainnet launch. | Property | Value | |---|---| | Purpose | Distribute testnet GHOST tokens | | Rate limiting | Per-wallet, time-based | | Environment | Testnet only | ## Authentication All relayer endpoints are authenticated using a two-factor scheme: 1. **HMAC Authentication**: Each request includes an HMAC signature computed over the request body using a shared secret. This prevents unauthorized access and request tampering. 2. **EIP-191 Wallet Signature**: Clients sign a challenge message with their Ethereum-compatible wallet private key. This binds the request to a specific wallet address and prevents replay attacks. Both factors must be valid for the relayer to process the request. ## Network Configuration All services follow the same deployment pattern: | Property | Detail | |---|---| | Binding | All services bind to `localhost` only — no external network exposure | | TLS | nginx terminates TLS with valid certificates | | Process management | PM2 with automatic restart on crash, log rotation, and cluster mode | | Rate limiting | nginx-level rate limiting per IP and per endpoint | | Monitoring | PM2 metrics + nginx access logs | ## Trust Model Summary The relayer network operates on a principle of **computational delegation without trust**: | Threat | Impact | Mitigation | |---|---|---| | Relayer sees commitment inputs | Cannot reverse field elements to raw data | Inputs are HKDF-derived; one-way | | Relayer sees proof witness | Cannot identify which commitment is being revealed | Witness contains field elements, not indices | | Relayer logs all requests | Historical data is field elements only | No usable plaintext is ever transmitted | | Relayer goes offline | Denial-of-service for light clients | Desktop clients compute locally; multiple relayers can be deployed | | Relayer publishes wrong root | Proofs against incorrect root fail verification | Root is deterministic and independently verifiable | | Relayer modifies proof | On-chain verifier rejects invalid proofs | Groth16 soundness guarantees | The fundamental guarantee: **even a fully compromised relayer cannot steal your data, redirect your tokens, or link your commits to your reveals.** Relayers are a convenience layer for resource-constrained clients, not a trust dependency. --- # Stealth Addresses Specter implements an **ERC-5564 compatible stealth address system** for private, one-time recipient addresses. Stealth addresses allow a sender to create a fresh, unlinkable address for every transfer — ensuring that on-chain observers cannot determine who received a payment, even when the sender and recipient are known entities. This system complements the Ghost Protocol. Where the Ghost Protocol provides privacy through commit/reveal unlinkability (vanish and summon are unconnectable), stealth addresses provide privacy at the **transfer layer** — hiding the recipient of a direct on-chain token transfer. ## How Stealth Addresses Work The stealth address scheme uses **Elliptic Curve Diffie-Hellman (ECDH)** key agreement on the secp256k1 curve. The recipient publishes a **meta-address** (a pair of public keys) once, and senders derive unique one-time addresses from it without any interaction. ### Key Components | Component | Description | |---|---| | **Spending key** (`p_spend`) | Recipient's long-term private key for claiming funds | | **Viewing key** (`p_view`) | Recipient's private key for scanning and identifying incoming transfers | | **Meta-address** | Public keys `(P_spend, P_view)` published once by the recipient | | **Ephemeral keypair** | One-time keypair `(r, R)` generated by the sender for each transfer | | **Stealth address** | One-time address derived from ECDH shared secret | | **View tag** | Single-byte hint for efficient scanning | ### Protocol Flow ### Step-by-Step Breakdown **1. Sender generates an ephemeral keypair** The sender creates a random scalar `r` and computes the corresponding public point `R = r * G`, where `G` is the secp256k1 generator. This keypair is used only once and discarded after the transfer. **2. Sender computes the one-time stealth address via ECDH** Using the recipient's published meta-address `(P_spend, P_view)`, the sender computes a shared secret: ``` s = r * P_view ``` The stealth public key is derived as: ``` P_stealth = P_spend + hash(s) * G ``` The stealth address is the Ethereum-style address of `P_stealth`. This address has never appeared on-chain before and is unlinkable to the recipient's known addresses. **3. Sender calls GhostStealthAnnouncer** The sender invokes the `GhostStealthAnnouncer` contract, which: - Transfers tokens to the computed stealth address. - Records the transfer metadata — the ephemeral public key `R` and a **view tag** (first byte of `hash(s)`) — in an on-chain storage array. **4. Recipient scans the on-chain transfers array** The recipient periodically reads the `transfers` array from the `GhostStealthAnnouncer` contract. For each entry: - **View tag pre-filtering**: The recipient computes `hash(p_view * R_entry)` and checks if the first byte matches the stored view tag. This is a cheap check that filters out ~99.6% of non-matching transfers without performing the full ECDH computation. - **Full ECDH verification**: For entries that pass the view tag filter, the recipient performs the complete shared secret derivation and verifies the stealth address matches. **5. Recipient derives the stealth private key and claims funds** Once a matching transfer is identified, the recipient derives the stealth private key: ``` p_stealth = p_spend + hash(s) ``` This private key controls the stealth address. The recipient signs a transaction with it to claim the funds. ## On-Chain Array Storage A standard ERC-5564 implementation on Ethereum uses **event logs** for the announcement mechanism — the sender emits an event containing the ephemeral public key and view tag, and the recipient scans event logs using `eth_getLogs`. Specter uses **on-chain array storage** instead of event logs. This is a deliberate design choice driven by a limitation of the Cosmos EVM environment: | Approach | Ethereum | Specter (Cosmos EVM) | |---|---|---| | Announcement mechanism | Event logs | Storage array | | Scanning method | `eth_getLogs` RPC | Direct storage reads | | Why | Efficient log indexing available | `eth_getLogs` not reliably supported in Cosmos EVM | | Trade-off | Lower storage cost, index-dependent | Higher storage cost, no indexer dependency | The `GhostStealthAnnouncer` contract maintains a `transfers` array in contract storage. Each entry contains: ```solidity struct StealthTransfer { address ephemeralPubKeyAddr; // Compressed ephemeral public key uint8 viewTag; // First byte of ECDH shared secret hash address stealthAddress; // Derived one-time address uint256 amount; // Transfer amount } ``` Recipients scan this array directly via `eth_call` (read-only RPC) without needing a log indexer. ## View Tag Optimization Scanning all on-chain transfers requires an ECDH computation per entry, which becomes expensive as the transfer count grows. The **view tag** provides a 1-byte probabilistic filter: The view tag eliminates 255/256 (~99.6%) of non-matching entries from requiring full ECDH computation, reducing scanning cost by approximately two orders of magnitude. ## Integration with the Ghost Protocol Stealth addresses and the Ghost Protocol provide complementary privacy guarantees. They can be composed for layered privacy: **Flow**: A sender transfers tokens to a stealth address (hiding the recipient). The recipient claims the stealth funds and immediately **vanishes** them into the Ghost Protocol (hiding the amount and breaking the transaction graph). Later, the recipient **summons** the value to any address (hiding the link between claim and spend). This composition provides: 1. **Recipient privacy** — the stealth address hides who received the initial transfer. 2. **Amount privacy** — the vanish operation commits the value without revealing it. 3. **Graph privacy** — the summon operation is unlinkable to the vanish, breaking the transaction trail. ## Privacy Properties | Property | Guarantee | |---|---| | Recipient unlinkability | Each transfer uses a unique one-time address; no on-chain link to recipient's known addresses | | Sender privacy | Sender's address is visible on-chain (stealth addresses hide recipients, not senders) | | Transfer amount | Visible on-chain in the current implementation | | Ephemeral key reuse | Each transfer MUST use a fresh ephemeral keypair; reuse breaks recipient privacy | | Meta-address publication | Published once; does not reveal spending key or viewing key | | Scanning privacy | Only the viewing key holder can identify their transfers; requires reading on-chain data | ## Security Considerations - **Ephemeral key uniqueness**: If a sender reuses an ephemeral keypair `(r, R)` for two transfers to different recipients, both recipients can compute the same shared secret. This does not directly compromise funds but leaks the fact that both transfers came from the same sender with the same ephemeral randomness. Clients MUST generate a fresh ephemeral keypair per transfer. - **View tag collisions**: The 1-byte view tag produces false positives at a rate of ~1/256. This is a performance optimization, not a security mechanism. False positives are resolved by the full ECDH check. - **Scanning cost**: As the transfers array grows, scanning cost increases linearly. Future optimizations may include sharded announcement contracts or off-chain indexing with Merkle proofs of inclusion. --- # Cross-Chain Interoperability Specter is a sovereign blockchain, but data and value do not exist in isolation. Users need to move assets between Specter and other chains — both within the Cosmos ecosystem and across to Ethereum, L2 rollups, and other EVM-compatible networks. Specter supports two bridge mechanisms, each suited to a different connectivity domain. ## Bridge Architecture Overview ## IBC (Inter-Blockchain Communication) ### Overview IBC is the native cross-chain communication protocol of the Cosmos ecosystem. Specter inherits full IBC support through the **Cosmos SDK v0.53.2** application framework and **ibc-go v10**. IBC provides **trustless** cross-chain communication — it does not rely on multisigs, validators, or trusted relayers for security. Instead, it uses light client verification: each chain maintains a light client of the counterparty chain, and cross-chain messages are verified against the counterparty's consensus state. If the counterparty chain has honest consensus, the bridge is secure. ### How IBC Works ### IBC Capabilities on Specter | Capability | Status | Description | |---|---|---| | ICS-20 token transfers | Supported | Standard fungible token transfers between IBC-connected chains | | IBC channels | Supported | Bidirectional communication channels with any ibc-go compatible chain | | Light client verification | Supported | Trustless verification via CometBFT light clients | | Timeout and refund | Supported | Automatic refund if destination chain does not acknowledge within timeout | ### Trust Model IBC's trust model is fundamentally different from bridge multisigs: | Property | IBC | Typical Bridge Multisig | |---|---|---| | Trust assumption | Counterparty chain has honest consensus (>2/3 validators) | M-of-N bridge signers are honest | | Verification | On-chain light client proof verification | Off-chain signature aggregation | | Failure mode | Byzantine counterparty consensus (extremely unlikely) | Key compromise of M signers (historically common) | | Liveness | Depends on relayers submitting packets (permissionless role) | Depends on bridge operators | ### Future: Private Cross-Chain Transfers The long-term vision for IBC on Specter is **private cross-chain transfers** — data is vanished on the source chain and summoned on the destination chain, with the IBC packet carrying only a commitment and proof rather than plaintext asset information. This is a Phase 4 roadmap item. The protocol infrastructure (IBC channels, light clients, packet relay) is already operational; the privacy layer (commitment encoding in IBC packets, cross-chain Merkle proof verification) is under research. ## Hyperlane ### Overview Hyperlane is a modular interoperability protocol that connects Specter to **non-Cosmos chains** — Ethereum mainnet, L2 rollups (Arbitrum, Optimism, Base), and other EVM-compatible networks. Unlike IBC, which relies on light client consensus verification, Hyperlane uses a pluggable security model based on **Interchain Security Modules (ISMs)**. ### Contract Architecture Specter deploys a set of Hyperlane-specific contracts for bridging: #### HypGhostERC20Collateral Deployed on the **remote chain** (e.g., Ethereum). When a user bridges tokens to Specter: 1. The user deposits tokens (USDC, WETH, LABS, etc.) into the collateral contract. 2. Tokens are **locked** in the contract. 3. A cross-chain message is dispatched via the Hyperlane Mailbox. When tokens are bridged back: 1. A cross-chain message arrives from Specter. 2. The collateral contract **unlocks** and returns the original tokens to the user. #### HypGhostERC20Synthetic Deployed on **Specter**. When a cross-chain message arrives from a remote chain: 1. The ISM verifies the message authenticity. 2. The synthetic contract **mints** Ghost-wrapped tokens (e.g., gUSDC, gWETH, gLABS) to the recipient. When tokens are bridged out: 1. The synthetic contract **burns** the Ghost-wrapped tokens. 2. A cross-chain message is dispatched to the remote chain to unlock collateral. #### HyperlaneTokenRegistry A registry contract that maps **warp routes** — the correspondence between a token on one chain and its synthetic representation on another. This enables the bridge contracts to resolve which synthetic token to mint for a given collateral deposit, and vice versa. ### Interchain Security Modules (ISMs) Hyperlane's pluggable security model allows Specter to choose the verification mechanism for cross-chain messages: | ISM Type | Description | Trust Assumption | |---|---|---| | **TrustedRelayerISM** | A single designated relayer attests to message validity | Trust in one relayer entity | | **MultisigISM** | M-of-N validators must sign to attest message validity | Trust in M of N validators being honest | The current deployment uses **TrustedRelayerISM** for testnet simplicity. The production configuration will migrate to **MultisigISM** with a geographically and organizationally diverse validator set. ### Bridge Tokens The following Ghost-wrapped synthetic tokens are currently supported: | Synthetic Token | Underlying Asset | Source Chain | Collateral Type | |---|---|---|---| | **gUSDC** | USDC | Ethereum / L2s | ERC-20 | | **gWETH** | WETH | Ethereum / L2s | ERC-20 | | **gLABS** | LABS | Ethereum / L2s | ERC-20 | Ghost-wrapped tokens (prefixed with `g`) are standard ERC-20 tokens on Specter. They can be: - Held and transferred like any token. - **Vanished** into the Ghost Protocol for private storage or transfer. - **Summoned** back from the Ghost Protocol to any address. - Bridged back to the original chain by burning the synthetic and unlocking collateral. ### Integration with Ghost Protocol The bridge and the privacy protocol compose naturally: A user can bridge USDC from Ethereum, receive gUSDC on Specter, vanish it into the Ghost Protocol, summon it to a fresh address, and bridge it back to Ethereum — achieving full transaction graph breakage across chains. ## Comparison of Bridge Mechanisms | Property | IBC | Hyperlane | |---|---|---| | Target chains | Cosmos ecosystem | EVM chains (Ethereum, L2s) | | Trust model | Light client verification (trustless) | ISM-dependent (trusted relayer or multisig) | | Latency | ~10-30 seconds (finality-dependent) | ~2-15 minutes (depending on source chain finality) | | Token model | ICS-20 vouchers | Collateral lock / synthetic mint | | Maturity | Production (years of mainnet operation) | Production (deployed across multiple chains) | | Privacy integration | Future (Phase 4) | Future (Phase 4) | | Native to Specter | Yes (Cosmos SDK built-in) | Via deployed Solidity contracts | --- # Threat Model This page defines the adversary classes Specter's data privacy protocol is designed to resist, what the protocol protects against, what it does **not** protect against, and how multiple defense layers compose to provide depth. A threat model is not marketing. It is a precise enumeration of what an attacker can and cannot do. Understanding the boundaries of the protocol's guarantees is essential for users, integrators, and auditors. ## Adversary Classes ### 1. Network Observer **Capabilities**: Can see all on-chain data — every transaction, every block, every state change. Can run a full node. Can query any RPC endpoint. Can monitor the mempool. **Examples**: Blockchain analytics firms, chain indexers, curious validators, any user running a full node. This is the most common adversary. Every public blockchain assumes network observers exist. The Ghost Protocol is primarily designed to defeat this adversary class. ### 2. Malicious Validator **Capabilities**: Everything a network observer can do, plus the ability to **censor transactions** (refuse to include them in blocks) and **reorder transactions** within a block they propose. Cannot forge transactions or alter protocol rules (the rest of the validator set enforces consensus). **Examples**: A compromised validator operator, a validator coerced by a state actor. With CometBFT consensus, a single malicious validator has limited power — they can only censor/reorder during blocks they propose. Sustained censorship requires control of >1/3 of the validator set (liveness failure) or >2/3 (safety failure, which implies consensus corruption). ### 3. Compromised Relayer **Capabilities**: Full access to the relayer infrastructure — can read all inputs and outputs, modify code, log all requests. Can refuse to serve requests (denial-of-service). **Examples**: A hacked relayer server, a malicious relayer operator. See the [Relayer Network](../infrastructure/relayer-network.md) page for detailed analysis. The critical property: relayers perform **computation on derived values only** and cannot extract raw secrets, redirect value, or link commits to reveals. ### 4. Quantum Adversary **Capabilities**: Access to a cryptographically relevant quantum computer capable of running Shor's algorithm on elliptic curve discrete logarithms and Grover's algorithm on symmetric primitives. **Examples**: Future state actors, advanced research labs. Not currently practical (estimated 10-20+ years for cryptographically relevant quantum computers). This adversary can break BN254 elliptic curve security (Groth16 proofs, ECDH key agreement) but **not** symmetric primitives at 128-bit security (Keccak-256, AES-256 under Grover's provide 128-bit post-quantum security). ## What Specter Protects Against ### Transaction Linkability **Threat**: An observer tries to determine which Summon (reveal) corresponds to which Vanish (commit). **Defense**: The Vanish operation inserts a Poseidon commitment into a Merkle tree. The Summon operation generates a Groth16 ZK proof that the prover knows the preimage of *some* commitment in the tree — without revealing which one. The nullifier (derived from `nullifierSecret`) is unrelated to the commitment hash. There is no on-chain data connecting a specific Vanish to a specific Summon. An observer sees N commits and M reveals but cannot determine the mapping between them. The anonymity set is the full set of commitments in the Merkle tree. ### Data Content Disclosure **Threat**: An observer tries to determine what data was committed. **Defense**: The commitment is `Poseidon4(secret, nullifierSecret, dataHash, blinding)` or the 7-input token variant. The Poseidon hash is a one-way function — given the commitment, the observer cannot recover the inputs. The `blinding` factor ensures that even identical data produces different commitments (semantic security). ### Recipient Disclosure **Threat**: An observer tries to determine who originally committed the data being revealed. **Defense**: The ZK proof demonstrates knowledge of a valid commitment preimage and Merkle membership. It does not identify the committer — only that the prover knows the secret values. The proof's public inputs are the Merkle root, the nullifier, and the data hash (or token details). None of these link to the committer's identity. ### Front-Running **Threat**: An observer sees a pending Summon transaction in the mempool and submits their own transaction to claim the value first. **Defense**: The recipient address is **bound into the ZK proof as a public input**. The on-chain verifier checks that the proof was generated for the specific recipient address in the transaction. A front-runner cannot reuse the proof for a different address — the proof would fail verification. ### Double-Spending **Threat**: A user tries to reveal the same commitment twice. **Defense**: Every Summon operation records a **nullifier** derived from `nullifierSecret`. The `NullifierRegistry` contract enforces that each nullifier can only be used once. A second reveal attempt with the same commitment preimage produces the same nullifier, which is rejected. ### Commitment Forgery **Threat**: An attacker tries to generate a valid ZK proof for a commitment they did not create. **Defense**: The Groth16 proof requires knowledge of the full commitment preimage (`secret`, `nullifierSecret`, `dataHash`, `blinding`). Without these values, the attacker cannot generate a satisfying witness. Groth16 soundness guarantees that no polynomial-time adversary can produce a valid proof without a valid witness (under the knowledge-of-exponent assumption). ## What Specter Does NOT Protect Against ### Timing Analysis **Threat**: An observer correlates the timing of Vanish and Summon operations. If Alice vanishes 100 GHOST at 3:01 PM and Bob summons 100 GHOST at 3:02 PM, and no other operations occurred in between, the link is probabilistically obvious. **Mitigation (partial)**: The relayer network introduces some timing decoupling — light clients submit through relayers, which batch and delay submissions. However, this is **not a guaranteed defense**. A sophisticated adversary with full mempool visibility can still perform timing analysis. **Recommendation**: Users should wait for the anonymity set to grow before summoning. The more commits that occur between a vanish and summon, the larger the anonymity set and the weaker the timing correlation. ### Metadata Leakage **Threat**: An observer correlates IP addresses, browser fingerprints, or network metadata to link commits and reveals. **Mitigation (none at protocol level)**: Specter is a data protocol, not a network anonymity system. It does not provide IP obfuscation, traffic analysis resistance, or metadata protection. Users who require metadata privacy should use Tor, VPNs, or other network-level anonymity tools. ### Side-Channel Attacks on Client Devices **Threat**: Malware on the user's device reads the Phantom Key, seed, or private key material from memory, disk, or clipboard. **Mitigation (none at protocol level)**: Client-side security is outside the protocol's scope. Specter provides passphrase encryption (AES-256-GCM with PBKDF2 key derivation) for stored secrets, but this does not defend against an attacker with code execution on the device. ### Social Engineering **Threat**: An attacker convinces a user to reveal their Phantom Key, share their Phantom Identity PNG, or submit a transaction to a malicious contract. **Mitigation (none at protocol level)**: Social engineering is a human problem, not a cryptographic one. User education and UX design (e.g., warnings before sharing bearer instruments) are the appropriate mitigations. ### Anonymity Set Size **Threat**: If the anonymity set (total number of commitments in the Merkle tree) is small, statistical analysis can narrow the possible commit-reveal links even without breaking cryptography. **Mitigation (partial)**: The anonymity set grows over time as more users commit data. Batch operations and the Open Ghost Protocol (allowing third parties to commit on behalf of others) accelerate anonymity set growth. However, in early network stages, the anonymity set may be small enough for probabilistic deanonymization. ### Global Passive Adversary with Auxiliary Information **Threat**: An adversary who observes all on-chain data AND has off-chain auxiliary information (e.g., knows Alice received exactly 47.3 GHOST from an exchange at a specific time) can potentially link commits to real-world identities. **Mitigation (partial)**: Fixed denominations (when used), time delays, and large anonymity sets reduce the effectiveness of auxiliary information. But they do not eliminate the risk entirely. ## Defense-in-Depth Layers Specter's security is not a single mechanism but a composition of independent layers. Compromise of one layer does not necessarily compromise the system: | Layer | Protects Against | Failure Mode | |---|---|---| | **ZK Proofs (Groth16)** | Linkability, data disclosure, identity disclosure | Broken if BN254 discrete log is solved or trusted setup is compromised | | **On-Chain Verification** | Invalid proofs, double-spending, unauthorized reveals | Broken if smart contract has a bug (mitigated by audits) | | **Quantum Commitments** | Future quantum adversaries breaking BN254 | Provides 128-bit post-quantum security via Keccak-256 | | **Policy Enforcement** | Unauthorized access patterns, compliance violations | Broken if policy contract logic has a bug | No single point of failure compromises all layers simultaneously. A quantum computer breaks Layer 1 but not Layer 3. A smart contract bug in the policy system affects Layer 4 but not Layers 1-3. This layered approach ensures the protocol degrades gracefully rather than catastrophically. --- # Cryptographic Assumptions Every cryptographic system relies on assumptions — mathematical problems believed to be hard, security parameters believed to be sufficient, and construction properties believed to hold. This page enumerates the specific assumptions underlying Specter's data privacy protocol, their strength, and their known vulnerabilities. If any of these assumptions is broken, the corresponding protocol component fails. Understanding which assumptions support which components is essential for evaluating the protocol's security posture and planning migration paths. ## BN254 Elliptic Curve The **BN254 curve** (also known as alt-bn128 or bn256) is the elliptic curve used for all Groth16 proof generation and verification in Specter. It was chosen because it is the curve supported by Ethereum's `ecPairing` precompile (`0x08`), enabling efficient on-chain proof verification. ### Security Level BN254 provides approximately **100-110 bits of security** against classical attacks (revised downward from the original 128-bit estimate due to advances in the Number Field Sieve for computing discrete logarithms on pairing-friendly curves). For practical purposes, this remains computationally infeasible for classical adversaries. ### Hardness Assumptions | Assumption | Description | Used For | |---|---|---| | **Elliptic Curve Discrete Log (ECDLP)** | Given points `P` and `Q = kP`, computing `k` is infeasible | Foundation of all BN254-based cryptography | | **Decisional Linear (DLIN)** | Given `(P, aP, bP, cP)`, deciding if `c = a+b` is infeasible | Bilinear pairing security | | **Bilinear Diffie-Hellman (BDH)** | Computing `e(P,Q)^{abc}` from `(aP, bQ, cP)` is infeasible | Pairing-based proof verification | | **Knowledge of Exponent (KEA)** | An adversary that outputs `(C, Y)` where `Y = xC` must "know" `x` | Groth16 soundness (extractability) | ### Quantum Vulnerability BN254 is **vulnerable to Shor's algorithm** on a sufficiently large quantum computer. Shor's algorithm solves the elliptic curve discrete log problem in polynomial time, which would break: - Groth16 proof soundness (an adversary could forge proofs) - ECDH key agreement in the stealth address system - Any scheme relying on the hardness of ECDLP on BN254 Specter's **quantum commitment layer** (Keccak-256 based) provides a defense-in-depth against this future threat. See the [Threat Model](./threat-model.md) for details. ### Migration Path If BN254 security is deemed insufficient (either due to quantum advances or classical cryptanalysis improvements), the protocol can migrate to: - **BLS12-381**: ~120-bit security, widely supported, already used in Ethereum 2.0 - **BW6-761**: ~128-bit security, supports efficient proof composition - **Lattice-based curves**: Full quantum resistance (research stage) ## Groth16 Proof System **Groth16** is the zero-knowledge proof system used for all commit-reveal proofs in Specter. It was chosen for its minimal proof size (3 group elements, ~128 bytes) and fast verification (a single pairing check). ### Soundness Assumptions | Assumption | Description | Implication if Broken | |---|---|---| | **q-Power Knowledge of Exponent (q-PKE)** | An adversary given powers of a secret `s` in both source groups must "know" the polynomial it computes | Adversary could forge proofs without knowing the witness | | **q-Strong Diffie-Hellman (q-SDH)** | Computing `(c, g^{1/(s+c)})` from `(g, g^s, g^{s^2}, ..., g^{s^q})` is infeasible | Adversary could produce valid-looking proofs for false statements | | **Generic Group Model (GGM)** | Adversary interacts with group elements only via generic operations | Model-dependent; real-world attacks may exploit algebraic structure | ### Trusted Setup Requirement Groth16 requires a **circuit-specific trusted setup ceremony** that generates a structured reference string (SRS). The setup produces: - A **proving key** (used to generate proofs) - A **verification key** (used to verify proofs on-chain) - **Toxic waste** (secret randomness that MUST be destroyed) If the toxic waste is not destroyed — if any single participant in the ceremony retains their contribution — they can forge proofs for any statement. This is the **most critical trust assumption** in the protocol. **Mitigation**: Multi-party computation (MPC) ceremonies ensure that the toxic waste is destroyed as long as **at least one participant** is honest and destroys their randomness. Specter's Phase 2 roadmap includes a public MPC ceremony. See [Trusted Setup](../zk-proofs/trusted-setup.md) for full details. ### Proof Properties | Property | Guarantee | Assumption | |---|---|---| | **Completeness** | An honest prover with a valid witness always produces an accepted proof | None (information-theoretic) | | **Soundness** | No polynomial-time adversary can produce a proof for a false statement | q-PKE, q-SDH | | **Zero-Knowledge** | The proof reveals nothing about the witness beyond the statement's truth | Simulation-based; holds in the random oracle model | | **Succinctness** | Proof size is constant (3 group elements) regardless of circuit size | By construction | ## Poseidon Hash Function **Poseidon** is the algebraic hash function used for all commitment hashes, nullifier derivations, Merkle tree nodes, access tags, and token identifiers in Specter. ### Security Model Poseidon is designed to be secure as a **collision-resistant hash function** and **pseudorandom function** over the BN254 scalar field. Its security relies on the algebraic hardness of the underlying permutation. ### Attack Resistance | Attack Class | Description | Poseidon Defense | |---|---|---| | **Grobner basis attacks** | Solve the polynomial system representing the hash | Full rounds ensure high algebraic degree (degree 5^R after R rounds) | | **Interpolation attacks** | Recover the permutation polynomial via interpolation | Requires degree > field size; conservative round count prevents this | | **Differential cryptanalysis** | Exploit input-output differential patterns | Wide trail strategy in the linear layer; analyzed bounds on differential probability | | **Linear cryptanalysis** | Exploit linear approximations of the S-box | S-box `x^5` has optimal nonlinearity over prime fields | | **Algebraic attacks** | Exploit the low-degree S-box structure | Partial rounds add non-uniform structure; full rounds dominate security margin | ### Round Structure Poseidon uses a combination of **full rounds** and **partial rounds**: - **Full rounds** (`R_F`): Every state element passes through the S-box (`x^5`). Provides security against statistical attacks. - **Partial rounds** (`R_P`): Only one state element passes through the S-box. Provides security against algebraic attacks at lower cost. The parameters used in Specter (e.g., `t=5, R_F=8, R_P=57` for 4-input Poseidon) provide a **security margin of ~2x** over the minimum rounds required by the original Poseidon paper's security analysis. ### Assumption Summary The security of Poseidon assumes that the algebraic degree of the round function grows exponentially with the number of rounds, making it infeasible to solve the resulting polynomial system. This is a **relatively new assumption** compared to SHA-256 or Keccak — Poseidon has been analyzed since 2019, while SHA-256 has been analyzed since 2001. The conservative parameter choices are designed to account for this shorter analysis history. ## Keccak-256 **Keccak-256** (the algorithm underlying SHA-3) is used in Specter's **quantum commitment layer**. When a commitment is created, a Keccak-256 hash of the commitment data is also stored. This provides a post-quantum binding guarantee. ### Security Properties | Property | Classical Security | Post-Quantum Security | |---|---|---| | Collision resistance | 128 bits | 128 bits (Grover's provides no advantage for collision search) | | Preimage resistance | 256 bits | 128 bits (Grover's algorithm halves the security) | | Second preimage resistance | 256 bits | 128 bits | ### Why Keccak-256 for Quantum Defense? Keccak-256 is a **symmetric primitive** — its security is based on the combinatorial structure of the sponge construction, not on any algebraic hardness assumption. Quantum computers provide only a quadratic speedup (Grover's algorithm) against symmetric primitives, reducing 256-bit security to 128-bit — still computationally infeasible. By contrast, BN254 and Groth16 rely on the elliptic curve discrete log problem, which Shor's algorithm solves in polynomial time. The Keccak-256 quantum commitment layer ensures that even if BN254 is broken, the commitment binding property remains intact. ## AES-256-GCM **AES-256-GCM** is used for **passphrase encryption** of Phantom Keys and Phantom Identities at rest. When a user encrypts a Phantom Identity PNG or a Phantom Key with a passphrase, the underlying secrets are encrypted with AES-256-GCM. ### Security Properties | Property | Value | |---|---| | Key size | 256 bits | | Classical security | 256 bits | | Post-quantum security | 128 bits (Grover's algorithm) | | Mode | Galois/Counter Mode (authenticated encryption) | | Nonce | 96-bit random nonce per encryption | | Authentication | 128-bit authentication tag (integrity + authenticity) | AES-256-GCM provides **authenticated encryption** — it guarantees both confidentiality (an attacker cannot read the plaintext) and integrity (an attacker cannot modify the ciphertext without detection). This is critical for Phantom Identity PNGs, where a modified ciphertext could cause the user to recover a wrong private key. ## PBKDF2-SHA256 **PBKDF2-SHA256** derives the AES-256 encryption key from the user's passphrase. It is a **key derivation function** designed to be deliberately slow, making brute-force passphrase guessing expensive. ### Parameters | Parameter | Value | Rationale | |---|---|---| | Hash function | SHA-256 | Widely analyzed, conservative choice | | Iteration count | 100,000 | ~100ms on modern hardware per guess | | Salt | 128-bit random | Prevents rainbow table attacks | | Output | 256-bit key | Matches AES-256 key size | ### Brute-Force Resistance At 100,000 iterations, a single passphrase guess costs approximately 100ms on a modern CPU. An attacker trying 10 billion passphrases per second on specialized hardware faces: | Passphrase Entropy | Guesses Required | Time at 10^10 guesses/sec | |---|---|---| | 40 bits | ~10^12 | ~100 seconds | | 60 bits | ~10^18 | ~3 years | | 80 bits | ~10^24 | ~3 million years | | 128 bits | ~10^38 | Heat death of the universe | **Recommendation**: Passphrases should have at least 60 bits of entropy (approximately 5 random words from a large dictionary) for meaningful security. ### Migration Consideration PBKDF2 is a conservative choice but is less resistant to GPU/ASIC acceleration than memory-hard KDFs like Argon2id. A future protocol version may migrate to Argon2id for stronger brute-force resistance. ## Assumption Dependency Map The following diagram shows which protocol components depend on which cryptographic assumptions: If **ECDLP on BN254** is broken (e.g., by quantum computers), Groth16 proofs and stealth addresses fail, but commitments (Poseidon), the quantum layer (Keccak), and encryption (AES) remain secure. This is the foundation of Specter's defense-in-depth strategy. --- # Audit Status Specter has undergone multiple independent security audits covering smart contracts, ZK circuits, scaling infrastructure, and operational security. This page summarizes audit findings, remediation status, and outstanding mainnet-blocking issues. Audits are a point-in-time assessment. They identify issues in the codebase as it existed at audit time. Subsequent code changes may introduce new issues or resolve identified ones. This page reflects the current understanding of all findings. ## Audit Summary | Audit | Total Findings | Critical | High/Major | Medium | Low/Minor | Informational | |---|---|---|---|---|---|---| | **Smart Contract** | 65 | 3 | 14 | 18 | 16 | 14 | | **ZK Circuit** | 11 | 1 | 2 | 2 | 2 | 4 | | **Scaling Contracts** | 24 | 2 | 2 | 9 | 8 | 3 | | **Infrastructure** | 25 | 2 | 4 | 10 | 7 | 2 | | **Total** | **125** | **8** | **22** | **39** | **33** | **23** | ## Smart Contract Audit ### Scope The smart contract audit covered the core Ghost Protocol contracts deployed on Specter's EVM: | Contract | Description | |---|---| | **CommitRevealVault** | Core commit/reveal logic — accepts commitments, verifies proofs, processes reveals | | **NullifierRegistry** | Tracks spent nullifiers to prevent double-spending | | **CommitmentTree** | Merkle tree implementation for commitment storage and root management | | **NativeAssetHandler** | Bridges between native Cosmos SDK tokens and EVM-side operations via the ghostmint precompile | | **AssetGuard** | Controls which assets can enter and exit the privacy system | | **Policy contracts** | Programmable constraints on commit/reveal operations (time locks, amount limits, compliance rules) | ### Findings Breakdown | Severity | Count | Description | |---|---|---| | Critical (3) | 3 | Issues that could result in loss of funds, broken privacy guarantees, or protocol bypass | | Major (14) | 14 | Issues that could result in degraded security, incorrect state, or exploitable edge cases | | Medium (18) | 18 | Issues with limited exploitability but requiring code changes for correctness | | Minor (16) | 16 | Code quality, gas optimization, and defensive programming improvements | | Informational (14) | 14 | Best practices, documentation gaps, and stylistic suggestions | ### Key Finding Categories - **Access control**: Several functions lacked appropriate caller restrictions, potentially allowing unauthorized state modifications. - **Reentrancy**: Token callback patterns required reentrancy guards in specific reveal flows. - **Integer handling**: Edge cases in amount encoding/decoding for the 7-input token commitment variant. - **Root staleness**: Insufficient validation of Merkle root freshness in certain reveal paths. - **Policy bypass**: Specific sequences of operations that could circumvent policy constraints. ## ZK Circuit Audit ### Scope The ZK circuit audit covered the Circom circuits that generate and verify zero-knowledge proofs: | Circuit | Description | |---|---| | **redemption.circom** | Core reveal circuit — proves knowledge of commitment preimage and Merkle membership | | **accessProof.circom** | Persistent access circuit — proves knowledge without consuming the nullifier | | **Commitment libraries** | Shared Poseidon hashing and constraint generation code used by both circuits | ### Findings Breakdown | Severity | Count | Description | |---|---|---| | Critical (1) | 1 | Constraint under-specification that could allow proof forgery | | High (2) | 2 | Missing range checks and insufficient input validation | | Medium (2) | 2 | Redundant constraints and optimization opportunities | | Low (2) | 2 | Documentation and naming clarity | | Informational (4) | 4 | Circuit design patterns and best practices | ### Key Finding Categories - **Under-constrained signals**: A circuit signal that was not fully constrained, potentially allowing an adversary to manipulate a proof input without detection. - **Range checks**: Missing bit-length constraints on inputs that should be bounded to specific ranges (e.g., token amounts must fit in 64 bits). - **Nullifier derivation**: Verification that the nullifier derivation path in the circuit matches the on-chain expectation exactly. ## Scaling Contracts Audit ### Scope The scaling contracts audit covered the batch processing and session infrastructure designed for high-throughput operations: | Contract | Description | |---|---| | **BatchCommitRevealVault** | Processes multiple commits and/or reveals in a single transaction | | **SessionVault** | Manages session-based access with time-bounded authentication | | **RunnerRegistry** | Registry for authorized proof generation runners in the scaling infrastructure | | **ShardedTreeRegistry** | Manages multiple Merkle tree shards for horizontal scaling | ### Findings Breakdown | Severity | Count | Description | |---|---|---| | Critical (2) | 2 | Issues in batch processing that could corrupt Merkle tree state or allow cross-session attacks | | High (2) | 2 | Race conditions in concurrent session management | | Medium (9) | 9 | Gas optimization, event emission completeness, and edge case handling | | Low (8) | 8 | Code quality and documentation | | Informational (3) | 3 | Architecture suggestions | ### Key Finding Categories - **Batch atomicity**: Ensuring that a partially-failed batch does not leave the Merkle tree in an inconsistent state. - **Session isolation**: Preventing one session's proof from being replayed in another session context. - **Shard consistency**: Ensuring cross-shard operations maintain global nullifier uniqueness. - **Runner authorization**: Access control for the runner registry to prevent unauthorized proof submissions. ## Infrastructure Audit ### Scope The infrastructure audit covered operational security of the deployed system: | Component | Description | |---|---| | **Validator node** | CometBFT validator configuration, key management, sentry node architecture | | **Relayer services** | Root Updater, Commitment Relayer, Proof Relayer — authentication, rate limiting, input validation | | **Firewall** | Network configuration, port exposure, ingress/egress rules | | **TLS** | Certificate management, cipher suite selection, HSTS configuration | ### Findings Breakdown | Severity | Count | Description | |---|---|---| | Critical (2) | 2 | Exposed management interfaces and insufficient key protection | | High (4) | 4 | Missing rate limiting on critical endpoints, weak TLS configurations | | Medium (10) | 10 | Logging gaps, monitoring coverage, backup procedures | | Low (7) | 7 | Documentation, operational procedures, and configuration hardening | | Informational (2) | 2 | Best practices and recommendations | ### Key Finding Categories - **Key management**: Validator private keys and relayer signing keys required improved storage and rotation procedures. - **Rate limiting**: Several relayer endpoints lacked per-IP and per-wallet rate limiting, enabling potential denial-of-service. - **Monitoring**: Insufficient alerting on anomalous patterns (e.g., rapid nullifier spending, unusual proof generation rates). - **TLS configuration**: Cipher suite and protocol version hardening recommendations. ## Mainnet-Blocking Issues The following issues have been identified as **blockers for mainnet launch**. All are tracked and remediation is planned: | Issue | Source Audit | Severity | Status | Description | |---|---|---|---|---| | **Phase 2 Trusted Setup** | ZK Circuit | Critical | Planned | The current circuits use a development-phase trusted setup. A public multi-party computation (MPC) ceremony is required before mainnet to ensure no single party holds the toxic waste. | | **SessionVault Payment Timing** | Scaling Contracts | Critical | Identified | A timing vulnerability in session payment processing that could allow a session to be used without completing payment. | | **Policy Validation Bypass** | Smart Contract | Critical | Identified | A specific sequence of operations that can circumvent policy enforcement under certain conditions. | ### Remediation Timeline ## Audit Philosophy Specter treats audits as **one input** to a broader security strategy, not as a certification of correctness. The complete security approach includes: 1. **External audits** — independent expert review (completed, documented above). 2. **Formal verification** — mathematical proofs of circuit constraint correctness (research phase). 3. **Fuzz testing** — automated property-based testing of contract edge cases (ongoing). 4. **Bug bounty** — public reward program for responsibly disclosed vulnerabilities (planned for mainnet). 5. **Incremental deployment** — testnet operation with progressive feature activation before mainnet. No software is bug-free. The goal is to systematically identify, prioritize, and remediate issues before they can be exploited, and to design the system so that individual component failures do not cascade into catastrophic outcomes. --- # Development Phases Specter's development follows a phased approach — each phase builds on the previous one's foundation, and no phase is activated until its prerequisites are met. This page describes each phase, its deliverables, and the dependencies between them. ## Phase Overview ## Phase 1: Testnet (Current) **Status**: Active on `specter-testnet-1` Phase 1 delivers the complete core protocol on a public testnet. All fundamental primitives are operational and available for developers, integrators, and auditors to test. ### Deliverables | Component | Description | Status | |---|---|---| | **Ghost Protocol** | Core commit/reveal (vanish/summon) with Groth16 ZK proofs and Poseidon commitments | Deployed | | **Phantom Keys** | Bearer instrument key format — numeric encoding of commitment secrets | Deployed | | **Phantom Identity** | Persistent anonymous identity via PNG bearer objects with split-key encryption | Deployed | | **Policy System** | Programmable constraints on commit/reveal operations (time locks, amount limits, compliance hooks) | Deployed | | **Batch Operations** | BatchCommitRevealVault for processing multiple commits/reveals in a single transaction | Deployed | | **Open Ghost Protocol** | Third-party commitment capability — anyone can vanish data on behalf of another party | Deployed | | **Commitment Tree** | On-chain Merkle tree with Poseidon node hashing and root history | Deployed | | **Nullifier Registry** | Double-spend prevention via unique nullifier tracking | Deployed | | **Relayer Network** | Root Updater, Commitment Relayer, Proof Relayer for light client support | Operational | | **Faucet** | Testnet GHOST token distribution | Operational | | **Stealth Addresses** | ERC-5564 compatible one-time recipient addresses via GhostStealthAnnouncer | Deployed | ### What Phase 1 Validates - End-to-end commit/reveal flow works with real ZK proofs on a live chain. - Phantom Keys and Phantom Identities encode/decode correctly across clients. - Policy enforcement correctly constrains operations. - Batch operations maintain Merkle tree consistency. - Relayer network handles real client traffic under load. ## Phase 2: Pre-Mainnet **Status**: Planned Phase 2 is the hardening phase. No new features are added. The focus is entirely on security, correctness, and operational readiness. ### Deliverables | Component | Description | Dependency | |---|---|---| | **Trusted Setup Ceremony** | Multi-party computation (MPC) ceremony to generate production proving and verification keys. At least one honest participant ensures toxic waste is destroyed. | Finalized circuit design from Phase 1 | | **Audit Remediation** | Fix all critical and high-severity findings from the smart contract, ZK circuit, scaling, and infrastructure audits. | Completed audit reports | | **Re-Audit** | Independent verification that remediated issues are correctly resolved and no regressions introduced. | Completed remediation | | **Test Coverage Expansion** | Comprehensive unit, integration, and property-based tests for all contracts and circuits. Target: >95% branch coverage for critical paths. | Stable codebase | | **Operational Runbooks** | Documented procedures for validator onboarding, key rotation, incident response, and chain upgrades. | Infrastructure audit remediation | ### Trusted Setup Ceremony Details The trusted setup is the **single most security-critical event** in the protocol's lifecycle. It generates the cryptographic parameters (proving key, verification key) for the Groth16 circuits. The ceremony is structured as: 1. **Coordinator** publishes the initial parameters. 2. **Participants** (community members, researchers, organizations) each contribute randomness by applying a secret transformation to the parameters. 3. Each participant's contribution is verified and the result is passed to the next participant. 4. **Each participant destroys their secret** after contributing. 5. The final parameters are published for public verification. The security guarantee: as long as **at least one participant** honestly destroys their secret randomness, the toxic waste is unrecoverable and the parameters are safe. The ceremony will be open to public participation to maximize the number of independent contributors. ### Why Phase 2 Blocks Mainnet Without a production trusted setup, the current circuits use development-phase parameters where the toxic waste is known. This means proofs could theoretically be forged. This is acceptable for testnet (where tokens have no value) but is a **hard blocker** for mainnet. ## Phase 3: Mainnet Launch **Status**: Planned (post-Phase 2 completion) Phase 3 is the public launch of the Specter mainnet with real economic value at stake. ### Deliverables | Component | Description | Dependency | |---|---|---| | **Genesis Configuration** | Initial validator set, token distribution, governance parameters | Phase 2 complete | | **Validator Onboarding** | Documented process for validators to join the network, stake GHOST, and participate in consensus | Operational runbooks | | **Governance Activation** | On-chain governance for protocol upgrades, parameter changes, and community proposals via Cosmos SDK gov module | Genesis configuration | | **Block Explorer** | Public block explorer with privacy-aware transaction display (showing commitments/nullifiers without exposing linkability) | Deployed infrastructure | | **SDK Release** | Stable client SDK for developers to integrate Specter's data privacy features into their applications | Tested API surface | | **Bug Bounty Program** | Public reward program for responsibly disclosed vulnerabilities | Deployed mainnet | ### Mainnet Launch Criteria All of the following must be true before mainnet launch: - [ ] Phase 2 trusted setup ceremony completed and verified - [ ] All critical and high audit findings remediated and re-audited - [ ] Testnet has operated for a sustained period without critical incidents - [ ] At least N independent validators committed and operational - [ ] Client SDK tested against production configuration - [ ] Emergency upgrade procedure tested on testnet ## Phase 4: Cross-Chain **Status**: Research and development Phase 4 extends Specter's data privacy guarantees across chain boundaries. ### Deliverables | Component | Description | Dependency | |---|---|---| | **IBC Private Transfers** | Vanish on source Cosmos chain, IBC packet carries commitment + proof, summon on Specter | IBC channel establishment, cross-chain proof format | | **Hyperlane Bridge Deployment** | Production Hyperlane deployment with MultisigISM for Ethereum and L2 connectivity | MultisigISM validator set, bridge contract audits | | **Cross-Chain Vanish/Summon** | Data vanished on one chain and summoned on another — full transaction graph breakage across chain boundaries | IBC private transfers + Hyperlane integration | | **Bridge Token Expansion** | Support for additional bridged assets beyond gUSDC, gWETH, gLABS | HyperlaneTokenRegistry updates | ### Cross-Chain Privacy Architecture The key challenge is **cross-chain Merkle root verification** — the destination chain needs to verify that a commitment exists in the source chain's Merkle tree. This can be solved via: - **IBC light client proofs** — verify the source chain's state root, then verify Merkle membership against it. - **Hyperlane ISM verification** — the ISM attests that the commitment was valid on the source chain. ## Phase 5: Advanced Scaling **Status**: Research Phase 5 addresses throughput and cost limitations for high-volume use cases. ### Deliverables | Component | Description | Dependency | |---|---|---| | **Session Vaults** | Time-bounded sessions with pre-authenticated access — reduces per-operation proof overhead | SessionVault audit remediation | | **Sharded Merkle Trees** | Multiple parallel Merkle trees with a global nullifier registry — horizontal scaling of commitment throughput | ShardedTreeRegistry deployment | | **Runner Network** | Decentralized network of proof generation nodes — replaces centralized relayer with permissionless infrastructure | RunnerRegistry, incentive model | | **Proof Aggregation** | Verify one proof that attests to the validity of N proofs — amortizes verification cost across batch operations | Recursive proof research | ### Scaling Targets | Metric | Phase 1 (Testnet) | Phase 5 (Target) | |---|---|---| | Commits per block | ~10-50 | ~1,000+ | | Proof generation time (server) | 2-5 seconds | Under 1 second (parallelized) | | Merkle tree capacity | 2^20 leaves (~1M) | 2^20 per shard, unlimited shards | | Verification cost per proof | ~200K gas | ~50K gas (aggregated) | ### Sharded Tree Architecture Each shard is an independent Merkle tree with its own root and leaf count. The `ShardedTreeRegistry` manages shard creation and assignment. The `NullifierRegistry` remains global — a nullifier spent in any shard cannot be reused in any other shard, preserving the double-spend guarantee across the entire system. ## Phase Dependencies Phases 4 and 5 can proceed in parallel after mainnet launch. Cross-chain operations may benefit from proof aggregation (Phase 5), but they are not strictly dependent — the initial cross-chain implementation can use individual proofs. --- # Research Directions This page describes active and planned research areas that may influence Specter's protocol evolution. These are not committed roadmap items — they are directions being investigated, with varying levels of maturity. Some may be adopted in future protocol upgrades; others may prove impractical or unnecessary. ## Recursive Proofs ### Problem Currently, each Summon (reveal) operation requires independent on-chain verification of a Groth16 proof. When batch operations process N reveals, the verifier must check N proofs, each costing approximately 200K gas. Verification cost scales linearly with batch size. ### Research Direction **Recursive proof composition** allows a prover to generate a single proof that attests: "I have verified N inner proofs, and all of them are valid." The on-chain verifier checks only the outer proof — a constant cost regardless of N. ### Challenges - **Cycle of curves**: Recursive verification requires that the proof system's verification algorithm can be efficiently expressed in its own circuit. For BN254-based Groth16, this requires either a cycle of pairing-friendly curves or a different inner proof system. - **Proof generation time**: Recursive proofs are computationally expensive to generate. The aggregation step may take minutes for large batches. - **Complexity**: Recursive proof circuits are significantly more complex than the base circuits, increasing the audit surface. ### Potential Impact If feasible, recursive proofs reduce batch verification cost from O(N) to O(1), enabling orders-of-magnitude improvement in throughput for high-volume use cases like bulk credential issuance or mass token distribution. ## PLONK/Halo2 Migration ### Problem Groth16 requires a **circuit-specific trusted setup ceremony**. Every time a circuit is modified — even a minor change to the constraint system — a new trusted setup is required. This imposes significant operational overhead and ceremony logistics for each protocol upgrade. ### Research Direction **PLONK** and **Halo2** are proof systems that use a **universal and updatable structured reference string (SRS)**: | Property | Groth16 | PLONK | Halo2 | |---|---|---|---| | Setup type | Circuit-specific | Universal (one-time) | No trusted setup (transparent) | | Proof size | ~128 bytes (3 group elements) | ~400-800 bytes | ~400-800 bytes | | Verification time | ~1ms (1 pairing check) | ~3-5ms | ~5-10ms | | Prover time | Fast | Moderate | Moderate-slow | | Circuit changes | Require new setup | Reuse existing SRS | No setup at all | - **PLONK** with a universal SRS means one trusted setup ceremony supports all circuits, now and in the future. Circuit upgrades do not require new ceremonies. - **Halo2** eliminates the trusted setup entirely using an **inner product argument** (IPA). No toxic waste. No ceremony. Fully transparent. ### Challenges - **Proof size increase**: PLONK/Halo2 proofs are 3-6x larger than Groth16 proofs, increasing on-chain storage and verification gas costs. - **Verification cost**: On-chain verification is more expensive without pairing-based optimizations. - **EVM compatibility**: Efficient on-chain verification of PLONK/Halo2 proofs may require custom precompiles or significant gas investment. - **Migration complexity**: Changing the proof system requires replacing all circuits, verifier contracts, and client-side proving logic. ### Potential Impact Eliminating the trusted setup requirement removes the single most operationally complex and trust-sensitive component of the protocol. It also enables rapid circuit iteration — bug fixes and optimizations can be deployed without ceremony overhead. ## Lattice-Based ZK Proofs ### Problem Specter's current defense-in-depth against quantum computers relies on the Keccak-256 quantum commitment layer — a symmetric primitive that provides 128-bit post-quantum security for commitment binding. However, the **ZK proof system itself** (Groth16 on BN254) is not quantum-resistant. A quantum adversary could forge proofs. ### Research Direction **Lattice-based ZK proof systems** derive their security from the hardness of lattice problems (Learning With Errors, Short Integer Solution), which are believed to be resistant to both classical and quantum attacks. ### Challenges - **Proof size**: Current lattice-based ZK proofs are orders of magnitude larger than Groth16 proofs (kilobytes to megabytes vs. 128 bytes). - **Prover efficiency**: Lattice-based proving is significantly slower than Groth16. - **Maturity**: Lattice-based ZK systems are in early research stages. No production-grade implementation exists with the efficiency required for on-chain verification. - **Hash function compatibility**: Lattice-friendly hash functions may be required to replace Poseidon, adding migration complexity. ### Potential Impact Full quantum resistance across the entire proof stack — not just the commitment layer. This would make Specter's privacy guarantees robust against any future quantum advancement. ## Private Smart Contract Execution ### Problem Currently, Specter's privacy guarantees cover **data commitments and reveals** — you can commit arbitrary data (credentials, tokens, keys, images) and prove properties about that data without revealing it. However, arbitrary **computation** on private data is not supported. If you want to execute a smart contract function on private inputs and get a private output, the current protocol cannot do this. ### Research Direction **Private smart contract execution** extends the ZK proof paradigm from proving properties of committed data to proving correct execution of arbitrary programs on private inputs. Approaches under investigation: | Approach | Description | Maturity | |---|---|---| | **zkVM** | A virtual machine where every instruction is proven in ZK. Compile contracts to zkVM bytecode; execution produces a proof of correct computation. | Active research (RISC Zero, SP1, Valida) | | **Circuit compilation** | Compile specific smart contract functions into ZK circuits. Each function has a dedicated circuit that proves correct execution. | Moderate maturity (Circom, Noir) | | **MPC-based** | Multiple parties compute a function on their combined private inputs without revealing those inputs to each other. | Mature for specific applications | ### Challenges - **General-purpose zkVM performance**: Proving arbitrary computation in ZK is orders of magnitude slower than native execution. A simple ERC-20 transfer might take minutes to prove. - **Contract language changes**: Developers may need to use ZK-specific languages (Noir, Leo) rather than Solidity. - **Composability**: Private contract state must compose with public contract state, requiring careful interface design. - **Verification cost**: On-chain verification of general-purpose computation proofs is significantly more expensive than verifying specialized circuits. ### Potential Impact Private smart contract execution would transform Specter from a data privacy protocol into a **private computation platform** — enabling use cases like private DEX trading, private governance voting, private credential verification with complex logic, and private machine learning inference. ## Enhanced Anonymity Sets ### Problem The anonymity set for a given reveal is the set of all commitments in the Merkle tree that the reveal could plausibly correspond to. Larger anonymity sets provide stronger privacy. Currently, the anonymity set is limited to commitments on the Specter chain within a single Merkle tree (or shard). ### Research Direction Two complementary approaches to expanding anonymity sets: **1. Cross-Chain Anonymity Set Sharing** If commitments exist on multiple chains (Specter, Ethereum L2s, other Cosmos chains), a reveal on one chain could prove membership in a **cross-chain Merkle tree** that includes commitments from all chains. This dramatically increases the anonymity set. **2. Time-Delayed Reveals** Enforcing a minimum delay between commit and reveal (already partially supported via time-lock policies) ensures that additional commitments accumulate before any reveal occurs, guaranteeing a minimum anonymity set size. ### Challenges - **Cross-chain root synchronization**: Maintaining a consistent view of multiple chains' Merkle roots requires reliable cross-chain communication with low latency. - **Proof complexity**: Cross-chain Merkle membership proofs are more complex than single-chain proofs, increasing circuit size and proving time. - **Time-delay trade-off**: Longer delays improve anonymity but degrade user experience. Finding the right balance requires empirical analysis. ### Potential Impact Cross-chain anonymity sets could provide anonymity set sizes orders of magnitude larger than any single chain, approaching the aggregate privacy of the entire cross-chain ecosystem. ## Formal Verification ### Problem Specter's correctness guarantees currently rely on testing, auditing, and code review. These approaches find many bugs but cannot prove the absence of all bugs. For a protocol handling private data and value, stronger guarantees are desirable. ### Research Direction **Formal verification** uses mathematical proof to verify that the implementation matches its specification: | Target | What Is Verified | Tool Candidates | |---|---|---| | **Circuit constraints** | Every valid proof corresponds to a valid witness; every invalid witness produces no valid proof | Ecne, Picus, manual Lean/Coq proofs | | **Solvency invariants** | The total value committed minus the total value revealed equals the expected outstanding balance at all times | Certora, Halmos | | **Nullifier uniqueness** | No two distinct reveals can produce the same nullifier; no single commitment can be revealed twice | Circuit-level formal proof | | **Merkle tree consistency** | The on-chain tree state is always a valid Merkle tree with correct root computation | Contract-level formal verification | | **Policy composability** | Composed policies enforce the intersection of their individual constraints, not a weaker condition | Property-based specification | ### Challenges - **Specification effort**: Writing a formal specification is as difficult as writing the implementation. Errors in the specification are as dangerous as errors in the code. - **Tooling maturity**: Formal verification tools for ZK circuits are nascent. Most mature tools target smart contracts (Solidity/EVM), not Circom/R1CS. - **Ongoing maintenance**: Every code change requires updating the formal proofs, adding significant development overhead. ### Potential Impact Formal verification provides the highest possible assurance of correctness. For a privacy protocol, where bugs can silently leak data or enable theft, this level of assurance is exceptionally valuable. Even partial formal verification (e.g., proving the circuit constraints are sound) would significantly strengthen the protocol's security story. ## Research Prioritization | Direction | Impact | Feasibility (Near-Term) | Priority | |---|---|---|---| | Recursive proofs | High (scaling) | Moderate | High | | PLONK/Halo2 migration | High (operational) | Moderate | High | | Formal verification | High (security) | Moderate | High | | Enhanced anonymity sets | High (privacy) | Moderate | Medium | | Lattice-based ZK proofs | Critical (quantum) | Low | Medium (long-term) | | Private smart contract execution | Transformative | Low | Low (long-term) | Priorities are based on the combination of impact and near-term feasibility. Recursive proofs and PLONK/Halo2 migration are highest priority because they address immediate operational and scaling challenges. Lattice-based proofs and private execution are long-term research investments. --- # Glossary Alphabetical definitions of terms used throughout the Specter documentation. Terms in **bold** within definitions link to their own glossary entry where applicable. --- ### Access Proof A zero-knowledge proof that demonstrates knowledge of a commitment's preimage without consuming its **Nullifier**. Used by **Phantom Identity** for persistent, repeatable authentication. Unlike a redemption proof (used in **Summon**), an access proof records an **Access Tag** instead of a nullifier, allowing the commitment to be accessed unlimited times. ### Access Tag A unique identifier derived from the **NullifierSecret** and a **Session Nonce** during an **Access Proof**. Access tags are recorded on-chain to prevent replay within a session but do not consume the commitment. Each authentication session produces a different access tag, making sessions unlinkable. ### aghost The smallest denomination of the GHOST token. 1 GHOST = 10^18 aghost. Analogous to wei in Ethereum or uatom in Cosmos. All on-chain token amounts are denominated in aghost. ### Bearer Instrument A credential where possession equals authorization. Whoever holds the instrument can use it — no identity verification, no account lookup, no password. **Phantom Keys** and **Phantom Identities** are bearer instruments: the numbers (or the PNG file) *are* the access. ### Blinding Factor A random **BN254** field element included in every **Commitment** to provide semantic security. The blinding factor ensures that identical data committed twice produces different commitment hashes, preventing an observer from detecting duplicate commitments. ### BN254 The elliptic curve (also called alt-bn128 or bn256) used for all **Groth16** proof generation and verification in Specter. It operates over a ~254-bit prime field and supports bilinear pairings, which are required for efficient on-chain proof verification via Ethereum's `ecPairing` precompile. Provides approximately 100-110 bits of classical security. ### CometBFT The Byzantine Fault Tolerant consensus engine (formerly Tendermint) that provides Specter's consensus layer. Delivers immediate finality, ~2.5-second block times, and BFT safety as long as more than 2/3 of validators are honest. ### Commitment A Poseidon hash that irreversibly binds a user to specific data without revealing that data. The 4-input variant is `Poseidon4(secret, nullifierSecret, dataHash, blinding)` for general data. The 7-input variant adds `tokenId`, `amount`, `policyId`, and `policyParamsHash` for token operations. Once inserted into the **Merkle Tree**, a commitment can be proven via a ZK proof without revealing its inputs. ### CommitRevealVault The core Solidity smart contract that implements the Ghost Protocol's commit/reveal logic. It accepts **Commitments** during **Vanish** operations, verifies **Groth16** proofs during **Summon** operations, and coordinates with the **NullifierRegistry** and **CommitmentTree**. ### Data Hash A Poseidon hash of the arbitrary data being committed. The data hash is one input to the **Commitment** function. The protocol does not interpret the data hash — it treats it as an opaque field element. The semantics of what the hash represents (credential, image, API key, token metadata) are determined by the application layer. ### Ghost Protocol Specter's core primitive: a **commit/reveal** system for any data. Users hash data into a **Poseidon** **Commitment**, insert it into an on-chain **Merkle Tree**, and later prove knowledge of the committed data using a **Groth16** zero-knowledge proof. The commit and reveal operations are cryptographically unlinkable. The GHOST token is one use case; the protocol supports arbitrary data types. ### Ghostmint A custom **precompile** in Specter's EVM that bridges between the Cosmos SDK bank module and EVM-side operations. It enables **Vanish** operations to burn native GHOST tokens and **Summon** operations to mint them, without requiring a token pool or custodial contract. ### Groth16 The zero-knowledge proof system used in Specter. Produces constant-size proofs (3 group elements, ~128 bytes) with fast verification (a single pairing check). Requires a circuit-specific **Trusted Setup**. Security relies on the **Knowledge of Exponent** assumption and the hardness of the discrete log problem on **BN254**. ### Merkle Tree A binary hash tree where each leaf is a **Commitment** and each internal node is the **Poseidon** hash of its two children. The tree provides efficient membership proofs — proving a commitment exists in the tree requires only a logarithmic-length path (20 hashes for a tree of depth 20, supporting ~1M leaves). The root of the tree is published on-chain by the **Root Updater**. ### Nullifier A value derived from the **NullifierSecret** that is recorded on-chain when a **Commitment** is revealed (summoned). The **NullifierRegistry** enforces that each nullifier can only be used once, preventing double-spending. The nullifier is computed so that it cannot be linked to the commitment it consumes — an observer sees the nullifier but cannot determine which commitment was revealed. ### NullifierSecret A **BN254** field element that is part of every **Commitment** preimage. It is used to derive both the **Nullifier** (for one-time reveals) and the **Access Tag** (for persistent access). Derived deterministically from the user's seed via HKDF-SHA256. ### Open Ghost A mode of the Ghost Protocol where a third party can commit data on behalf of another user. The committer does not need to know the **Secret** or **NullifierSecret** — they submit a pre-computed **Commitment** hash. This enables use cases like bulk credential issuance, gift cards, and airdrops where the issuer vanishes data that recipients later summon. ### Phantom Identity A persistent, reusable anonymous identity encoded as a PNG bearer object. Contains a split-encrypted secp256k1 keypair, commitment secrets, and a quantum secret. Unlike a **Phantom Key** (which is consumed on first use), a Phantom Identity survives indefinitely. Each authentication session uses an **Access Proof** to recover the private key without consuming the commitment. ### Phantom Key A **Bearer Instrument** for data. A set of secret numbers encoded as groups of four digits (e.g., `9473 0018 7376 9372 0484 1273`) that represent the preimage of a **Commitment** in the **Merkle Tree**. Whoever knows the numbers can generate a zero-knowledge proof and reveal the committed data. Phantom Keys are consumed on use — the **Nullifier** is spent, preventing reuse. ### Policy A programmable constraint enforced during **Vanish** or **Summon** operations. Policies are implemented as Solidity contracts that the **CommitRevealVault** calls during commit/reveal processing. Examples include time locks (minimum delay between commit and reveal), amount limits (maximum value per operation), and compliance hooks (external verification requirements). ### Poseidon An algebraic hash function designed for efficient computation inside zero-knowledge circuits. Operates natively on finite field elements using field multiplications and additions (S-box: `x^5`), requiring approximately 8x fewer R1CS constraints than SHA-256. All **Commitments**, **Nullifiers**, **Access Tags**, **Merkle Tree** nodes, and token identifiers in Specter are computed using Poseidon. ### PersistentKeyVault An on-chain contract that stores the encrypted second half of a **Phantom Identity's** split private key. When a user authenticates with an **Access Proof**, the vault releases the encrypted key share, which is combined with the local share (stored in the PNG) to reconstruct the full secp256k1 private key. The vault enforces that only valid access proofs can retrieve the key share. ### Reveal See **Summon**. ### Secret A **BN254** field element that is part of every **Commitment** preimage. Together with the **NullifierSecret**, **Data Hash**, and **Blinding Factor**, it forms the private input to the commitment hash. Derived deterministically from the user's seed via HKDF-SHA256. Knowledge of the secret is required to generate a valid ZK proof for the commitment. ### Session Nonce A unique value used in **Access Proof** generation to derive a fresh **Access Tag** for each authentication session. The session nonce ensures that repeated authentications with the same **Phantom Identity** produce different access tags, preventing session linkability. ### Split-Key Encryption The encryption scheme used by **Phantom Identity** to protect the secp256k1 private key. The private key is XOR-split into two shares: one stored locally in the PNG file (encrypted with AES-256-GCM under the user's passphrase) and one stored on-chain in the **PersistentKeyVault** (encrypted with AES-256-GCM under a key derived from the commitment secrets). Both shares are needed to reconstruct the private key. ### Trusted Setup A multi-party computation ceremony that generates the proving key and verification key for a **Groth16** circuit. The ceremony produces "toxic waste" (secret randomness) that must be destroyed. If any single participant honestly destroys their contribution, the toxic waste is unrecoverable and the parameters are safe. Specter requires a production trusted setup ceremony before mainnet launch. ### Vanish The commit phase of the Ghost Protocol. Data or tokens "vanish" from public view by being hashed into a **Poseidon** **Commitment** and inserted into the **Merkle Tree**. For tokens, the vanished amount is **burned** (destroyed, not deposited). The original data is never stored on-chain — only the commitment hash. ### Zero-Knowledge Proof A cryptographic proof that demonstrates knowledge of information without revealing that information. In Specter, ZK proofs demonstrate: (1) the prover knows the preimage of a **Commitment**, (2) that commitment exists in the **Merkle Tree**, and (3) the **Nullifier** is correctly derived. The verifier learns only that these statements are true — nothing about the committed data, the prover's identity, or which specific commitment was proven. --- # References Academic papers, protocol specifications, and technical documentation referenced throughout the Specter whitepaper. Organized by category. ## Zero-Knowledge Proofs | Reference | Description | |---|---| | **Groth, J. (2016).** "On the Size of Pairing-Based Non-interactive Arguments." *EUROCRYPT 2016.* [ePrint 2016/260](https://eprint.iacr.org/2016/260) | The Groth16 proof system used in Specter. Defines the pairing-based SNARK construction with constant-size proofs (3 group elements) and fast verification. | | **Parno, B., Howell, J., Gentry, C., Raykova, M. (2013).** "Pinocchio: Nearly Practical Verifiable Computation." *IEEE S&P 2013.* [ePrint 2013/279](https://eprint.iacr.org/2013/279) | Foundational work on verifiable computation using quadratic arithmetic programs (QAPs). Groth16 builds on the QAP framework introduced here. | | **Ben-Sasson, E., Chiesa, A., Tromer, E., Virza, M. (2014).** "Succinct Non-Interactive Zero Knowledge for a von Neumann Architecture." *USENIX Security 2014.* [ePrint 2013/879](https://eprint.iacr.org/2013/879) | The vnTinyRAM construction — early work on SNARKs for general computation. Informs the theoretical basis for circuit-based proof systems. | | **Gabizon, A., Williamson, Z.J., Ciobotaru, O. (2019).** "PLONK: Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of Knowledge." [ePrint 2019/953](https://eprint.iacr.org/2019/953) | The PLONK proof system with universal and updatable structured reference string. Potential future migration target to eliminate circuit-specific trusted setups. | | **Bowe, S., Grigg, J., Hopwood, D. (2019).** "Recursive Proof Composition without a Trusted Setup." [ePrint 2019/1021](https://eprint.iacr.org/2019/1021) | The Halo construction for recursive proof composition without trusted setup. Informs research into proof aggregation and transparent proof systems. | ## Hash Functions | Reference | Description | |---|---| | **Grassi, L., Khovratovich, D., Rechberger, C., Roy, A., Schofnegger, M. (2021).** "Poseidon: A New Hash Function for Zero-Knowledge Proof Systems." *USENIX Security 2021.* [ePrint 2019/458](https://eprint.iacr.org/2019/458) | The Poseidon hash function used for all commitments, nullifiers, and Merkle tree nodes in Specter. Defines the algebraic hash construction optimized for arithmetic circuits. | | **Bertoni, G., Daemen, J., Peeters, M., Van Assche, G. (2011).** "The Keccak Reference." [keccak.team](https://keccak.team/files/Keccak-reference-3.0.pdf) | The Keccak sponge construction underlying SHA-3 and used in Specter's quantum commitment layer. | ## Elliptic Curves | Reference | Description | |---|---| | **Barreto, P., Naehrig, M. (2005).** "Pairing-Friendly Elliptic Curves of Prime Order." *SAC 2005.* | The BN curve construction. BN254 (alt-bn128) is the specific parameterization used in Specter and supported by Ethereum's ecPairing precompile. | | **Bowe, S. (2017).** "BLS12-381: New zk-SNARK Elliptic Curve Construction." [electriccoin.co](https://electriccoin.co/blog/new-snark-curve/) | The BLS12-381 curve used in Ethereum 2.0 and Zcash Sapling. Potential migration target for higher security margin (~120 bits). | | **Barbulescu, R., Duquesne, S. (2018).** "Updating Key Size Estimations for Pairings." *Journal of Cryptology.* [ePrint 2017/334](https://eprint.iacr.org/2017/334) | Revised security estimates for pairing-friendly curves, including the downward revision of BN254 from ~128-bit to ~100-110-bit security. | ## Blockchain Infrastructure | Reference | Description | |---|---| | **Cosmos SDK Documentation.** [docs.cosmos.io](https://docs.cosmos.io) | The application framework used to build Specter's blockchain. Specter uses Cosmos SDK v0.53.2. | | **CometBFT Documentation.** [docs.cometbft.com](https://docs.cometbft.com) | The Byzantine Fault Tolerant consensus engine (formerly Tendermint) providing Specter's consensus layer. Specter uses CometBFT v0.38.17. | | **Wood, G. (2014).** "Ethereum: A Secure Decentralised Generalised Transaction Ledger." (Yellow Paper). [ethereum.github.io/yellowpaper](https://ethereum.github.io/yellowpaper/paper.pdf) | The Ethereum Virtual Machine specification. Specter's EVM layer (cosmos/evm) implements this specification for Solidity smart contract execution. | | **IBC Protocol Specification.** [github.com/cosmos/ibc](https://github.com/cosmos/ibc) | The Inter-Blockchain Communication protocol specification. Specter uses ibc-go v10 for cross-chain communication with other Cosmos chains. | | **Hyperlane Documentation.** [docs.hyperlane.xyz](https://docs.hyperlane.xyz) | The modular interoperability protocol used for Specter's bridge to Ethereum and L2 chains. | ## Standards | Reference | Description | |---|---| | **ERC-5564: Stealth Addresses.** [eips.ethereum.org/EIPS/eip-5564](https://eips.ethereum.org/EIPS/eip-5564) | The stealth address standard implemented by Specter's GhostStealthAnnouncer. Defines the meta-address format, ECDH-based stealth address derivation, and announcement mechanism. | | **EIP-1559: Fee Market Change.** [eips.ethereum.org/EIPS/eip-1559](https://eips.ethereum.org/EIPS/eip-1559) | The base fee mechanism for EVM transaction fee pricing. Implemented in Specter's EVM layer. | | **ERC-20: Token Standard.** [eips.ethereum.org/EIPS/eip-20](https://eips.ethereum.org/EIPS/eip-20) | The fungible token standard. Ghost-wrapped bridge tokens (gUSDC, gWETH, gLABS) are ERC-20 compatible. | | **EIP-191: Signed Data Standard.** [eips.ethereum.org/EIPS/eip-191](https://eips.ethereum.org/EIPS/eip-191) | The signed data standard used for wallet signature authentication in Specter's relayer network. | | **EIP-196/197: Elliptic Curve Operations.** [eips.ethereum.org/EIPS/eip-196](https://eips.ethereum.org/EIPS/eip-196), [eip-197](https://eips.ethereum.org/EIPS/eip-197) | The precompiled contracts for BN254 elliptic curve addition, scalar multiplication, and pairing checks. Required for on-chain Groth16 proof verification. | ## Tooling | Reference | Description | |---|---| | **Circom.** [docs.circom.io](https://docs.circom.io) | The domain-specific language for defining arithmetic circuits. Specter's ZK circuits (redemption.circom, accessProof.circom) are written in Circom. | | **snarkjs.** [github.com/iden3/snarkjs](https://github.com/iden3/snarkjs) | JavaScript library for Groth16 proof generation and verification. Used by Specter's client SDK and Proof Relayer for proof computation. | | **Foundry.** [book.getfoundry.sh](https://book.getfoundry.sh) | Ethereum development toolkit (Forge, Cast, Anvil). Used for Specter's smart contract development, testing, and deployment. | | **OpenZeppelin Contracts.** [docs.openzeppelin.com/contracts](https://docs.openzeppelin.com/contracts) | Audited, reusable smart contract library. Specter uses OpenZeppelin's ERC-20, access control, and reentrancy guard implementations. | | **PM2.** [pm2.keymetrics.io](https://pm2.keymetrics.io) | Node.js process manager used to manage Specter's relayer services (Root Updater, Commitment Relayer, Proof Relayer, Faucet). | ## Hardware | Reference | Description | |---|---| | **NXP NTAG 424 DNA Datasheet.** [nxp.com](https://www.nxp.com/docs/en/data-sheet/NT4H2421Gx.pdf) | NFC tag IC with AES-128 authentication and SUN (Secure Unique NFC) messaging. Used for physical bearer instruments — NFC cards that encode Phantom Keys and enable tap-to-authenticate interactions with Specter's data privacy protocol. | ## Cryptographic Primitives | Reference | Description | |---|---| | **Krawczyk, H. (2010).** "Cryptographic Extraction and Key Derivation: The HKDF Scheme." [RFC 5869](https://tools.ietf.org/html/rfc5869) | HMAC-based Key Derivation Function used to derive all commitment secrets (secret, nullifierSecret, blinding) from a single 128-bit seed in Phantom Keys. | | **Kaliski, B. (2000).** "PKCS #5: Password-Based Cryptography Specification Version 2.0." [RFC 2898](https://tools.ietf.org/html/rfc2898) | PBKDF2 specification. Specter uses PBKDF2-SHA256 with 100,000 iterations for passphrase-based key derivation in Phantom Identity encryption. | | **Dworkin, M. (2007).** "Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC." *NIST SP 800-38D.* [nist.gov](https://csrc.nist.gov/publications/detail/sp/800-38d/final) | AES-GCM authenticated encryption specification. Used for passphrase encryption of Phantom Keys and Phantom Identities. | | **Shor, P. (1994).** "Algorithms for Quantum Computation: Discrete Logarithms and Factoring." *FOCS 1994.* | Shor's quantum algorithm that solves the discrete log problem in polynomial time. Motivates Specter's post-quantum defense-in-depth via the Keccak-256 quantum commitment layer. | | **Grover, L. (1996).** "A Fast Quantum Mechanical Algorithm for Database Search." *STOC 1996.* | Grover's quantum search algorithm that provides a quadratic speedup for brute-force search. Reduces symmetric cipher security by half (e.g., AES-256 provides 128-bit post-quantum security). |