Circuit Inputs & Outputs
Complete reference for all inputs and outputs of both ZK circuits.
Redemption circuit
Public inputs (8)
| Index | Name | Type | Description |
|---|---|---|---|
| 0 | root | Field | Merkle tree root the proof is generated against |
| 1 | nullifier | Field | Derived nullifier = Poseidon2(nullifierSecret, leafIndex) |
| 2 | withdrawAmount | uint256 | Amount being withdrawn (in aghost) |
| 3 | recipient | address (as Field) | Address receiving the minted tokens |
| 4 | changeCommitment | Field | Poseidon hash of the change commitment (0 for full reveal) |
| 5 | tokenId | Field | Token identifier = keccak256(tokenAddress) % p |
| 6 | policyId | address (as Field) | Policy contract address (0 if no policy) |
| 7 | policyParamsHash | Field | Poseidon hash of policy parameters (0 if no policy) |
Private inputs
| Name | Type | Description |
|---|---|---|
secret | Field | User's private key for this commitment |
nullifierSecret | Field | Used to derive the nullifier |
amount | uint256 | Full committed amount |
blinding | Field | Random blinding factor |
pathElements[20] | Field[20] | Merkle proof sibling hashes |
pathIndices[20] | bit[20] | Merkle proof path directions (0=left, 1=right) |
newBlinding | Field | Blinding factor for the change commitment |
Constraints verified
1. commitment = Poseidon5(secret, nullifierSecret, tokenId, amount, blinding)
2. MerkleProof(commitment, pathElements, pathIndices) == root
3. leafIndex = binaryToDecimal(pathIndices)
4. nullifier == Poseidon2(nullifierSecret, leafIndex)
5. amount == withdrawAmount + changeAmount
6. If changeAmount > 0: changeCommitment == Poseidon5(secret, newNullifierSecret, tokenId, changeAmount, newBlinding)
7. If policyId != 0: commitment includes policyId and policyParamsHash
Access proof circuit
Public inputs (4)
| Index | Name | Type | Description |
|---|---|---|---|
| 0 | root | Field | Merkle tree root |
| 1 | dataHash | Field | Hash of the sealed data being accessed |
| 2 | sessionNonce | Field | Random nonce for this session |
| 3 | accessTag | Field | Poseidon2(nullifierSecret, sessionNonce) |
Private inputs
| Name | Type | Description |
|---|---|---|
secret | Field | User's private key |
nullifierSecret | Field | Used to derive the access tag |
blinding | Field | Blinding factor |
pathElements[20] | Field[20] | Merkle proof sibling hashes |
pathIndices[20] | bit[20] | Merkle proof path directions |
Constraints verified
1. commitment = Poseidon5(secret, nullifierSecret, tokenId, amount, blinding)
2. MerkleProof(commitment, pathElements, pathIndices) == root
3. accessTag == Poseidon2(nullifierSecret, sessionNonce)
Field modulus
All field elements must be less than the BN254 scalar field modulus:
p = 21888242871839275222246405745257275088548364400416034343698204186575808495617
This is approximately . Values that exceed this modulus will wrap around, producing invalid proofs.
Computing the leaf index
The leaf index is derived from the pathIndices array (binary encoding):
leafIndex = pathIndices[0] * 2^0 + pathIndices[1] * 2^1 + ... + pathIndices[19] * 2^19
This index determines the nullifier value through Poseidon2(nullifierSecret, leafIndex).