Proof Verification

Learn how to verify the integrity of actions and state on the Dual platform using Merkle proofs and zero-knowledge proofs.

Verify the integrity of any action on the Dual platform. Two types of cryptographic proofs, Merkle inclusion and zero-knowledge validity, enable investors, developers, auditors, and regulators to independently confirm that actions were processed correctly and state is tamper-proof.

Why Verification Matters

Trust in the Dual platform is not blind. Every action can be independently verified using cryptographic proofs. Instead of relying on Dual's assertions, you can perform mathematical verification that proves:

  • Don't trust, verify: Anyone can independently confirm the platform state without relying on Dual's word.
  • No reliance on Dual: Cryptographic proof is mathematical certainty. The proofs are valid even if Dual goes offline or acts maliciously.
  • Regulatory value: Auditors and regulators can verify platform state without accessing sensitive data. Proofs are non-interactive and tamper-proof.
  • Investor confidence: Proof of correctness is embedded in the platform. Institutional participants can rely on cryptographic guarantees, not trust.

Key Metrics

Two Types of Proof

Dual uses two complementary proof systems to enable trustless verification:

Merkle Inclusion Proofs

Prove that a specific action is part of a checkpoint Merkle tree.

  • How it works: Path from leaf (action hash) to root (checkpoint hash)
  • When to use: "Was my transaction included?"
  • Proof size: O(log n), typically 200–300 bytes
Code
Leaf → Sibling₁ → Sibling₂ → ... → Root ✓

ZK Validity Proofs

Prove that all state transitions in a checkpoint are valid.

  • How it works: ZK-SNARK verifies computation without replaying it
  • When to use: "Were all rules followed?"
  • Verification: Single elliptic curve pairing check
Code
Proof + Public Input → Verifier → ✓/✗

Merkle Proof Deep Dive

Merkle proofs allow you to prove that a specific action was included in a checkpoint without downloading the entire batch. They are small, fast to verify, and work off-chain or on-chain.

Leaf Hash Construction

Each leaf is a SHA-256 hash of action metadata:

Code
leafHash = SHA256(
orgId || actionId || timestamp ||
payloadHash
)

Proof Size and Complexity

Logarithmic space, grows slowly with batch size:

Code
Tree Depth: log₂(N)
Proof Size: 32 × log₂(N) bytes
For 10,000 actions: ~5.3 KB proof

Verification Algorithm

To verify a Merkle proof:

  1. Compute the leaf hash from action data (orgId || actionId || timestamp || payloadHash)
  2. Obtain the Merkle proof (array of sibling hashes from leaf to root)
  3. Hash upward along the proof path: hash(leaf, sibling₁)hash(result, sibling₂) → ...
  4. Verify the computed root matches the on-chain checkpoint root

This computation is O(log N) and requires only the Merkle proof and the checkpoint root, no need to download the entire batch.

Code: Verifying a Merkle Proof

Using the Dual SDK:

typescript
import { DualClient } from "dual-sdk";
import crypto from "crypto";
const client = new DualClient({
token: process.env.DUAL_API_KEY,
authMode: "api_key",
});
// Get the Merkle proof for an action
const proofData = await client.sequencer.getProof(
"action-id-12345"
);
// Manually verify (optional, SDK does this automatically)
function verifyMerkleProof(
leaf,
proof,
root
) {
let hash = leaf;
for (const sibling of proof) {
hash = crypto
.createHash("sha256")
.update(Buffer.concat([hash, sibling]))
.digest();
}
return hash.equals(root);
}
const isIncluded = verifyMerkleProof(
proofData.leafHash,
proofData.proof,
proofData.checkpointRoot
);
console.log(isIncluded ? "✓ Included" : "✗ Not found");

ZK Validity Proof Deep Dive

Zero-knowledge proofs prove that all state transitions in a checkpoint are valid without replaying the computation. This enables regulators and auditors to verify correctness independently.

What the Circuit Proves

  • Signature validity: All action signatures are cryptographically valid and signed by authorized parties
  • State transitions: All state transitions follow template rules and ownership constraints
  • No double-spends: No token is transferred or burned twice in the same checkpoint
  • State root: Final state root matches the claimed state root hash

