Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ python3 -m venv .venv && .venv/bin/pip install -e ".[dev,oracles]"
.venv/bin/bitlisp-asm [text] # text to serialized bytecode hex
.venv/bin/bitlisp-disasm [hex] # serialized bytecode hex to text
.venv/bin/bitlisp-compile [source] # v0 language source to bytecode hex
.venv/bin/bitlisp-commit [program] # leaf script, tapleaf, merkle root, control block, scriptPubKey
.venv/bin/python tools/run_vectors.py # full vector corpus
.venv/bin/python tools/diff_clvm.py --count 10000 --seed 1 # diff harness
.venv/bin/python tools/mutate.py --tests # mutation pass, survivors need triage
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ ci/lint/lint.sh

## Using the tools

The editable install adds seven console scripts:
The editable install adds eight console scripts:

```
.venv/bin/bitlisp [tx.json] # REPL with stepping debugger
Expand All @@ -54,11 +54,14 @@ The editable install adds seven console scripts:
.venv/bin/bitlisp-compile [source] # v0 language source to bytecode hex
.venv/bin/bitlisp-curry [hex] --arg <value> # fix values into a program
.venv/bin/bitlisp-uncurry [hex] # split a curried program back out
.venv/bin/bitlisp-commit [program] [--sibling <hex>] # leaf, root, control block, scriptPubKey
```

`bitlisp-run` reports the verdict, the emitted conditions, and the
cost for one spend, exiting 0 on a valid spend, 1 on an invalid
one, and 2 on input that could not be used. The converters read
one, and 2 on input that could not be used. `bitlisp-commit` prints
what an output committing to a program commits to, and the control
block a spend of it carries. The converters read
stdin when no argument is given and compose in pipes. The REPL
loads the same transaction context, keeps a constants scratch
space, and drives the debugger with `step`, `next`, `cont`, and
Expand Down
53 changes: 43 additions & 10 deletions docs/commitment-record.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,17 @@ the trigger.
table is exhaustive over those tags and gains a row with any new
one, the check that keeps the argument true. TapSighash is base
consensus's key-path digest, computed by no BitLisp rule, and
its first byte is `0xf4` regardless. The vector pinning the
table lands with the reference witness layer.
its first byte is `0xf4` regardless. The table is pinned by a
test rather than the vector this entry first promised (decision
by Evan, 2026-09-06, steelmanned both ways): the table fits no
suite's case shape, and a test can state the argument as well as
the values, recomputing every row from the tag strings and
checking that the rows are exactly the tags in use, every tagged
digest passing through one function. Writing that test found the
challenge row wrong:
the table had named the tag `BIP340/challenge` with first byte
`0x07`, and BIP340 spells it `BIP0340/challenge`, first byte
`0x7b`. Corrected the same day, the argument unchanged.

