Skip to content

feat(dlv): the DLV-policy digest is derived from the signed vault state, never chosen - #760

Merged
cryptskii merged 1 commit into
mainfrom
feat/dlv-policy-digest-derived-from-signed-birth
Sep 3, 2026
Merged

feat(dlv): the DLV-policy digest is derived from the signed vault state, never chosen#760
cryptskii merged 1 commit into
mainfrom
feat/dlv-policy-digest-derived-from-signed-birth

Conversation

@cryptskii

Copy link
Copy Markdown
Collaborator

Summary

The DLV-policy authority cut, immediately before settlement. It establishes the vault's policy identity and authority; it does not yet make that policy govern behaviour, which is settlement's and close's job.

signed VaultStateV2
        ├── market_policy ── CPTA A + CPTA B      (token layer, independent authorities)
        ├── release_policy ─┐
        └── fee_policy ─────┴─ dlv_policy_digest  (DLV layer, one derived view)
                                  ├── DlvSpecV1.policy_digest
                                  ├── persisted vault record
                                  └── routing advertisement / route hop

No caller chooses that digest any more.

What was wrong

DlvSpecV1.policy_digest was thirty-two free bytes the LiquidityScreen asked a human to paste as a "policy anchor", labelled a CPTA anchor in the schema and the frontend, length-checked by dlv.create and compared to nothing — persisted, echoed into every advertisement and signed by traders into route hops. A token identity in the vault-policy slot, bound by no creator-signed object: parameters_hash, CCB(VaultStateV2) and the create operation's canonical bytes all omitted it. Meanwhile the signed vault state already committed a DLV-layer policy as members 8 and 9, unconnected to that field.

The derivation

dlv_policy_digest = BLAKE3("DSM/dlv-policy-digest", CCB(ReleasePolicy) || CCB(FeePolicy))

Member 7 (market_policy, the CPTA pair) is deliberately not an input; the core test proves two vaults over different pairs with the same release family and fee share a digest while their signed state commitments differ, and that the fee changes it. A new domain tag, because DSM/dlv-policy already names a different preimage (a SmartPolicy proto) and a domain must not cover two structures. No new CCB field, no change to c_n: anyone holding CCB(V_n) recomputes it.

Where it now binds

site change
dlv.create (AMM) derives the digest; refuses a supplied value that disagrees, by name; persists and echoes only the derived value; hands it to the draft before signing
LimboVault::parameters_hash and verify fold the digest, presence-tagged so absent and thirty-two zero bytes differ; the creator now signs the vault's policy identity, and a digest altered or dropped after signing fails verify
rehydrate_amm_vault re-derives from the leaf-proven fee and the beta release family; refuses any other row value (PolicyDigestNotDerived); FeePolicy::new also closes the fee-above-denominator gap the leaf pair never bounded
route.publishRoutingAdvertisement fills the advertised digest from the vault record and refuses a request naming any other — the previously unfenced advertised digest
schema, frontend, harness proto comments corrected and the never-implemented spec_id comment removed; the paste box, the policyDigest input and its test deleted; the Android SoFi harness sends empty on create and publish and its synthetic-digest helper is gone

Non-AMM DLVs have no DLV-layer policy object to derive from, so their supplied digest is kept — and is now at least creator-signed. Stated, not hidden.

Wipe boundary, recorded

verify recomputes parameters_hash from the vault's fields, so every pre-existing DLV's creator signature stops verifying. Recorded in CHANGELOG.md as a second beta wipe boundary: no migration, no re-signing, no synthesis, deliberately. Beta runs on dsm-testnet with no production user state.

Proof

check result
core: the derivation excludes the pair, distinguishes the fee green
core: a digest altered or dropped after signing fails verify green
create: chosen digest refused by name; derived value accepted; empty is born carrying it in the record and the signed vault, verify true green
rehydration: non-derived row refused, positive control rebuilds green
the two pre-existing create-then-rehydrate tests, re-pinned from the old pasted literal to the derived value; one now supplies it explicitly on create (a second positive control of the accept path) green
publish: chosen digest refused; empty advertises the record's; the record's value accepted green
mutation, per gate (create, rehydration, publish, verify fold) each reddens only its named test; inverse-edit restoration checksum-verified
frontend proto regenerated; type-check, lint and jest green
test names vs main +5, −0: one new test each in ccb, limbo_vault, dlv_routes, route_routes, vault_rehydration; amm_vault_records and vault_funding_routes unchanged in count (fixtures re-rooted to the derived digest). Frontend LiquidityScreen 9 → 8: the test of the deleted paste box deleted with its subject
boards workspace 74 binaries, 3951 passed / 0 failed / 17 ignored (dsm lib 1682/0, dsm_sdk lib 1810/0/7 ignored), exit 0; node 12 binaries, 276/0, exit 0; root make lint exit 0; ci/production_safety_checks.sh PASS (pinned 1.98.0, incl. TLA+); scripts/ci_scan.sh PASS — all on tree stamp 2e4346317b3a, identical before and after every run

