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:

  1. An ephemeral anonymous identity

  2. A well-formed payload

  3. A symmetric encryption key

  4. Access to the ZK proof system

  5. 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