Building ZKAP Frames
A ZKAP Frame is the core object that replaces traditional blockchain transactions. Instead of sending a public, metadata-rich transaction, developers use the SDK to construct an encrypted, zero-knowledge–validated frame that can be submitted to the ZKAP Validator contract.
This file explains how to build a ZKAP Frame step-by-step: identity → payload → encryption → proof → frame assembly.
1. What is a ZKAP Frame?
A ZKAP Frame is a structured object containing:
a zero-knowledge proof
a commitment binding the payload
encrypted payload (ciphertext)
a nullifier (to prevent replay attacks)
a session hash
an expiry block
The final frame looks like this conceptually:
{
zk_proof: "0x...",
commitment_root: "0x...",
payload_ciphertext: "0x...",
nullifier: "0x...",
session_hash: "0x...",
expiry_block: 12345678
}Every field is essential for validity and anonymity.
2. Prerequisites before building a frame
Before building a frame, you must have:
An ephemeral anonymous identity
A well-formed payload
A symmetric encryption key
Access to the ZK proof system
The expiry block for the request
These are typically created using the SDK.
3. Step 1 — Generate an Anonymous Identity
Identity is ephemeral and unlinkable.
Example (SDK function):
const id = generateIdentity();
This identity is used only inside the proof.
4. Step 2 — Create the Payload
A payload is a plain object representing the action.
Example:
const payload = {
action: "swap",
tokenIn: "USDC",
tokenOut: "ETH",
amount: "500",
minOut: "0.3",
expiry: 12345678
};This data will be encrypted before being added to the frame.
5. Step 3 — Encrypt the Payload
Encryption ensures no one can read the payload except the destination application.
Example:
const ciphertext = await encryptPayload(payload, sessionKey);
The ciphertext hides all details, including:
token types
amounts
action type
routing logic
strategy parameters
6. Step 4 — Generate the Zero-Knowledge Proof
The proof validates:
payload structure
identity correctness
nullifier derivation
expiry constraints
commitment root consistency
Example (conceptual):
const proof = await generateProof({
identity: id,
payloadHash,
nonce,
expiry
});The blockchain will verify this proof using the ZKAP Validator.
7. Step 5 — Build the Commitment Root
The commitment root binds the encrypted payload and expiry together.
commitment_root = Hash(payload_hash || expiry_block)
This guarantees that the payload cannot be tampered with after proof generation.
8. Step 6 — Derive the Nullifier
The nullifier prevents replay attacks.
const nullifier = deriveNullifier(id, nonce);
A nullifier:
reveals nothing about identity
prevents replays
is safe to publish
It’s the only persistent state stored by the validator.
9. Step 7 — Create the Session Hash
The session hash provides optional context for ephemeral flows.
const sessionHash = createSessionHash(id, payloadHash);
This makes correlation between actions impossible.
10. Step 8 — Assemble the ZKAP Frame
Now combine all components:
const frame = {
zk_proof: proof,
commitment_root,
payload_ciphertext: ciphertext,
nullifier,
session_hash: sessionHash,
expiry_block: payload.expiry
};This frame is what gets submitted to the blockchain.
11. Step 9 — Submit the Frame
Once assembled, the frame can be submitted to the ZKAP Validator:
await submitFrame(frame);
If the proof verifies and nullifier is fresh, a AnonymousAction event is emitted.
12. Example Code (Putting It All Together)
Below is a simplified conceptual example:
import {
generateIdentity,
encryptPayload,
generateProof,
buildFrame,
submitFrame
} from "@zkap/sdk";
(async () => {
const id = generateIdentity();
const payload = { action: "swap", amount: 500, expiry: 12345678 };
const ciphertext = await encryptPayload(payload);
const proof = await generateProof({ identity: id, payload });
const frame = await buildFrame({ proof, ciphertext, id, payload });
await submitFrame(frame);
})();The SDK abstracts many low-level details, making integration simple.
Last updated