Public Inputs

Known to both prover and verifier:

Code
checkpointRoot
previousStateRoot
finalStateRoot
actionCount

Verification

Performed via DUALVerifier contract:

Code
verify(proof,
publicInputs)
→ bool

Code: Verifying On-Chain

Call the DUALVerifier contract directly using ethers.js:

typescript
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider(RPC_URL);
const verifier = new ethers.Contract(
DUAL_VERIFIER_ADDRESS,
VERIFIER_ABI,
provider
);
const publicInputs = [
checkpointRoot,
previousStateRoot,
finalStateRoot,
actionCount
];
const isValid = await verifier.verify(
zkProof,
publicInputs
);
if (isValid) {
console.log("✓ ZK proof verified on-chain");
} else {
console.log("✗ Proof verification failed");
}

Verification Workflows

Different workflows for different use cases:

Developers, Verify via API

GET /sequencer/proof/:actionId, Fetch Merkle proof for a single action and verify off-chain using SDK methods.

Auditors, Bulk verification

GET /sequencer/checkpoints/:id, Download a checkpoint and verify all proofs in batch. Stream actions and verify state transitions.

Regulators, On-chain verification

DUALVerifier.verify(), Call the verifier contract directly. No API access needed, verification is independent of Dual servers.

Users, End-user flow

App webhook, Application automatically verifies proof when checkpoint is confirmed. Display verified badge in UI.

Building Verification Into Your App

Integrate proof verification into your application to show users that their actions are cryptographically verified.

SDK Methods

  • client.sequencer.getProof(actionId), Fetch the Merkle proof for a given action
  • client.sequencer.verifyProof(proof, root), Verify a Merkle proof against a checkpoint root (off-chain)
  • client.sequencer.getCheckpoint(checkpointId), Fetch full checkpoint metadata and actions

Full Example: Fetch and Verify

End-to-end verification flow:

typescript
async function verifyAction(actionId) {
// Step 1: Get the proof from the sequencer
const { proof, checkpointRoot } =
await client.sequencer.getProof(actionId);
// Step 2: Verify the proof
const isValid = await client.sequencer.verifyProof({
actionId,
proof,
checkpointRoot,
});
// Step 3: Show result to user
if (isValid) {
showVerifiedBadge(actionId);
console.log("✓ Cryptographically verified");
} else {
showErrorBadge(actionId);
console.log("✗ Verification failed");
}
return isValid;
}
// Call it after action confirmation
await client.actions.submit(actionData);
await verifyAction("action-id-12345");

Webhook Integration

Auto-verify when checkpoints confirm:

typescript
// Listen for checkpoint.confirmed webhook
app.post("/webhooks/checkpoint", async (req) => {
const { checkpointId, actions } = req.body;
// Verify all actions in the checkpoint
const results = await Promise.all(
actions.map(async (action) => {
const isValid = await client.sequencer
.verifyProof({
actionId: action.id,
proof: action.proof,
checkpointRoot: action.checkpointRoot,
});
return { actionId: action.id, verified: isValid };
})
);
// Mark actions as verified in your database
await markActionsVerified(results);
});

UI Pattern: Verification Status Badge

Display verification status in your UI:

tsx
function ActionRow({ action, verified }) {
return (
<div className="flex items-center gap-3">
<span>{action.description}</span>
{verified ? (
<span className="badge verified">
✓ Verified
</span>
) : verified === false ? (
<span className="badge error">
✗ Failed
</span>
) : (
<span className="badge pending">
⏳ Verifying...
</span>
)}
</div>
);
}

Trust Model

Dual offers multiple trust levels. Choose the one appropriate for your risk model:

Level 1: API Trust

Trust Dual's API response directly.

Trust Assumption: Dual server is honest

Use when: Early development, internal testing, low-stakes transactions

Level 2: Merkle Trust

Trust cryptographic math, not Dual.

Trust Assumption: SHA-256 is secure

Use when: Production apps, user-facing transactions, external audits

Level 3: On-Chain Trust

Trust the blockchain, not Dual.

Trust Assumption: Blockchain consensus

Use when: Institutional settlement, regulatory compliance, high-value assets