Unit 7: the vault benchmark puzzle - #57
Merged
Merged
Conversation
The unit 7 bullet records the dated decisions: vault ahead of the payment pool, core semantics matching BIP-345 in one PR, the keyless leader/follower consolidation path over the message ledger (the evaluation doc's section 3.2 benchmark), and the per-instance recovery posture. The dispatch helpers naming a reconstructed root once are recorded as the pre-registered assign trigger firing. docs/puzzles/vault.md states the state machine, both programs' parameters and paths with their emitted conditions, the BIP-345 correspondence table with recorded divergences (literal recovery scriptPubKey, no merged batch triggers under rule 1's exact-content counting, the identity stand-in pending the Phase 4 commitment scheme), the consolidation construction with its adversarial analysis and the monotonicity argument, and the worked instance with the pinned mod hashes. Glossary rows for the vault vocabulary land with the terms per the terminology rule.
The first benchmark puzzle, written in the v0 language: plain functions, the fixed forms, and the unit 6b surfaces. vault.bl carries four paths (trigger, recovery, consolidation follower and leader), triggered.bl two (withdrawal, recovery), with the shared recovery path and the triggered-root derivation in vault-shared.blib. puzzles/lib/curry-hash.blib is the generic identity helper: it recomputes a curried program's tree hash from the mod hash and the fixed values' digests, the digest bitlisp-curry -T prints, so every path can bind itself to its coin through ASSERT_MY_TAPROOT. This establishes the puzzles/ source layout and the lib/ include directory for the remaining unit 7 puzzles. Semantics authorized by docs/puzzles/vault.md. Conditions consumed: CREATE_OUTPUT, CREATE_OUTPUT_TAPROOT, ASSERT_MY_TAPROOT, ASSERT_MY_SCRIPTPUBKEY, ASSERT_MY_AMOUNT, ASSERT_SIG_MY_OUTPOINT, ASSERT_SEQUENCE_HEIGHT, SEAL_OUTPUTS, SEND_MESSAGE, RECEIVE_MESSAGE (CONDITIONS.md section 2). BIP-345 defines the core lifecycle the vault reproduces, per the correspondence table in the design doc.
vault-programs.json (suite vm) pins one compiled-and-curried representative per spend path plus the in-program guard failures, per VM.md sections 3 and 4, replayed byte-exact by the corpus runner and round-tripped by the converter sweep. vault-core.json pins the condition-level lifecycle under VALIDATION.md rules 1, 2, and 8: triggers with and without revault, the signature bindings (bad signature, replay onto another outpoint, redirected target caught by the message binding), sequence maturity and version gating, seal violations and the open input side, recovery in both postures from both states, rule 1's exact-content counting under batched recovery (two slots pass, one merged slot fails), and the program-on-wrong-coin boundary. vault-consolidation.json pins the message-ledger construction under VALIDATION.md rules 1 and 3, the obligation 1 adversarial surface from the evaluation doc's section 3.2: the happy paths including equal-amount followers and two disjoint groups composing in one transaction, and one theft vector per attack shape (omitted, phantom, understated, overstated, and under-received followers, a hostile leader at a foreign scriptPubKey, a hostile extra sender, and the underpaid or missing merged output). Every case was executed against the validator at generation time.
The source-to-bytes link: both mod tree hashes pinned as literals, the in-program curried-hash reconstruction checked against the curry tooling and uncurry read-back, every spend path run through the single-spend runner, and every failure mode asserted by exact error code live, including the parse-stage rejection of a negative listed follower amount that would otherwise deflate the consolidation sum before rule 3 runs. The lifecycle test chains vault, trigger, and withdrawal transactions by real txids, exercises the delay boundary, the open input side of the seal against a grafted output, and recovery from both states. Drift guards recompute the pinned vector programs and the vector condition lists from source, so puzzle source and corpus cannot drift apart silently.
… guard Ten findings from the pre-merge review, all verified live, plus three smaller cleanups. Puzzle guards (mod hashes re-pinned, vectors regenerated and re-verified case by case). Every path of both programs now checks the fixed values' domains first: authorization key 32 bytes, recovery key nil or 32, delay 0 to 65535, target hash 32 bytes. Without this a 33-byte recovery key curried into an instance left every path working except recovery, which failed forever at condition parse, and a delay past 65535 triggered fine and then bricked the withdrawal path, both reproduced end to end. The trigger additionally rejects a non-positive trigger amount, a negative revault amount, and a malformed target, closing the signed-solution shapes that minted an oversized or worthless triggered coin. Corrected claims. The identity section no longer says a wrong curry is unspendable, never theft: the taproot assert binds program to coin and cannot check values against intent, stated with the wallet's verification duty. The consolidation monotonicity claim is withdrawn: rule 1 leaves unclaimed slots unconstrained, so funded dust at the vault scriptPubKey can always appear, and the accurate statements are that conflicting consolidations still merge what they consume and that no keyless path splits value. The per-group reading of the section 3.2 benchmark predicate is now stated explicitly as the composition-compatible reading. Vectors. New pins: negative and non-minimal listed follower amounts (bad_condition_arg at parse, the sum-deflation hole), the two-leaders double-claim (unbalanced_message), the stale-signature redirected target (unsatisfied_sig_assert where the output claim alone would pass), the malformed-instance and amount-guard failures as vm cases. Spec citations unchanged: VALIDATION.md rules 1 and 3 and the CONDITIONS.md argument domains. Tests. The identity test now cross-checks three independent computations (tree_hash, a spec-rule reimplementation, the program's own reconstruction) and checks uncurry's inner program, replacing an assertion that compared tree_hash to itself. The validation drift guard is a full closure: every conditions field in both validation files recomputed, set-equal in both directions. Guard tests cover every new rejection. Ruff findings fixed, solution_items replaced by the library's iter_proper_list. Docs and repo. Glossary row for mod hash. Redundant include dropped from triggered.bl. Stray probe artifact removed.
…delay-encoding guards, signing-domain tags Four findings from Evan's review of the fold-in commit. The same-target withdrawal merge is a real divergence with a consequence and the doc now says so instead of calling it batching. SEAL_OUTPUTS drops the input-side commitment CTV's template hash carries, the withdrawal path is keyless, so two matured triggered coins with one target hash satisfy a single committed output set together and the second coin's value burns to fees, profitably for a miner. CTV's input-count commitment is the known half-spend protection and this construction does not have it. The divergence entry states the attack, the wallet rule (never reuse a target), and flags a count-committing seal variant for a spec decision outside this unit. The merge transaction is pinned as the valid vector same_target_withdrawals_merge_second_burns and a runner test, and the vault value never pays fees claim now carries the recorded exception. RECOVERY_SPK joins the domain guards in both programs, 1 to 10000 bytes. It was the one curried value without a guard and its failure is the guard block's own stated worst case: nil is the legitimate spelling for the adjacent RECOVERY_KEY, and a nil recovery script triggered, revaulted, and consolidated perfectly while only the panic path was dead. DELAY must be minimally encoded, guarded by (= DELAY (+ DELAY 0)), byte-exact equality against the normalizing sum. A padded encoding read as the same number in the numeric guards but ASSERT_SEQUENCE_HEIGHT rejects it, bricking withdrawal alone, exactly the asymmetric-path failure the guards exist to remove. Probed live: = is byte-exact, + normalizes. Both signed message lists now open with a signing-domain tag, 1 for trigger and 2 for recovery, so the two authorizations live in disjoint digest domains even when one key is curried into both roles, instead of resting on the lists' shapes differing. Mod hashes re-pinned (vault 15884715, triggered 214b0347), vectors regenerated and executed case by case, new vm pins for the nil recovery script and padded delay instances. 784 tests, 1061 vector cases.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Unit 7, first puzzle: the vault, written in the v0 language as the acceptance artifact, resequenced ahead of the payment pool (decision 2026-08-16). Core semantics match BIP-345 (OP_VAULT and OP_VAULT_RECOVER) reproduced with the condition vocabulary, plus the keyless leader/follower consolidation path over the message ledger, the evaluation doc's section 3.2 benchmark, with its obligation 1 theft vectors. Tooling and vectors only: nothing in
spec/orpython/bitlisp/changes.What changed
puzzles/is new, establishing the source layout for the remaining benchmark puzzles:puzzles/lib/curry-hash.blib(generic curried-identity helpers),puzzles/vault/vault.bl(four paths),puzzles/vault/triggered.bl(two paths),puzzles/vault/vault-shared.blib.docs/puzzles/vault.md: state machine, per-path conditions, the BIP-345 correspondence table with recorded divergences, the consolidation construction with its adversarial analysis and monotonicity argument.vectors/vm/vault-programs.json(17 compiled representatives),vectors/validation/vault-core.json(23 lifecycle cases),vectors/validation/vault-consolidation.json(15 cases, one theft vector per attack shape).python/tests/test_vault_puzzles.py(20 tests), including drift guards that recompile the sources and byte-compare them against the pinned vectors.docs/execution-plan.mdunit 7 bullet updated with the dated decisions, and glossary rows for the new vocabulary.Decisions recorded (Evan, 2026-08-16)
RECOVERY_KEYnil is keyless, a 32-byte key demands an outpoint-bound signature.assigntrigger firing, recorded in the plan bullet for the unit 7 ledger note.Authorizing sections
curry, identity checked with-T, anduncurryread-back is pinned in a test.docs/puzzles/vault.md.Read the commits in this order
docs: the vault benchmark design, unit 7 resequenced vault-firstpuzzles: the vault and triggered programsvectors: pin the vault lifecycle and the consolidation theft casestests: compile-and-run coverage for the vault puzzlesreview fold-ins: domain guards, honest identity claims, closure drift guardreview fold-ins: target-reuse divergence stated, recovery script and delay-encoding guards, signing-domain tagsVerify independently
Every vector case was executed against the validator at generation time, and the adversarial cases (signature replay, redirected and stale-signature targets, the ten consolidation theft shapes including the encoding-level sum-deflation attempts and the two-leaders double-claim, the merged recovery slot) were probed live: the test suite reproduces each with its exact error code.
Two review rounds are folded in as the final commits. The first (ten verified findings): in-program domain guards on the curried values so a malformed instance fails on first spend instead of quietly losing one path, trigger amount guards against misbehaving signers, corrected identity and monotonicity claims in the design doc, the per-group benchmark reading stated explicitly, four new adversarial vector pins, a de-tautologized identity test, and a full-closure drift guard over every conditions field in the validation vectors.
The second round (four findings from Evan's review): the same-target withdrawal merge is stated as a divergence with a consequence rather than as batching, pinned by the valid vector
same_target_withdrawals_merge_second_burns, with the wallet rule (never reuse a target hash) and a count-committing seal variant flagged for a spec decision outside this unit.RECOVERY_SPKand minimalDELAYencoding joined the domain guards in both programs, closing the last quiet single-path failure modes. Both signed messages now open with a signing-domain tag so trigger and recovery authorizations stay in disjoint domains even under a shared key.