4. **The witness is four elements, and an annex only under a self
assert.** RATIFIED (decision by Evan, 2026-09-06, revising the
Expand Down Expand Up @@ -312,11 +321,35 @@ the trigger.
asset token study.
- Relay policy for the leaf version: today's policy discourages
unknown leaf versions, a deployment question for Phase 6.
- The commitment-hash utility: a command printing a leaf hash, a
merkle root, and a scriptPubKey from program sources, lands with
the reference witness layer now that the scheme fixes its output.
- Vectors owed to the reference witness layer: every stage 1 and 2
failure mode including the element bound at and above 10,000
bytes, the digest-domain table, the two programs spec section
3.3 names, the atom `1` and nil, and the annex rule in both
directions with the ASSERT_MY_ANNEX cases.

## 5. The reference witness layer

Landed 2026-09-06, the PR after the scheme. `python/bitlisp/`
gained `commitment.py` (the tree hash, the tagged hashes, the
control block) and `spend.py` (the stages of spec section 4 for one
input), the vector corpus a `spend` suite, and the front end
`bitlisp-commit`, the commitment-hash utility queued 2026-08-14.
Three choices made there, none a change to the scheme:

- Base consensus's control block checks are run by the reference
as a precondition and reported outside the error taxonomy
(`BaseConsensusError`), never as a code a vector could pin: the
spec says a spend failing them never reaches it, and a reference
that read the triple unchecked would call spends valid that base
consensus rejects. A vector whose witness fails them is
malformed.
- The annex admission rule is one function run twice on the
reference path, at stage 5 by the spend entry and first in
transaction validation, because the validation vectors and the
runner assemble transaction views without the spend entry and
the view's invariant must hold for them too. It lives with the
per-input stages, and transaction validation imports it, so the
dependency runs from the later stage to the earlier one.
- The witness form of `bitlisp-run` and the REPL is deferred to
unit 9's first PR, where the vault is the first consumer.

Writing the vectors found spec section 3.3 wrong about the nil
program (its empty condition list is valid under CONDITIONS.md and
the corpus, so the nil leaf is spendable by anyone) and the digest
table wrong about the challenge tag (decision 3). Both corrected
in their own spec commits.
8 changes: 5 additions & 3 deletions docs/condition-record.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Section 4 registers the rules that have no external reference at all.
| C22 | signature-condition cost | 1,200,000 (AGG_SIG_COST) per occurrence in every regime, pricing one BLS pairing toward the bundle aggregate | 1,300,000 (CONDITION_SIG_ASSERT_COST), equal to the VM's SECP_VERIFY_COST, PROVISIONAL | Both layers of BitLisp price the same BIP340 verification, so the condition constant ties to the operator constant rather than to Chia's BLS figure: one Phase 4 measurement settles both, and a program can never buy the same verification cheaper in one layer than the other. Chia's magnitude corroborates the range, their pairing being work of the same order. Ratified 2026-08-09, decision 25. | `conditions/costs.json` signature cases |
| C23 | execution-identity addressing | none: puzzle hash is both the coin's script commitment and the executing program, one field carries both meanings | the specifier table composes the executing leaf's tapleaf hash (bit 3) and the spending path's merkle root (bit 4) onto the prevout rows, commitment values 0 to 31, the mode packed as assurer times 32 plus requirer, both fields validator-filled from the control block. The pair itself is renamed ASSURE and REQUIRE (decision 27) | Taproot splits Chia's one identity into two, and the C9 mapping carried only the script commitment, so program-to-program trust had no faithful addressing field. Internal-key addressing declined as attacker-satisfiable (no possession proof in BIP341). Grafted-leaf and moved-root caveats recorded in decision 26 and in rule 3's author guidance. Ratified 2026-08-18, decision 26, landed 2026-08-20. | `validation/messages.json` identity cases, `conditions/messages.json` composed-mode cases |
| C24 | the cheap identity assert | none: ASSERT_MY_PUZZLEHASH reads the one identity field, and no derive-versus-read split exists because a puzzle hash is a plain tree hash | ASSERT_MY_TAPTREE, a self assert over the control block's internal key and merkle root at the generic cost, the transaction view's identity widened to a triple. ASSERT_MY_TAPROOT, the derivation assert it subsumes, removed once the puzzle rework landed (decision 29) | Taproot's identity is a tweak preimage the control block reveals and base consensus authenticates, so the assert reads it at 200 where the derivation assert re-derives it at a point multiplication, the largest single cost in both landed puzzles. Internal key read by self asserts only: decision 26's attacker-satisfiable objection concerns a counterpart's key, not the spending input's own. Ratified 2026-08-22, decision 28. | `validation/self-asserts.json` taptree cases, `conditions/self-asserts.json` and `conditions/costs.json` taptree cases |
| C25 | the annex assert | none: Chia has no annex | ASSERT_MY_ANNEX, a self assert over the BIP341 `sha_annex` digest of the spending input's annex at the generic cost, the annex admitted on a BitLisp spend only under it | Bitcoin's annex is a witness element no operator reads and no BitLisp signature digest commits to, so an ignored annex is third-party malleability on every spend, and a rejected one closes the upgrade path under this leaf version, since a soft fork cannot loosen. Admitting it only under a self assert commits the spender to it, keeps keyless paths safe by default, and leaves the door open. Ratified 2026-09-06, decision 30. | owed to the reference witness layer: `validation/self-asserts.json` and `conditions/self-asserts.json` annex cases |
| C25 | the annex assert | none: Chia has no annex | ASSERT_MY_ANNEX, a self assert over the BIP341 `sha_annex` digest of the spending input's annex at the generic cost, the annex admitted on a BitLisp spend only under it | Bitcoin's annex is a witness element no operator reads and no BitLisp signature digest commits to, so an ignored annex is third-party malleability on every spend, and a rejected one closes the upgrade path under this leaf version, since a soft fork cannot loosen. Admitting it only under a self assert commits the spender to it, keeps keyless paths safe by default, and leaves the door open. Ratified 2026-09-06, decision 30. | landed 2026-09-06 with the reference witness layer: `conditions/self-asserts.json` parses and rejections, `validation/self-asserts.json` both directions, `spend/programs.json` the annex through the witness |

## 2. Reference provenance

Expand Down Expand Up @@ -1642,8 +1642,10 @@ Section 4 registers the rules that have no external reference at all.
the same value. 0x39 follows the taptree assert, 0x34 to 0x37
staying the visible gap. Error `unsatisfied_annex_assert` for
the condition and `unasserted_annex` for the witness rule,
each its own vector, both owed to the reference witness layer
with the implementation.
each its own vector, both landed 2026-09-06 with the reference
witness layer: the assert in `conditions/self-asserts.json` and
`validation/self-asserts.json`, the witness rule in
`spend/programs.json` and `spend/witness.json`.

## 4. Novel-layer register

Expand Down
1 change: 1 addition & 0 deletions docs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ A term enters this table in the same PR that introduces it.
| assurer half, requirer half | no direct equivalent | the mode's sender and receiver bit fields | the two commitment values a mode packs, assurer times 32 plus requirer since the execution-identity widening (condition-record decision 26) |
| execution identity | tapleaf hash, merkle root, and internal key of the script-path spend | puzzle hash (one field carries both meanings) | the triple validator-filled from the control block: the tapleaf hash and merkle root are specifier fields addressing the executing program and its tree, the internal key is read by self asserts only (condition-record decisions 26 to 28, divergences C23 and C24) |
| leaf version | BIP341 term, the control block's first byte with its low bit cleared | none: a coin's puzzle is not versioned | `BITLISP_LEAF_VERSION = 0xd0`, provisional until deployment, the byte that makes a script-path spend a BitLisp spend (spec/SPEC.md section 2.1), glossed 2026-09-06 with the commitment scheme |
| control block | BIP341 term, the last witness element of a script-path spend: leaf version and parity byte, internal key, merkle path | none: a coin spend reveals the puzzle and nothing about a tree | read by the reference for the execution identity and checked against the spent scriptPubKey as base consensus checks it (spec/SPEC.md section 2.3), printed by `bitlisp-commit`, glossed 2026-09-06 |
| leaf script | BIP341 term, the script a leaf commits to and a spend reveals | puzzle hash, as the value a coin commits to | the 32-byte tree hash of the committed program, never executed, the program itself a separate witness element the validator checks against it (spec/SPEC.md section 2.2), glossed 2026-09-06 |
| per-path leaf | one tapleaf per spending condition, the taproot idiom | none: a puzzle's paths share one puzzle hash | a spend path written as its own curried program in its own leaf, so a spend reveals the executed path and 32 bytes of control block per tree level (spec/SPEC.md section 2.3, commitment-record decisions 7 and 8), glossed 2026-09-06 |
| ANNOUNCE, ASSERT_ANNOUNCEMENT | no direct equivalent | the four announcement conditions (coin and puzzle, create and assert) | landed 2026-08-07, the unaddressed pair, namespace first-class, announcer precision chosen by the assert (condition-record decisions 10 and 16) |
Expand Down
36 changes: 32 additions & 4 deletions docs/lang/repl.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
The interactive front end over the reference VM, the spend runner,
the compiler, and the debug machine, all in `python/bitlisp_tools/`.
This is tooling, not consensus. The REPL is the `bitlisp` command,
and five one-shot commands ship beside it: `bitlisp-asm` assembles
and six one-shot commands ship beside it: `bitlisp-asm` assembles
text to serialized bytecode hex, `bitlisp-disasm` renders hex back
as text, `bitlisp-compile` compiles a source program to serialized
bytecode hex, and `bitlisp-curry` and `bitlisp-uncurry` fix values
into a program and split them back out. The text syntax is defined
bytecode hex, `bitlisp-curry` and `bitlisp-uncurry` fix values
into a program and split them back out, and `bitlisp-commit` prints
what an output committing to a program commits to. The text syntax is defined
in `syntax.md`, the authoring language in `language.md`, and the
currying and tree-hash surfaces in `curry.md`.

Expand Down Expand Up @@ -198,9 +199,11 @@ bitlisp-compile [--symbols <path>] [-I <path>]... [-T] [<file-or-literal>]
bitlisp-curry [-a <sexpr>]... [-T] [<file-or-literal>]
fix values into a program
bitlisp-uncurry [<file-or-literal>] split a curried program back
bitlisp-commit [--hex] [-k <hex>] [-s <hex>]... [<file-or-literal>]
leaf, root, control block, scriptPubKey
```

All five take one argument, a file when one exists at that path
All six take one argument, a file when one exists at that path
and the literal otherwise, the `bitlisp-run` convention, or read
stdin when the argument is omitted, so they compose in pipelines:

Expand All @@ -223,6 +226,31 @@ into a hex program, and `bitlisp-uncurry` prints the inner
program's hex and then one fixed value per line as text. The
curried shape and its contract are defined in `curry.md`.

`bitlisp-commit` takes a program as text, or as serialized hex
under `--hex`, and prints six lines: the leaf script (the program's
tree hash), the tapleaf hash under BitLisp's leaf version, the
merkle root, the internal key, the control block a spend of the
leaf carries, and the taproot scriptPubKey of the output. The leaf
sits alone in its tree unless `-s` names the 32-byte hashes beside
it, leaf upward, and the internal key is the BIP341
nothing-up-my-sleeve point unless `-k` names one, so the default
output has no key path. The control block's first byte is the leaf
version `0xd0` with the output key's parity in its low bit, so it
reads `d0` or `d1` depending on the key and the tree. It exits 2
when the program does not parse, a hash or key is not 32 bytes of
hex, there are more than 128 siblings, or the key does not lift to
a curve point.

```
$ bitlisp-commit "1"
leaf script: 9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2
tapleaf: dde54461ef9453a9d235b60142c77f6680d0243ec4a02d206750059e8ea08b95
merkle root: dde54461ef9453a9d235b60142c77f6680d0243ec4a02d206750059e8ea08b95
internal key: 50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0
control block: d050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0
scriptPubKey: 5120864ac98761331f672c01124a5a08e77402420ff7e471bd38cf5ce354a104a194
```

`bitlisp-asm`, `bitlisp-disasm`, `bitlisp-compile`, and
`bitlisp-curry` take `-T`, printing the program's tree hash
instead of their usual output. The digest names the tree, not the
Expand Down
55 changes: 55 additions & 0 deletions docs/mutation-triage.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,61 @@ no signer reaches without about 2^128 work, `r` at or above `P`
likewise, and a tweak scalar of exactly `N` is a hash preimage. The
group order itself moved by one changes only those checks.

## Pass of 2026-09-06

Run on the witness-layer branch over the seven modules it touched
(`commitment` and `spend` new, `conditions`, `validation`, `tx`,
`errors`, and `secp256k1` changed), the corpus at 1,199 cases before
this pass's additions, with `--tests`.

| module | mutants | killed | crashed | survived | timeout |
| --- | --- | --- | --- | --- | --- |
| commitment | 100 | 48 | 24 | 28 | 0 |
| conditions | 397 | 315 | 41 | 41 | 0 |
| errors | 2 | 0 | 1 | 1 | 0 |
| secp256k1 | 153 | 105 | 26 | 20 | 2 |
| spend | 45 | 36 | 2 | 7 | 0 |
| tx | 178 | 100 | 14 | 64 | 0 |
| validation | 152 | 128 | 17 | 7 | 0 |
| total | 1,027 | 732 | 125 | 168 | 2 |

Every survivor in the two new modules is killed by the pytest
suite, 35 of 35. Three things came out of reading them.

One gap, one vector: the compact-size prefix of the annex digest
turns three bytes wide at 253, and no annex case sat on either side
of the boundary, so `n < 0xFD` moved by one survived the corpus.
`spend/programs.json` gains `annex_of_252_bytes` and
`annex_of_253_bytes` (SPEC.md section 3.4). The wider forms at
65,536 and 2^32 bytes lie above `MAX_WITNESS_ELEMENT_SIZE` and no
element reaches them: beyond reach.

One dead function: `_apply_tweak` in `secp256k1.py` survived under
the corpus because nothing in the consensus path called it once
`taproot_output_point` landed. Removed, its tests moved onto
`_tweaked_point`.

One new survivor class, **outside BitLisp by design**: the control
block length bound, the leaf version check, the key lift, the tweak
check, and the parity check in `commitment.py` and `spend.py`, and
the two-element threshold of the annex split. A witness that fails
them is refused by base consensus or is not a BitLisp spend, SPEC.md
assigns it no outcome, and the spend suite treats such a case as
malformed rather than pinning it, so no vector can kill these
mutants. The pytest suite kills every one (the BIP341 differential
and the base-consensus tests in `test_spend.py`). In the same file,
the sibling sort's `<` to `<=` is equivalent: two equal hashes
concatenate to the same bytes in either order.

The other modules' survivors are the accepted classes above. The
one new survivor in `conditions.py` is the frozen flag of
`AssertMyAnnex` (equivalent), the three new in `tx.py` are the
`annex_hash` width check (model precondition), and the five new in
`secp256k1.py` are the width guard and value defects of
`taproot_output_point` (unreachable guard, beyond reach), the same
sites `taproot_output_key` carried before it. `validation.py` stays
at seven.

## Re-running

A pass belongs with any change to `python/bitlisp/` that adds a
Expand Down
5 changes: 3 additions & 2 deletions docs/vm-record.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,9 @@ marked open name the phase that owes the answer.
separation. The owed statement is that context argument, written
down and pinned. Written down 2026-09-06: `spec/SPEC.md`
section 2.4 states the disjointness as a first-byte fact over
every tag in use (commitment-record decision 3), and the vector
lands with the reference witness layer.
every tag in use (commitment-record decision 3), pinned
2026-09-06 by the test that recomputes the table from the tags
the implementation hashes under.
8. **Peak memory and evaluation order.** OPEN, owed with the Phase 4
budget mapping (recorded 2026-08-16 from the public record in
§8.5 of the evaluation doc). CLVM costing charges total
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ bitlisp-disasm = "bitlisp_tools.cli:disasm_main"
bitlisp-compile = "bitlisp_tools.cli:compile_main"
bitlisp-curry = "bitlisp_tools.cli:curry_main"
bitlisp-uncurry = "bitlisp_tools.cli:uncurry_main"
bitlisp-commit = "bitlisp_tools.cli:commit_main"

[tool.setuptools]
package-dir = {"" = "python"}
Expand Down
Loading