Key Formats
Specter uses two JSON key formats for different privacy use cases. ghostchain-v2 keys are one-time keys used for token privacy (commit/reveal), while open-ghost-persistent-v1 keys are persistent keys used for data privacy (encrypted storage with revocable access).
ghostchain-v2 (Token Privacy Keys)
The ghostchain-v2 format encodes all cryptographic material needed to reveal a committed token deposit. Each key corresponds to exactly one commitment in the Merkle tree and is consumed (invalidated by nullifier) after a single reveal.
Schema
{
"version": "ghostchain-v2",
"token": "0xA0bA5389b07BAdDAaE89B8560849774Bf015acc3",
"seed": null,
"secret": "18493027561038472950183746502918374650291837465029183746502918",
"nullifierSecret": "7482910384756102938475610293847561029384756102938475610293847",
"blinding": "3948271506382940175603829401756038294017560382940175603829401",
"amount": "100000000000000000000",
"commitment": "12849305718294057182940571829405718294057182940571829405718294",
"leafIndex": 42,
"tokenIdHash": "9182736450918273645091827364509182736450918273645091827364509",
"quantumSecret": "0xa1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
"policyId": "0x0000000000000000000000000000000000000000",
"policyParamsHash": "0",
"policyParams": null
}
Field Descriptions
| Field | Type | Required | Description |
|---|---|---|---|
version | string | Yes | Must be "ghostchain-v2". Used for format detection and migration. |
token | string | Yes | The EVM address of the committed token contract. For native GHOST, this is the NativeAssetHandler address (0xA0bA5389b07BAdDAaE89B8560849774Bf015acc3). |
seed | string|null | No | Reserved for future HD key derivation. Currently always null. |
secret | string | Yes | A random scalar in the BN254 field (decimal string). The primary secret input to the Poseidon7 commitment hash. Knowledge of this value is required to generate a valid reveal proof. |
nullifierSecret | string | Yes | A random scalar in the BN254 field (decimal string). Combined with leafIndex via Poseidon2 to derive the nullifier. The nullifier is published during reveal to prevent double-spending. |
blinding | string | Yes | A random scalar in the BN254 field (decimal string). Provides additional entropy to the commitment, ensuring that two commitments with the same secret, token, and amount produce different hashes. |
amount | string | Yes | The committed token amount in the smallest unit (e.g., aghost for GHOST, 6-decimal units for gUSDC). Decimal string representation of a uint256. |
commitment | string | Yes | The Poseidon7 hash of (secret, nullifierSecret, blinding, tokenIdHash, amount, policyId, policyParamsHash). Stored on-chain in the Merkle tree. Decimal string representation of a BN254 field element. |
leafIndex | number|null | Yes* | The index of the commitment in the on-chain Merkle tree (uint32). null before the commit transaction confirms; must be populated before reveal. Required for nullifier derivation and Merkle proof generation. |
tokenIdHash | string | Yes | Poseidon2 hash of (tokenAddress, 0). Identifies the token inside the ZK circuit without revealing the raw address. Decimal string. |
quantumSecret | string | Yes | A 256-bit random hex string (with 0x prefix). Reserved for future post-quantum key encapsulation. Currently included in key material but not used in the ZK circuit. |
policyId | string | Yes | The EVM address of the policy contract applied to this commitment. "0x0000000000000000000000000000000000000000" for no policy. |
policyParamsHash | string | Yes | The keccak256 hash of the ABI-encoded policy parameters, represented as a decimal string. "0" when no policy is applied. |
policyParams | string|null | No | The raw ABI-encoded policy parameters (hex string). null when no policy is applied. Stored for convenience so the user does not need to reconstruct the encoding at reveal time. |
Commitment Derivation
commitment = Poseidon7(
secret,
nullifierSecret,
blinding,
tokenIdHash,
amount,
policyId, // cast to uint256
policyParamsHash // reduced to BN254 field
)
Nullifier Derivation
nullifier = Poseidon2(nullifierSecret, leafIndex)
open-ghost-persistent-v1 (Data Privacy Keys)
The open-ghost-persistent-v1 format encodes persistent keys used for the OpenGhost data privacy system. Unlike token keys which are one-time-use, persistent keys can be used repeatedly for encrypted data storage, access control, and revocable sharing.
Schema
{
"version": "open-ghost-persistent-v1",
"persistent": true,
"contentType": "text/plain",
"encryptedSecret": "0xabcdef1234567890...",
"encKeyPartA": "0x1234567890abcdef...",
"keyVaultId": 7,
"revokePolicy": "0xd84D534E94f1eacE9BC5e9Bd90338d574d02B95c",
"secret": "18493027561038472950183746502918374650291837465029183746502918",
"nullifierSecret": "7482910384756102938475610293847561029384756102938475610293847",
"dataHash": "0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"blinding": "3948271506382940175603829401756038294017560382940175603829401",
"commitment": "12849305718294057182940571829405718294057182940571829405718294",
"leafIndex": 15,
"createdAt": "2026-03-10T14:30:00Z"
}
Field Descriptions
| Field | Type | Required | Description |
|---|---|---|---|
version | string | Yes | Must be "open-ghost-persistent-v1". |
persistent | boolean | Yes | Always true. Distinguishes persistent keys from one-time token keys. |
contentType | string | Yes | MIME type of the encrypted content (e.g., "text/plain", "application/json", "application/octet-stream"). Used by the client to determine how to display or process the decrypted data. |
encryptedSecret | string | Yes | The encrypted payload (hex string with 0x prefix). This is the actual encrypted data, encrypted with a key derived from secret. Only the key holder can decrypt it. |
encKeyPartA | string | Yes | The first part of the encryption key (hex string with 0x prefix). Used in a split-key scheme where part A is stored in the key JSON and part B is derived from the on-chain commitment. Both parts are needed to reconstruct the decryption key. |
keyVaultId | number | Yes | The ID of the key entry in the on-chain PersistentKeyVault contract (0x338B0e3c722702E705357C291E151D76B8Fd9F61). Used to fetch on-chain metadata, check revocation status, and retrieve part B of the encryption key. |
revokePolicy | string | Yes | The EVM address of the policy contract that governs revocation of this persistent key. For example, the TimelockExpiry policy can auto-revoke access after a specified time. "0x0000000000000000000000000000000000000000" for no revocation policy. |
secret | string | Yes | A random scalar in the BN254 field (decimal string). Used as the primary input for deriving the encryption key and the commitment hash. |
nullifierSecret | string | Yes | A random scalar in the BN254 field (decimal string). Used to derive nullifiers for access proofs. Unlike token keys, persistent key nullifiers may be used multiple times (access is non-destructive). |
dataHash | string | Yes | The keccak256 hash of the unencrypted data (hex string with 0x prefix). Allows the client to verify data integrity after decryption without re-encrypting. |
blinding | string | Yes | A random scalar in the BN254 field (decimal string). Additional entropy for the commitment hash. |
commitment | string | Yes | The Poseidon4 hash of (secret, nullifierSecret, dataHash, blinding). Stored on-chain in the persistent key tree. Decimal string. |
leafIndex | number|null | Yes* | The index of the commitment in the persistent key Merkle tree. null before the on-chain registration confirms. |
createdAt | string | Yes | ISO 8601 timestamp of key creation. Used for display and ordering in the client UI. |
Commitment Derivation
Persistent keys use Poseidon4 (T5) instead of Poseidon7:
commitment = Poseidon4(
secret,
nullifierSecret,
dataHash, // reduced to BN254 field from keccak256
blinding
)
Access Proof
To prove access to a persistent key without revealing which key, the holder generates a Groth16 proof demonstrating knowledge of the commitment's preimage and its presence in the persistent key Merkle tree. The proof is verified by the AccessProofVerifier contract (0x508d326D68e5da728f8A74CB4ADB7552f7768B66).
Format Detection
To determine which key format a JSON blob uses, check the version field:
function detectKeyFormat(json) {
const key = typeof json === "string" ? JSON.parse(json) : json;
switch (key.version) {
case "ghostchain-v2":
return "token-privacy";
case "open-ghost-persistent-v1":
return "data-privacy";
default:
throw new Error(`Unknown key format: ${key.version}`);
}
}
Security Notes
- Both key formats contain secret cryptographic material. Treat them with the same care as private keys.
- Keys should be stored encrypted (the webapp encrypts localStorage entries with a wallet-signed key).
- Never transmit keys over unencrypted channels.
- For
ghostchain-v2keys, thesecret,nullifierSecret, andblindingfields together are sufficient to steal committed tokens. - For
open-ghost-persistent-v1keys, thesecretandencKeyPartAfields together (combined with on-chain data) are sufficient to decrypt the stored data.