Skip to content

fix(era): ERA's canonical token policy — its commitment derived from real bytes, replacing the hash of nothing - #1022

Merged
cryptskii merged 3 commits into
mainfrom
feat/era-canonical-token-policy
Sep 26, 2026
Merged

cryptskii merged 3 commits into
mainfrom
feat/era-canonical-token-policy

Conversation

@cryptskii

@cryptskii cryptskii commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Status: ready — merge before the on-device run

What

ERA's commitment was raw BLAKE3("") — the hash of a 0-byte file (dsm_sdk/src/policies/native.ctpa.bin), "verified" at load time by hashing that empty file against the hash of an empty file. It committed to no policy. Since #1016 the enforcer only takes a policy from bytes that re-hash to the commitment named, so every ERA transfer and burn is refused (the 45-test manifest, CONFORMANCE_GAPS §6.32). This PR defines ERA's policy for the first time and derives its commitment from those exact bytes.

Commits

  1. docs(spec): SoFi Amendment S11 — a network-anchored native policy names no creator and no signer set; exactly one exists, ERA's. ERA: version 3, fungible, native, transferable and burnable, no allowlist, release rule the beta faucet, ticker/alias ERA, decimals 0, genesis supply 80,000,000,000, no description/icon; commitment JXPMPGJH45HDTE0ARWE2CTB9E9BWTQZ3T78CE5RFF1RXMR9VKK80 (Crockford base32, computed from the layout). MASTER re-pinned; MR-SOFI-0334–0337.
  2. feat(token-policy): the grammar — TokenPolicy.release is AllAtCreation { creator, threshold, signers } or Faucet; the parser branches on the rule byte and requires the canonical TokenPolicyV3 wrapper (one policy, one commitment); the enforcer, adoption and publication refuse network-anchored lookalikes. Device-created policies are byte-identical (a pinned commitment held before and after).
  3. feat(era): ERA's 40-byte policy in Core, its commitment derived from it; pre-rooted in the enforcer (never cached, never fetched); the empty-file builtins deleted; the wallet's ERA facts from the policy. This is what turns ERA transfers back on.

Follow-up PR (hardening, not blocking): the reserve's supply from ERA's policy with exhaustion refused before signing, SoFi's ERA leg check, a gate against the digest of nothing, the remaining docs.

Consequences

ERA is re-keyed: balance keys and the reserve id change. The reserve's genesis state is computed, never stored, so the fleet needs no reset; wallets holding ERA under the old commitment (frozen since #1016) are left behind — fresh installs for the device run.

Verification (release, dsm_client/deterministic_state_machine, the storage node's own code on Postgres)

  • The §6.32 manifest: all 45 pass. dsm_sdk lib 183/0 across the eleven manifest modules (bluetooth::offline_step_tests, bilateral_finality_tests, node_e2e_tests, recipient_accept, recipient_admission_tests, recipient_dispatch, relationship_finalized, sender_admission_tests, storage_routes, b0x_sdk, core_sdk) plus policy::, token_routes, token_adoption_tests, balance_list_tests, wallet_routes, faucet_flow_tests.
  • dsm lib 73/0 (core::token, economic::token_policy, economic::provenance, types::device_state, economic::native_reserve); integration 66/0 (economic_provenance_semantics, economic_write_set, economic_peer_evidence, economic_admission_lifecycle, native_reserve_wire).
  • Commit 2 alone: dsm lib 100/0, provenance 8/0, dsm_sdk 48/0 + 8/0. The created-token layout pin a_device_created_policy_keeps_its_layout_and_commitment was run on the unchanged packer first (green) and holds after the grammar change.
  • make lint → 0. ci/production_safety_checks.sh (incl. the conformance-evidence check) → pass.
  • ERA's commitment was computed independently from the layout (b3sum over DSM/policy ‖ 0x00 ‖ bytes) and equals the golden value the code computes.

Known, not in this PR (CONFORMANCE_GAPS §6.33 Open)

  • The reserve's supply is still its own constant (equal to ERA's committed supply), and a faucet claim at exhaustion signs before checking what remains — the follow-up makes exhaustion terminal and refused before signing.
  • SoFi still exempts ERA's legs as a builtin; its policy now exists to check them against.
  • Wallet rows are keyed by ticker, so a created token named "ERA" collides with ERA in the display.
  • The Android Instrumented Tests (managed device) job has been red on main since feat(ci): the bridge's two sides must agree on every RPC name #1012 (AndroidLayerProofTest), unrelated to this change.

…k-anchored native policy names no creator and no signer set

