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:
The quantum commitment is a keccak256 hash:
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 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:
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:
persistentKeyVault.storeKeyPartB(keyId, commitment, encKeyPartB, quantumCommitment);
The keyId is deterministically computed as:
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:
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 |
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:
- The prover knows the preimage of a commitment in the Merkle tree.
- 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:
bytes memory encKeyPartB = persistentKeyVault.accessKeyPartB(
keyId,
proof,
publicInputs,
quantumSecret,
newQuantumCommitment
);
The vault performs the following checks:
- Proof verification: forwards the proof and public inputs to the on-chain Groth16 verifier. Reverts if invalid.
- Anti-replay: checks that the
accessTaghas not been previously recorded. Reverts if it has. Records the newaccessTag. - Quantum verification: checks that
keccak256(quantumSecret) == stored quantumCommitment. Reverts if it does not match. - Quantum rotation: updates the stored quantum commitment to
newQuantumCommitment(provided by the client). - Key return: returns
encKeyPartBwithout 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:
privateKeyis overwritten with zeros.encKeyPartBis 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:
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:
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
existsbefore 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.