Skip to main content

OpenGhostVault

The OpenGhostVault contract provides data privacy -- the ability to commit arbitrary data on-chain and later prove knowledge of it without revealing the data itself or linking the commit and reveal operations. Unlike CommitRevealVault, which handles token privacy (committing and revealing fungible assets), OpenGhostVault deals exclusively with data: encrypted messages, secret keys, document hashes, credentials, and any other information that benefits from a commit/reveal privacy pattern.

Overview

PropertyValue
PurposeData privacy (not token privacy)
Commitment hashPoseidon4(secret, nullifierSecret, dataHash, blinding)
Fee modelNative GHOST fee per commit (no token burning)
Merkle treeOpenCommitmentTree (separate from token CommitmentTree)
Nullifier registryOpenNullifierRegistry (separate from token NullifierRegistry)
Reveal contractOpenGhostReveal
Tree depth20

Why a Separate Vault?

OpenGhostVault exists as a distinct system from CommitRevealVault for several reasons:

  1. No token flow. Data commitments do not burn or mint tokens. There is no amount field, no withdrawAmount, no change commitment. The entire token lifecycle is absent.

  2. Different commitment structure. Token commitments use Poseidon7 (7 inputs including amount, tokenId, policyId). Data commitments use Poseidon4 (4 inputs) because they only need to encode the data hash, not financial parameters.

  3. Separate state trees. Mixing data commitments and token commitments in the same Merkle tree would create a larger anonymity set, but it would also mean data operations compete with financial operations for tree space. Separate trees ensure neither system can bottleneck the other.

  4. Different security model. Token reveals require nullifiers to prevent double-spending. Data reveals use nullifiers to prevent replay, but the semantics differ -- "spending" data is not the same as spending a token.

Commitment Structure

An OpenGhost commitment is a Poseidon4 hash of four field elements:

commitment = Poseidon4(secret, nullifierSecret, dataHash, blinding)
InputSizeDescription
secret31 bytesRandom secret known only to the committer. Proves ownership of the commitment.
nullifierSecret31 bytesSeparate random secret used to derive the nullifier. Separating this from secret enables delegation patterns.
dataHash32 bytes (field element)Hash of the data being committed. Typically keccak256(data) % BN254_FIELD. This is the payload.
blinding31 bytesRandom blinding factor ensuring commitment uniqueness even if the same data is committed twice.

Commitment Computation (Client-Side)

import { poseidon4 } from "poseidon-lite";
import { ethers } from "ethers";

const BN254_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;

function computeOpenGhostCommitment(
secret: bigint,
nullifierSecret: bigint,
data: Uint8Array,
blinding: bigint
): bigint {
// Hash the data payload into a field element
const dataHash = BigInt(ethers.keccak256(data)) % BN254_FIELD;

// Compute the Poseidon4 commitment
return poseidon4([secret, nullifierSecret, dataHash, blinding]);
}

Why Poseidon4 (Not Poseidon7)?

Token commitments require 7 inputs because they encode financial metadata: secret, nullifierSecret, tokenId, amount, blinding, policyId, policyParamsHash. Data commitments strip away all financial fields, leaving only the 4 inputs needed for a generic data commitment. Using a smaller Poseidon variant reduces circuit complexity, proof generation time, and the number of constraints.

Contract Interface

interface IOpenGhostVault {
/// @notice Commit data by inserting a Poseidon4 commitment into the OpenCommitmentTree
/// @param commitment The Poseidon4(secret, nullifierSecret, dataHash, blinding) hash
/// @dev Requires msg.value >= commitFee (paid in native GHOST)
function commit(uint256 commitment) external payable;

/// @notice Get the current commit fee
/// @return The fee in aghost (native GHOST, 18 decimals)
function commitFee() external view returns (uint256);

/// @notice Update the commit fee (governance only)
/// @param newFee The new fee in aghost
function setCommitFee(uint256 newFee) external;

/// @notice Emitted when a new data commitment is inserted
event DataCommitted(
uint256 indexed commitment,
uint256 leafIndex,
uint256 timestamp
);
}

Commit Flow

The commit operation is straightforward compared to token commits -- there is no token burning, no asset guard check, and no policy evaluation. The flow is:

Fee Model

OpenGhostVault charges a flat fee in native GHOST for each commit operation. This serves two purposes:

  1. Spam prevention. Without a fee, an attacker could fill the tree with garbage commitments, consuming tree capacity (2^20 = 1,048,576 leaves) and degrading the anonymity set with known-empty commitments.

  2. Operational sustainability. The fees fund protocol operations and can be directed to validators or a protocol treasury via governance.

The fee is denominated in aghost (18 decimals) and is configurable via governance. There is no token burning -- the fee is a transfer, not a destruction event. This is fundamentally different from CommitRevealVault, where the committed token amount is burned on commit and minted on reveal.

function commit(uint256 commitment) external payable {
require(msg.value >= commitFee, "Insufficient fee");

// Insert into the separate OpenCommitmentTree
uint256 leafIndex = openCommitmentTree.insert(commitment);

// Refund excess fee
if (msg.value > commitFee) {
payable(msg.sender).transfer(msg.value - commitFee);
}

emit DataCommitted(commitment, leafIndex, block.timestamp);
}