ERA's former commitment was raw BLAKE3 of empty input — the hash of a 0-byte
file — and committed to no policy. Owner decisions (2026-09-26): define ERA's
policy for the first time and derive its commitment from those exact bytes.

- A native token whose releases are anchored to the network names no creator
  and no signer set; exactly one such policy exists, ERA's, fixed in Core and
  checked by its commitment. Device-created layouts are unchanged.
- ERA: version 3, fungible, native, transferable and burnable, no allowlist,
  release rule the beta faucet, ticker/alias ERA, decimals 0, genesis supply
  80,000,000,000, no description or icon; commitment
  JXPMPGJH45HDTE0ARWE2CTB9E9BWTQZ3T78CE5RFF1RXMR9VKK80 (Crockford base32 of
  BLAKE3(DSM/policy || 0x00 || TokenPolicyV3 bytes), computed from the layout).
- The faucet payout is not committed; the reserve's accounting is. Join
  emission after beta is a different ERA identity (Open).
- Stale §47 packer line and §51 "Code" line fixed.

MASTER_REQUIREMENTS re-pinned; MR-SOFI-0300, 0303, 0315, 0332 scoped to
device-created policies; MR-SOFI-0334–0337 added (Missing in CONFORMANCE_GAPS
until built in this branch).
… signer set; one policy, one commitment

SoFi Amendment S11, the grammar.

- TokenPolicy.release is Release::AllAtCreation { creator_genesis,
  creator_device_id, threshold, signers } or Release::Faucet, so a creator or
  signer set is readable only where the policy has one. The parser branches on
  the rule byte: device-created blobs are byte-identical to before; a
  network-anchored blob goes from the rule byte straight to the ticker.
- parse_token_policy requires the canonical TokenPolicyV3 wrapper: an unknown
  field or a repeated blob field would have given one policy a second
  commitment.
- verify_genesis_release is one match admitting only device-created policies.
- The enforcer's register_policy, tokens.addByAnchor and tokens.publishPolicy
  (via adoptable_policy) refuse network-anchored blobs: the one that exists,
  ERA's, is fixed in Core; any other is a lookalike with no reserve.
- The one packer lays out both shapes.
- Tests: the created-token fixture's commitment pinned and held before and
  after the change; network-anchored parse; shape mismatch refused both ways;
  non-canonical wrappers refused; lookalike refused; the provenance rule test
  rebuilt on a well-formed network-anchored blob with its message asserted.
…m its bytes, pre-rooted

SoFi Amendment S11. ERA's commitment was raw BLAKE3 of empty input (a 0-byte
file "verified" against itself); it committed to no policy, so every ERA
transfer and burn was refused. This turns them back on under ERA's own rules.

- dsm/src/core/token/era_policy.rs (new): ERA's 40-byte TokenPolicyV3 —
  version 3, fungible, native, transferable and burnable, no allowlist, the
  beta faucet release rule (no creator, no signers), ticker/alias ERA,
  decimals 0, genesis supply 80,000,000,000. era_policy_commit() =
  BLAKE3(DSM/policy || 0x00 || bytes), computed once; the literal is deleted.
  Golden test: the specification's check value
  JXPMPGJH45HDTE0ARWE2CTB9E9BWTQZ3T78CE5RFF1RXMR9VKK80. The one packer
  reproduces the bytes (SDK test).
- The enforcer answers ERA from Core's bytes before the LRU cache and the
  resolver (never evicted, never fetched); ERA's bytes are never registered.
  A creation naming ERA's commitment is refused from ERA's own policy.
- SDK: native.ctpa.bin, native.commit32 and the native half of builtins.rs
  deleted; builtin_policy_commit("ERA") reads Core; tokens.addByAnchor refuses
  protocol assets; load_policy_bytes and anchored_policy_bytes answer ERA from
  Core and never fetch or store it; the wallet's ERA facts (symbol, decimals,
  supply, permissions, anchor) are the policy's.
- CONFORMANCE_GAPS §6.33; MR-SOFI-0334-0336 Met; the §6.32 manifest
  discharged: its 45 tests pass (dsm_sdk lib 183/0 across the manifest modules
  and the token, policy, wallet and faucet tests; dsm lib 73/0, integration
  66/0).

ERA is re-keyed (balance keys, reserve id); R_0 is computed, never stored, so
the fleet needs no reset; wallets holding the old ERA start fresh.
@cryptskii
cryptskii marked this pull request as ready for review September 26, 2026 22:00
@cryptskii
cryptskii merged commit f779cb8 into main Sep 26, 2026
23 of 24 checks passed
@cryptskii
cryptskii deleted the feat/era-canonical-token-policy branch September 26, 2026 22:46
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