Skip to main content

Merkle Proofs

TL;DR

The note commitments live in an on-chain incremental Merkle tree. These endpoints expose the engine's mirror of it: the current root, an inclusion proof for a note you own, and a paginated leaf read. You use the inclusion proof to generate the zero-knowledge input proof that backs an order or a withdrawal.

Why you need these

Two of the things you do on Darknyx require proving a note exists in the tree:

  • Backing an order. An order's collateral note must be provably in the tree; the input proof you attach to a place-order request is generated against an inclusion path.
  • Withdrawing. Spending a note out of the pool proves its inclusion and publishes its nullifier.

The tree is sharded for settlement throughput; each shard has its own root. Reads take an optional tree_id (default 0).

GET /tree/root

The current Merkle root of a shard. Public.

GET /tree/root?tree_id=0

Response

{
"tree_id": 0,
"merkle_root": "…",
"leaf_count": 1284,
"on_chain_slot": 309482001
}
FieldTypeDescription
tree_idintegerWhich shard this root is for (echoes the request; default 0).
merkle_rootstringCurrent root of the shard, hex. Equals the on-chain root (or a recent root still in the shard's ring buffer).
leaf_countintegerNumber of leaves in this shard.
on_chain_slotintegerSolana slot at which the engine last synced this shard from chain.
Cross-check on-chain

The root is also readable directly from the Solana program. The endpoint is a convenience mirror; a client that wants zero trust in the engine for this value can read the on-chain account itself.

GET /tree/inclusion

An inclusion proof for a note commitment. Authenticated (bearer).

GET /tree/inclusion?note_commitment=<hex>&tree_id=0

Response

{
"note_commitment": "…",
"leaf_index": 902,
"merkle_root": "…",
"siblings": ["…", "…", "… (20 entries) …"]
}
FieldTypeDescription
note_commitmentstringThe note commitment proven, hex.
leaf_indexintegerThe note's index in the tree.
merkle_rootstringThe root the proof is against.
siblingsstring[]The 20 sibling hashes from leaf to root (the authentication path).

The siblings path plus your secret note opening is the witness the SDK feeds to the input-proof circuit. You do not assemble the proof by hand. The SDK takes the inclusion proof and produces the Groth16 proof you attach to an order or withdrawal.

Roots age out

A proof is generated against a specific root. The on-chain program keeps a bounded ring buffer of recent roots, so a proof must be used (settled or withdrawn against) while its root is still in that window. In practice this means an order must settle within a bounded number of tree updates of when it was proven. The engine and SDK manage this; it is why a placed order carries the merkle_root it was proven against.

GET /tree/leaves

A paginated read of raw leaves. Authenticated (bearer). Use it to rebuild a local mirror of the tree from scratch (the "scan once, then follow updates" pattern the SDK uses to maintain your note store).

GET /tree/leaves?tree_id=0&from=0&limit=512

Response

{
"tree_id": 0,
"merkle_root": "…",
"leaves": [
{ "leaf_index": 0, "value": "…" },
{ "leaf_index": 1, "value": "…" }
]
}
FieldTypeDescription
leaves[].leaf_indexintegerThe leaf's position in the tree.
leaves[].valuestringThe leaf hash (a note commitment), hex.
merkle_rootstringThe root the page is consistent with.

A leaf value is a note commitment, an opaque hash. It tells you a note exists, not who owns it or what it is worth; only your spending key turns the leaves you own into balances. See Account Model.