Skip to main content

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

FieldTypeRequiredDescription
versionstringYesMust be "ghostchain-v2". Used for format detection and migration.
tokenstringYesThe EVM address of the committed token contract. For native GHOST, this is the NativeAssetHandler address (0xA0bA5389b07BAdDAaE89B8560849774Bf015acc3).
seedstring|nullNoReserved for future HD key derivation. Currently always null.
secretstringYesA 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.
nullifierSecretstringYesA 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.
blindingstringYesA 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.
amountstringYesThe committed token amount in the smallest unit (e.g., aghost for GHOST, 6-decimal units for gUSDC). Decimal string representation of a uint256.
commitmentstringYesThe 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.
leafIndexnumber|nullYes*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.
tokenIdHashstringYesPoseidon2 hash of (tokenAddress, 0). Identifies the token inside the ZK circuit without revealing the raw address. Decimal string.
quantumSecretstringYesA 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.
policyIdstringYesThe EVM address of the policy contract applied to this commitment. "0x0000000000000000000000000000000000000000" for no policy.
policyParamsHashstringYesThe keccak256 hash of the ABI-encoded policy parameters, represented as a decimal string. "0" when no policy is applied.
policyParamsstring|nullNoThe 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

FieldTypeRequiredDescription
versionstringYesMust be "open-ghost-persistent-v1".
persistentbooleanYesAlways true. Distinguishes persistent keys from one-time token keys.
contentTypestringYesMIME 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.
encryptedSecretstringYesThe 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.
encKeyPartAstringYesThe 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.
keyVaultIdnumberYesThe 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.
revokePolicystringYesThe 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.
secretstringYesA random scalar in the BN254 field (decimal string). Used as the primary input for deriving the encryption key and the commitment hash.
nullifierSecretstringYesA 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).
dataHashstringYesThe keccak256 hash of the unencrypted data (hex string with 0x prefix). Allows the client to verify data integrity after decryption without re-encrypting.
blindingstringYesA random scalar in the BN254 field (decimal string). Additional entropy for the commitment hash.
commitmentstringYesThe Poseidon4 hash of (secret, nullifierSecret, dataHash, blinding). Stored on-chain in the persistent key tree. Decimal string.
leafIndexnumber|nullYes*The index of the commitment in the persistent key Merkle tree. null before the on-chain registration confirms.
createdAtstringYesISO 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-v2 keys, the secret, nullifierSecret, and blinding fields together are sufficient to steal committed tokens.
  • For open-ghost-persistent-v1 keys, the secret and encKeyPartA fields together (combined with on-chain data) are sufficient to decrypt the stored data.