fix(dlv): the owner proves the settlement from the state it owns before it signs or folds - #761
Merged
Conversation
…re it signs or folds
Settlement blocker one. The owner fold (dlv.reconcile -> DlvOwnerApplyV2 ->
the ApplySettlement arm of advance) took input_amount and output_amount
verbatim from the trader's receipt. The receipt is the trader's witness of
what the trader committed; it says nothing about what the owner's reserves
pay. Composition re-simulates the route-commit HOP on the way to the
frontier, and the 0x0026 verifier re-simulates the trader's settle, but no
owner-side code ever ran the curve on the amounts the fold actually moved,
and advance never read the signed parent_binding at all. A trader could
commit a receipt above the curve and the owner's own reserves would fold it.
The core ApplySettlement arm is now the security boundary and is sufficient
by itself. From the head's own commitment outward:
vault-state leaf at parent_sequence (commits pair, fee, both reserves)
-> the mutation's pair + fee over the reserves this arm consumes must
reproduce that leaf; no leaf => nothing to consume, never synthesised
-> parent_binding (signed) == vault_state_commitment(parent_state), and
that state's vault, generation, reserves, pair, fee and owner equal
the leaves — the head holds no V_n, only the leaves V_n commits, so
CCB(V_n) rides the mutation as unsigned bytes tied by the signature
-> reserve_in is the leaf of the asset the trader PAID, by asset
identity, never a caller's "a"/"b" declaration
-> the ONE canonical constant_product_output over those reserves and
that fee must equal the proposed output exactly; no band
-> only then do the leaves move.
VaultReserveMutation::ApplySettlement gains parent_state: Vec<u8>.
dlv.reconcile mirrors the same derivation BEFORE signing — an early refusal,
not an authority: pair, fee, reserves and parent_binding now come from the
verified composition (composition retains each folded parent's state, so an
LP reconciling N generations back names the exact state that trade consumed),
never from the SQLite record and never from the receipt. The record read
for the fee is gone. The owner does not sign arithmetic it has not checked.
Dependent sweep: every owner-apply fixture in the tree folded an off-curve
literal (100 for 90 on 10 000/5 000; 1 000 for 970; 100 for 60 on 500/400;
100 for 60 on 100/100; a foreign receipt for 500 where the curve pays 453).
Each is re-rooted through the canonical function and names the head's own
parent state via a testing-gated DeviceState::parent_vault_state_for_tests.
Proven, each by a named test and an inverse-edit mutation cycle:
core — one unit above OR below the curve refused, the exact output advances;
pricing by the input asset's own reserve (b -> a), the pair-order price
refused for that direction; a fee the leaf never committed refused; a
parent_binding that is not the supplied state's commitment refused, and one
that commits a well-formed state the leaves do not hold refused; route — a
receipt one unit off the curve refused before signing with the root, both
leaves and the consume-once row untouched, and the curve-priced receipt
folds. CPTA enforcement is untouched at all three layers. Not in this cut:
R_econ admission for settle/owner-apply, ReleasePolicy semantics, close.
Boards: workspace 74 binaries, 3956 passed / 0 failed / 17 ignored (dsm lib 1686/0, dsm_sdk lib 1811/0/7 ignored), exit 0; node 11 binaries, 276/0, exit 0; make lint exit 0;
production safety PASS (pinned 1.98.0); ci_scan PASS.
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.
Summary
Settlement blocker one: the owner proves the exact transition from the state it owns before it signs or folds a settlement. Cut from merged
main(dbd1a56e, #760); no dependency on the source-map branch.What was wrong
dlv.reconcilecopiedinput_amount/output_amountfrom the trader's receipt into the signedDlvOwnerApplyV2and the coreApplySettlementarm moved the leaves by checked add/sub. The receipt witnesses what the trader committed, not what the owner's reserves pay. Composition re-simulates the route-commit hop on the way to the frontier (vault_state_composition.rs) and the 0x0026 verifier re-simulates the trader's settle (provenance.rs), but nothing on the owner's side ever ran the curve on the amounts the fold actually moved — andadvancenever read the signedparent_binding(every reference was a test literal). A receipt above the curve folded.The cut
ApplySettlementarm (advance) — the security boundary, sufficient aloneparent_sequencemust equalcompute_vault_smt_value(parent, reserves_digest(pair, fee, cur_a, cur_b))— the fee and reserves the curve runs on are the committed ones; no leaf ⇒ refused, never synthesised. (2)parent_binding(signed) must bevault_state_commitment(parent_state), and that state's vault, generation, both reserves, pair, fee and owner must equal the leaves — the head holds noV_n, soCCB(V_n)rides the mutation as unsigned bytes tied by the signature. (3)reserve_inis the leaf of the asset the trader paid;constant_product_output(input, reserve_in, reserve_out, fee)must equal the proposed output exactly. Typed refusal each; nothing moves.VaultReserveMutation::ApplySettlementparent_state: Vec<u8>dlv.reconcile— pre-sign mirror, early refusal, not authorityparent_bindingcome from the verified composition (which now retains each folded parent's state, so reconciling N generations back names the exact state that trade consumed); the SQLite-record fee read is deleted; same orientation rule, same canonical function, exact equality — refused beforesign_operation_sphincsfolded_parent_bindings: Vec<(u64, c_n)>→folded_parents: Vec<FoldedParent { generation, c_n, state }>DeviceState::parent_vault_state_for_tests#[cfg(any(test, feature = "testing"))]— describes the parent state the head itself holds, so fixtures name the real state they consumeCPTA enforcement is untouched at its three layers (route pre-flight, advance funnel, economic verifier). Not in this PR:
R_econadmission for settle/owner-apply,ReleasePolicysemantics, close/apply redesign.Dependent sweep — a finding, not a footnote
Every owner-apply fixture in the tree folded an off-curve literal: 100 for 90 on 10 000/5 000 (curve: 49); 1 000 for 970 (curve: 453); 100 for 60 on 500/400 (curve: 7); 100 for 60 on 100/100 (curve: 9); a foreign receipt claiming 500 where the curve pays 453 — and the consumed-generation route test reconciled that receipt as its winner. Each is re-rooted through the canonical function, with the known values pinned beside the derivation. That the whole suite was green on those numbers is the measure of the gap.
Proof
parent_bindingthat is not the supplied bytes' commitment refused; one committing a well-formed state the leaves do not hold refused; the real one advancesshasum -cOK each time; green aftermake lintexit 0;ci/production_safety_checks.shPASS (pinned 1.98.0, incl. TLA+);scripts/ci_scan.shPASS — all on tree stamp9788e485d2c7, identical before and after every run and after the mutation cycles