Component
Joining
A join fills empty slots in the tree: one new leaf, or a whole cohort that the operator builds off-chain and grafts in as one subtree for the price of one transaction. The covenant checks only that the slots were empty and that the new money is paid in, so each joiner checks their own leaf before they pay.
A single join
The transition for one leaf is JOIN_0. Its witness proves that a
slot holds the empty node, carries the new leaf's hash and amount, and carries
the operator's signature. The script folds the empty node up the path and
requires the result to match the root the tree output commits to, which shows
the slot was empty. It then folds the new leaf up the same path and requires the
transaction to recreate the tree output under the new root, holding the old
total plus the new leaf's amount. The extra money must come from another input;
the covenant does not care whose.
A cohort graft
A cohort is a group of members grafted together into one aligned subtree: a
block of 2h consecutive slots under one node at height h. The
transition is JOIN_h. The operator builds the cohort's subtree
off-chain and the witness carries only its top node, a 32-byte hash and an
8-byte sum. The script checks that the slot held the empty node of height h,
folds the new node up the D − h levels above it, and requires the
tree output to grow by the subtree's sum.
Because the script walks only the levels above the slot, a bigger cohort is a smaller transaction. At depth 16:
| Transition | Members | Transaction, vB | Per member, operator funds | Per member, members fund |
|---|---|---|---|---|
JOIN_0 | 1 | 937 | 937 | about 995 |
JOIN_2 | 4 | 872 | 218 | about 280 |
JOIN_6 | 64 | 743 | 11.6 | about 70 |
JOIN_8 | 256 | 679 | 2.7 | about 61 |
JOIN_10 | 1,024 | 614 | 0.60 | about 59 |
| One output in an on-chain batch | 43 |
Each height is its own script, and the set of scripts is fixed when the tree
is created. The reference set carries JOIN_0 and
JOIN_2; a deployment that wants 256-member cohorts compiles
JOIN_8 in at creation. A slot of height h takes any cohort of up to
2h members, with the unused slots left empty. Placing a cohort in one
subtree also sets up the cheap removals later: the
sweep and the cohort
rewrite both work on an aligned subtree.
What the covenant does not check
The script sees the grafted node's hash and sum, and nothing below them. It
cannot tell a real subtree of 256 leaves from 32 random bytes. That is what
keeps the graft one constant-size transaction, and it has a precise
consequence. If the operator grafts a node with no real tree behind it, nobody
can ever prove a leaf under it, so the value it brought stays in the tree
output for good. No value is created, because the sums still balance, and
nothing else in the tree is affected: every other leaf stays payable. The Lean
theorem join_sound states it: a join fills a slot that was empty,
the output grows by the grafted sum, and every other position keeps its
contents.
Who pays
The covenant requires the new sum to be paid in, and leaves open who pays it. There are three ways to fund a cohort.
- The operator funds the graft.
- The operator supplies the whole sum from its own input. Members hold no on-chain coins; each buys their balance off-chain, over Lightning, after the graft confirms, or through a hash-locked voucher that the covenant enforces (see swaps). The operator carries the risk that a member never pays. This is the cheap case: under 1 vB per member for a cohort of a thousand.
- Each member funds their own leaf.
- Each member adds an input to the graft transaction and signs it, about 58 vB each. The transaction cannot be broadcast until every member has signed, so one absent member stalls the cohort until the operator rebuilds it without them. An unsigned graft leaves every member's coins where they were.
- Mixed.
- The operator funds some leaves and members fund others. This changes nothing for anyone, because each member's safety rests on their own path.
What a joiner checks before paying
A member who signs an input to the graft checks the unsigned transaction first (spec 9.2):
- Input 0 spends the current tree output, with the expected amount.
- The empty slot folds up its path to that output's root.
- Their own leaf folds up their path inside the subtree to the grafted node, with every sum recomputed.
- Output 0 commits to the root that the grafted node folds to, with the old amount plus the subtree's sum.
- Their leaf's fields are as agreed: amount, their own keys, the evictor and the lease.
- Their own inputs and change are as agreed.
They then sign with a signature that covers every output, so their input can only be spent into a transaction whose tree output contains their leaf. They do not need to check anyone else's leaf: by the conservation property, a co-member's bad leaf cannot make a good one unpayable.
A member who pays off-chain has no input to sign. They wait for the graft to confirm, check the same path against the confirmed root, and only then pay. Paying first means trusting the operator to graft.
Deeper
- Spec 7.3:
JOIN_handJOINK_K, and rule 4 on what is not checked. - Spec 9.2: the joining protocol and the joiner's checks.
- Spec 10.1: what the operator can and cannot do.
design/graft.md: the trust model, funding modes and costs, and a worked example of 256 members.formal/README.md:join_soundand the grounding hypothesis.