Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.kynesys.xyz/llms.txt

Use this file to discover all available pages before exploring further.

How L2PS Transactions Are Handled

This page walks through the lifecycle of an L2PS transaction from client submission to L1 confirmation.

Client-Side Encryption

Before any transaction leaves the user’s device, it is encrypted using AES-GCM (32-byte key, 12-byte IV, with the GCM authentication tag preserved alongside the ciphertext). The SDK API is the L2PS class from @kynesyslabs/demosdk/l2ps:
import { Demos } from "@kynesyslabs/demosdk/websdk"
import L2PS from "@kynesyslabs/demosdk/l2ps"

// Connect to a node and a wallet
const demos = new Demos()
await demos.connect("https://node2.demos.sh")
await demos.connectWallet(mnemonic)

// Build the inner transaction (a normal Demos transaction)
const innerTx = await demos.transfer(
    "0xRecipient...",
    100_000_000_000n, // 100 DEM in OS
)

// Create or rejoin an L2PS instance with the shared subnet keys
const subnet = await L2PS.create(privateKeyHex, ivHex)

// Wrap the transaction in the L2PS encrypted envelope
const encryptedTx = await subnet.encryptTx(innerTx)
// encryptedTx.content.type === "l2psEncryptedTx"
// encryptedTx.content.data carries an L2PSEncryptedPayload:
//   { l2ps_uid, encrypted_data (base64 ciphertext),
//     tag (base64 GCM auth tag), original_hash }
For the full SDK reference (factory, instance registry, decrypt, config), see L2PS SDK.
The 32-byte AES key and the 12-byte IV must match exactly between every member of the subnet. If they don’t match, decryption fails and the transaction is rejected. Distribute these keys out-of-band — the SDK never transmits them.

Node Processing

When an L2PS node receives an l2psEncryptedTx:

Step 1: Decryption & Validation

The node looks up the subnet by l2ps_uid, decrypts the payload (AES-GCM verifies the auth tag — tampered ciphertext is rejected), then validates the inner transaction against L1 state (signature, nonce, balance) before executing it through the standard L2PS executor.

Step 2: GCR Edit Generation

L2PS uses the Global Change Registry (GCR) architecture. Instead of mutating state directly, a transaction emits GCR edits:
// GCR edits generated for a transfer (amounts in OS post-fork)
[
    { type: "balance_edit", account: "0xSender...",   delta: -101_000_000_000n }, // amount + 1 DEM L2PS fee
    { type: "balance_edit", account: "0xRecipient...", delta: +100_000_000_000n },
]
The 1 DEM L2PS fee is burned (not credited to any account). Total L2PS tx fee = L2PS_TX_FEE = 1 DEM per transaction.

Step 3: Mempool Storage

Validated transactions enter the L2PS-specific mempool, keyed by hash and L2PS UID. The mempool stores the encrypted blob, the resolved GCR edits, and the current status (l2ps_pending / l2ps_batched / l2ps_confirmed).

Batch Aggregation

The L2PS Batch Aggregator runs per L1 block and bundles pending transactions:
1

Collect Transactions

Gather pending L2PS transactions from the mempool — capped at ZK_CIRCUIT_MAX_BATCH_SIZE = 10 per batch.
2

Aggregate GCR Edits

Combine all balance changes into a single set of GCR edits.
3

Generate ZK Proof

Create a PLONK proof verifying batch validity without revealing content.
4

Submit L1 Batch

Single L1 transaction submitted containing the proof and the batch hash.

Configuration

SettingDefaultDescription
ZK_CIRCUIT_MAX_BATCH_SIZE10Maximum transactions per batch
L2PS_TX_FEE1 DEMPer-transaction fee (burned)

ZK Proof Generation

Each batch carries a PLONK zero-knowledge proof. The proof verifies:
  • All inner-transaction signatures are valid
  • All GCR edits are arithmetically correct
  • No double-spending within the batch
  • Batch integrity (no tampering)
Proofs are generated against pre-computed proving keys per batch size (e.g. keys/batch_5/, keys/batch_10/).
ZK proofs let validators verify batch validity without seeing the actual transaction content.

Consensus Integration

When a new L1 block is created, L2PS Consensus applies the batch:
  • Pending proofs for the block are loaded.
  • Each proof is verified.
  • The batch’s GCR edits are applied to L1 state via HandleGCR.apply(...).
  • Transaction statuses are advanced to l2ps_confirmed.

Transaction Status Flow

⚡ Executed (l2ps_pending)

Validated locally, GCR edits generated (instant)

📦 Batched (l2ps_batched)

Included in an L1 batch transaction (per block)

✓ Confirmed (l2ps_confirmed)

L1 block finalized (final)
StatusMeaningReversible?
l2ps_pendingValidated locally, GCR edits generatedYes (if batch fails)
l2ps_batchedIncluded in an L1 batch transactionYes (if block fails)
l2ps_confirmedL1 block finalizedNo

Authenticated History Access

Unlike L1 transactions (public), L2PS history requires cryptographic proof of ownership:
const timestamp = Date.now()
const message = `getL2PSHistory:${address}:${timestamp}`
const { data: signature } = await demos.signMessage(message)

const response = await demos.rpcCall({
    method: "getL2PSAccountTransactions",
    params: [{
        l2psUid: "testnet_l2ps_001",
        address,
        timestamp,
        signature,
    }],
})
If the signature is invalid or doesn’t match the requested address, the node returns 403 Access Denied.

Data Separation

L2PS maintains strict data separation for privacy:

L2PS Participants

  • Store: Full encrypted transactions
  • Process: Decrypt locally
  • See: Transaction content

Validators

  • Store: Only batch hashes and proofs
  • Process: Verify ZK proofs
  • See: Zero transaction visibility

Next Steps

Quick Start

Set up L2PS from scratch

L2PS SDK

Use L2PS in your application