Not in this PR

Behavioural enforcement of the committed DLV policy. ReleasePolicy is signed but read by nothing, and close semantics are hardcoded in advance; making the committed policy govern behaviour is the close/apply work. The owner-fold curve check is settlement blocker one and starts next.

…te, never chosen

Two independent policy layers govern an AMM vault and both apply to every
movement of value: the token (CPTA) policy of each asset, and the vault's
own DLV policy. The creator-signed VaultStateV2 already commits all three
— member 7 market_policy is the CPTA pair, members 8 and 9 release_policy
and fee_policy are the DLV layer. What it did not have was any link to
DlvSpecV1.policy_digest: thirty-two free bytes the UI asked a human to
paste, labelled a "CPTA anchor" in the schema, length-checked by
dlv.create and compared to nothing. A token identity in the vault-policy
slot, bound by nothing.

The digest is now a deterministic VIEW of the DLV layer:

    dlv_policy_digest = BLAKE3(DSM/dlv-policy-digest,
                               CCB(ReleasePolicy) || CCB(FeePolicy))

Member 7 deliberately does not enter it: the pair's CPTA commits stay
independent authorities over the assets. No new CCB field, no c_n change
— the value is recomputable by anyone holding CCB(V_n).

- dlv.create derives it for an AMM vault, refuses a supplied value that
  disagrees, persists and echoes only the derived value, and hands it to
  the draft BEFORE signing.
- LimboVault::parameters_hash folds it (presence-tagged, so "absent" and
  32 zero bytes differ), and verify recomputes the same fold: the creator
  now signs the vault's policy identity. Non-AMM vaults have no DLV-layer
  object to derive from; their supplied digest is kept and is now at least
  creator-signed.
- rehydrate_amm_vault re-derives it from the leaf-proven fee and the beta
  release family and refuses a row carrying anything else
  (PolicyDigestNotDerived). FeePolicy::new also refuses a fee at or above
  the denominator, which the leaf pair never bounded.
- route.publishRoutingAdvertisement fills the advertised digest from the
  vault record and refuses a request naming any other; the trader-signed
  hop therefore repeats a value the owner signed at birth.
- The schema comments that called this a CPTA anchor are corrected; the
  never-implemented spec_id derivation comment is gone; the LiquidityScreen
  paste box and the frontend's policyDigest input are deleted; the
  Android SoFi harness sends empty on both create and publish.

This establishes DLV-policy IDENTITY and AUTHORITY. It is not yet
behavioural enforcement: ReleasePolicy is committed but read by nothing,
and close semantics remain hardcoded in advance. Settlement and close make
the committed policy govern behaviour; that work starts next.

WIPE BOUNDARY (CHANGELOG): every pre-existing DLV's creator signature was
made over the old parameters preimage and stops verifying. No migration,
no re-signing, deliberately.

Proven: the derivation excludes the pair and distinguishes the fee (core
test); a digest altered or dropped after signing fails verify; an AMM
create with a chosen digest is refused by name, with the derived value
supplied is accepted, and with the field empty is born carrying it in the
record and in the signed vault; a record with a non-derived digest fails
rehydration; a publish with a chosen digest is refused and an empty one
advertises the record's. The two pre-existing create-then-rehydrate
tests are re-pinned from the old pasted literal to the derived value;
one now supplies it explicitly on create, a second positive control of
the accept path. Each gate mutation-proven per arm with inverse-edit
restoration. Frontend: type-check, lint and jest green; the
test of the deleted paste box is deleted with it.

Boards: workspace 74 binaries, 3951 passed / 0 failed / 17 ignored (dsm lib 1682/0, dsm_sdk lib 1810/0/7 ignored), exit 0; node 12 binaries, 276/0, exit 0; make lint exit 0;
production safety PASS (pinned 1.98.0); ci_scan PASS.
@cryptskii
cryptskii merged commit dbd1a56 into main Sep 3, 2026
18 checks passed
@cryptskii
cryptskii deleted the feat/dlv-policy-digest-derived-from-signed-birth branch September 3, 2026 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant