Skip to content

feat(economic): class 0x0029 AuthorizedIssuanceV1 — the missing source predicate - #748

Merged
cryptskii merged 1 commit into
mainfrom
feat/0x0029-authorized-issuance-v1
Aug 30, 2026
Merged

feat(economic): class 0x0029 AuthorizedIssuanceV1 — the missing source predicate#748
cryptskii merged 1 commit into
mainfrom
feat/0x0029-authorized-issuance-v1

Conversation

@cryptskii

Copy link
Copy Markdown
Collaborator

Class 0x0029 is the source predicate that was missing. Before this, a
user-created asset had no way for its supply to enter a validated lineage:
every credit needs a funded source, and the only arms that existed sourced
value that already existed somewhere else. token.mint filled that gap by
signing its own authorization with the caller's device key — which is not an
authorization, it is a caller asserting one.

0x0029 replaces that with a policy-authorized issuance the verifier reruns
from scratch. There is no caller-supplied policy_passed.

What proves an issuance

The 0x0023 arm is a nine-step conjunction over the AUTHENTICATED operation,
never the descriptor:

  1. the verified operation is a Mint/CreateToken for non-zero units
  2. the evidence bundle is fetched by exact content address and re-hashed
  3. the bundle's carried policy bytes re-hash to the OPERATION's policy_commit
  4. the authorization body states this issuance, lineage, position and op digest
  5. the committed policy parses and permits THIS issuance
  6. k distinct signers, each matched against the POLICY-NAMED set BEFORE its
    signature is verified
  7. the credit's asset and amount equal what the authorization names

The signatures cover the operation's own digest, so they cannot live inside
the operation. A Mint carrying a non-empty proof_of_authorization is
refused outright, keeping the evidence bundle the only channel rather than
leaving an ignored one beside it.

What the V1 matrix refuses rather than ignores

A finite SupplyCap is refused: its circulating-supply input is derived
per-device, so N authorized devices would each mint to the ceiling. An
authority whose own signer set cannot meet its threshold is refused. A policy
with mint_burn_enabled == false is refused. A non-empty
allowlist_device_ids binds the DEVICE RECEIVING the credit — the
authenticated DevID, never an authorized_by byte string the caller chose.

The matrix reads the committed v3 blob, the exact form policy_commit
commits. There is deliberately no second matrix over the richer
PolicyCondition model: the blob cannot encode those conditions, so a second
one would be an unreachable half that drifts unnoticed.

Capped creation is refused at the route

A token policy is immutable once anchored. Anchoring one whose positive supply
can never enter R_econ creates an asset that looks supported and is
permanently unissuable, discoverable only at mint time. Both refusals happen
after request validation and before the first side effect — no policy anchor,
no registry row, no ERA fee debit, no advance — so a malformed capped request
still gets the precise reason it is malformed instead of a capability refusal
that hides it.

The finite-cap encoding, its parser and its policy-condition meaning are left
intact. This is a capability fence, not a redefinition.

Consume-once, proven on the binding that carries it

the_same_authorization_bytes_cannot_fund_a_second_credit replays the
IDENTICAL bytes at the next economic position with the operation digest held
constant, so it cannot pass because "the digest differs" — it passes on the
position binding, which is the thing actually doing the work.

Mutation controls (each red, then restored)

  • policy-key matching neutralized -> a_signature_from_an_unnamed_key_does_not_count red
  • position binding neutralized -> the_same_authorization_bytes_cannot_fund_a_second_credit red
  • capped-creation gate neutralized -> a_capped_token_is_refused_at_creation_and_leaves_nothing_behind red

One honest note: the initial-supply guard is DIAGNOSTIC, not load-bearing.
Removing it does not make supply-at-creation possible — the policy parser, the
CreateToken write-set rule and the accepting layer each refuse it. What
removing it costs is the reason the caller is given. Its doc comment says so,
so it is not later mistaken for the fourth enforcement point.

Absence-asserting tests re-cut, not deleted

Five tests asserted the absence this work removes. Each was a deliberate
tripwire and each fired correctly; all five are re-cut to the narrower
property that replaced the absence, recording what they used to prove.
IssuancePredicateUndefined is deleted from both enums (an unconstructed
public variant produces no dead_code warning, so the sweep found these, not
the compiler), and CreateTokenInitialSupplyRequiresIssuancePredicate — still
correct, for a NEW reason — has its message corrected.

A fixture sweep across eight files follows from the creation gate. Two of those
fixtures asserted on cap semantics and were re-cut rather than flipped; the
cap-scaling arithmetic is still pinned through the capped path's own refusals,
which now assert on the REASON so they cannot silently re-target to the
capability gate.

Verification

workspace   3918 passed / 0 failed across 72 suites
node        270 passed / 0 failed, exit 0
make lint   exit 0
safety      exit 0

…e predicate

A user-created asset had no way for its supply to enter a validated lineage.
Every credit must name a funded source, and the arms that existed all sourced
value that already existed elsewhere. `token.mint` filled the gap by signing
its own authorization with the caller's device key, which is a caller
asserting an authorization rather than holding one.

0x0029 replaces that with a policy-authorized issuance the verifier reruns
from scratch. There is no caller-supplied `policy_passed`.

The 0x0023 arm is a conjunction over the AUTHENTICATED operation, never the
descriptor: the verified op is a Mint/CreateToken for non-zero units; the
evidence bundle resolves by exact content address and re-hashes; its carried
policy bytes re-hash to the OPERATION's policy_commit; the body states this
issuance, lineage, position and op digest; the committed policy parses and
permits THIS issuance; k distinct signers match the policy-named set BEFORE
their signatures are verified; asset and amount equal what the authorization
names.

The signatures cover the operation's own digest, so they cannot ride inside
it. A Mint carrying a non-empty proof_of_authorization is refused, keeping the
evidence bundle the only channel instead of leaving an ignored one beside it.

The V1 matrix refuses rather than ignores: a finite SupplyCap (its
circulating-supply input is per-device, so N authorized devices would each
mint to the ceiling), an authority its own signer set cannot satisfy, a policy
with mint/burn disabled, and a recipient device outside a committed allowlist
— the authenticated DevID, never a caller-chosen `authorized_by`. It reads the
committed v3 blob, the exact form policy_commit commits; there is deliberately
no second matrix over the PolicyCondition model, whose conditions that blob
cannot encode.

Capped creation is refused at token.create, after request validation and
before any side effect — no anchor, no registry row, no ERA fee debit, no
advance. A policy is immutable once anchored, so anchoring one whose supply
can never enter R_econ would create an asset that looks supported and is
permanently unissuable, discoverable only at mint time. The finite-cap
encoding and its parser are left intact; this is a capability fence.

Consume-once is proven by replaying the IDENTICAL bytes at the next economic
position with the operation digest held constant, so the test passes on the
position binding rather than on a differing digest.

Three mutation controls run red and restored: policy-key matching, the
position binding, and the capped-creation gate. The initial-supply guard is
DIAGNOSTIC and its doc says so — the policy parser, the CreateToken write-set
rule and the accepting layer each already refuse supply-at-creation, so
removing the guard costs the caller its reason, not the refusal.

Five absence-asserting tests fired as designed and are re-cut to the narrower
property that replaced the absence. Both IssuancePredicateUndefined variants
are deleted (an unconstructed public variant raises no dead_code warning), and
CreateTokenInitialSupplyRequiresIssuancePredicate — still correct, for a new
reason — has its message corrected. The uncalled condition-level matrix is
deleted rather than left as a parallel path.

Boards on the final tree: workspace 3918/0 across 72 suites, node 270/0,
make lint exit 0, production safety checks exit 0.
@cryptskii
cryptskii merged commit 68c5a75 into main Aug 30, 2026
18 checks passed
@cryptskii
cryptskii deleted the feat/0x0029-authorized-issuance-v1 branch August 30, 2026 01:42
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