fix(era): ERA's canonical token policy — its commitment derived from real bytes, replacing the hash of nothing - #1022
Merged
Conversation
…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.
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.
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
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; commitmentJXPMPGJH45HDTE0ARWE2CTB9E9BWTQZ3T78CE5RFF1RXMR9VKK80(Crockford base32, computed from the layout). MASTER re-pinned; MR-SOFI-0334–0337.feat(token-policy): the grammar —TokenPolicy.releaseisAllAtCreation { creator, threshold, signers }orFaucet; the parser branches on the rule byte and requires the canonicalTokenPolicyV3wrapper (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).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)dsm_sdklib 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) pluspolicy::,token_routes,token_adoption_tests,balance_list_tests,wallet_routes,faucet_flow_tests.dsmlib 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).dsmlib 100/0, provenance 8/0,dsm_sdk48/0 + 8/0. The created-token layout pina_device_created_policy_keeps_its_layout_and_commitmentwas 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.b3sumoverDSM/policy ‖ 0x00 ‖ bytes) and equals the golden value the code computes.Known, not in this PR (CONFORMANCE_GAPS §6.33 Open)
Android Instrumented Tests (managed device)job has been red onmainsince feat(ci): the bridge's two sides must agree on every RPC name #1012 (AndroidLayerProofTest), unrelated to this change.