Separate State Infrastructure

OpenGhostVault uses its own Merkle tree and nullifier registry, entirely separate from the token privacy system:

ComponentToken PrivacyData Privacy
VaultCommitRevealVaultOpenGhostVault
Merkle treeCommitmentTreeOpenCommitmentTree
Nullifier registryNullifierRegistryOpenNullifierRegistry
Reveal contract(within CommitRevealVault)OpenGhostReveal
Commitment hashPoseidon7Poseidon4
Tree depth2020

The OpenCommitmentTree is structurally identical to CommitmentTree -- same depth (20), same Poseidon2 internal hashing, same 100-root ring buffer. It is a separate deployment with its own state.

Similarly, OpenNullifierRegistry is a separate mapping from the token NullifierRegistry. A nullifier spent in one registry has no effect on the other.

OpenGhostReveal

The OpenGhostReveal contract handles the reveal side of data commitments. During a reveal, the user proves they know the preimage of a commitment in the OpenCommitmentTree without revealing which commitment is theirs.

Reveal Verification

interface IOpenGhostReveal {
/// @notice Verify a data reveal proof
/// @param proof The Groth16 proof bytes
/// @param root The Merkle root used during proof generation
/// @param nullifier The nullifier for this commitment
/// @param dataHash The hash of the data being revealed
function reveal(
bytes calldata proof,
uint256 root,
uint256 nullifier,
uint256 dataHash
) external;

event DataRevealed(
uint256 indexed nullifier,
uint256 dataHash,
uint256 timestamp
);
}

The reveal flow verifies:

  1. The root exists in the OpenCommitmentTree's root ring buffer
  2. The nullifier has not been spent in OpenNullifierRegistry
  3. The ZK proof is valid -- the prover knows (secret, nullifierSecret, blinding) such that Poseidon4(secret, nullifierSecret, dataHash, blinding) is a leaf in the tree at the given root
  4. The nullifier was correctly derived from the commitment

Upon successful verification, the nullifier is marked as spent in OpenNullifierRegistry and a DataRevealed event is emitted.

Use Cases

Revels (Permissionless Secret Sharing)

Revels are Specter's permissionless secret sharing primitive. A user commits encrypted data to OpenGhostVault, then shares the decryption key through a separate channel. The reveal proves the data was committed without exposing the committer's identity.

The key property is that Bob can verify the data was committed at a specific time (block timestamp) without any on-chain link to Alice. Alice's commit transaction could have come from any address -- potentially a fresh address funded by the Gas Relayer.

PersistentKeyVault

The PersistentKeyVault contract builds on OpenGhostVault to provide reusable encryption keys. Instead of generating a new key for each interaction, a user commits a long-lived key to OpenGhostVault and proves knowledge of it repeatedly using different session nonces.

The data flow for PersistentKeyVault:

  1. User generates a persistent key pair
  2. User commits Poseidon4(secret, nullifierSecret, keyHash, blinding) to OpenGhostVault
  3. For each session, the user generates an access proof (see Access Proof Circuit) that proves knowledge of the committed key without spending the nullifier
  4. The access proof uses a sessionNonce to produce a unique accessTag per session, ensuring sessions are unlinkable

This pattern enables recurring private interactions -- such as authenticated access to a private API, repeated decryption of a data stream, or ongoing participation in a private channel -- without creating a new commitment for each interaction.

Confidential Document Attestation

Organizations can commit document hashes to OpenGhostVault, later revealing specific documents when needed (e.g., for audits or legal proceedings) while proving the document existed at the commit timestamp. The commit/reveal unlinkability means the organization's document inventory remains private until selective disclosure is required.

// Commit a document hash
const documentBytes = new TextEncoder().encode(documentContent);
const dataHash = BigInt(ethers.keccak256(documentBytes)) % BN254_FIELD;
const commitment = poseidon4([secret, nullifierSecret, dataHash, blinding]);

const tx = await openGhostVault.commit(commitment, {
value: ethers.parseEther("0.01"), // commit fee
});

Architecture Diagram

The two systems (data privacy and token privacy) share no on-chain state. They are completely independent deployments with separate trees, separate nullifier registries, and separate verification circuits.

Comparison: OpenGhostVault vs CommitRevealVault

FeatureCommitRevealVaultOpenGhostVault
PurposeToken privacyData privacy
CommitmentPoseidon7(secret, nullifierSecret, tokenId, amount, blinding, policyId, policyParamsHash)Poseidon4(secret, nullifierSecret, dataHash, blinding)
On commitBurns committed token amountCollects flat GHOST fee
On revealMints tokens to recipientEmits data reveal event
Change commitmentYes (partial withdrawals)No (no amounts to split)
Policy enforcementYes (AssetGuard + policy contracts)No
Token bindingYes (tokenId in commitment)No
Nullifier purposePrevent double-spendingPrevent replay
Merkle treeCommitmentTreeOpenCommitmentTree
CircuitGhostRedemption(20)OpenGhost circuit