Skip to content

fix(genesis): write the canonical authority root on the existing-head branch and finish first-run identity publication in-session - #869

Merged
cryptskii merged 2 commits into
mainfrom
fix/genesis-root-and-identity-publication
Sep 13, 2026
Merged

cryptskii merged 2 commits into
mainfrom
fix/genesis-root-and-identity-publication

Conversation

@cryptskii

Copy link
Copy Markdown
Collaborator

Every freshly created wallet persisted a device head whose genesis authority root was [0u8; 32] while AppState held the real seed-rooted v3.g; the ERA faucet's authority evidence (which re-derives the true G) then failed closed on every first-run device. This PR fixes the root at its ordering and makes first-run identity publication complete in-session.

Root cause (reproduced by the new tests)

install_v2_genesis calls StateMachine::set_state BEFORE CoreSDK::write_genesis_device_head. set_state materialised a head with a zero genesis when none existed, and write_genesis_device_head only wrote genesis on its construct-new branch — so the existing-head branch (the one genesis install always takes) left the persisted head claiming a zero root. On top of that, AppRouterImpl::new ran the synthetic-genesis path on every router build, which once the write became authoritative would have overwritten a restored canonical root.

What changes

  • dsm/types/device_state.rs: DeviceState::set_genesis_digest — install G on an already-constructed head.
  • dsm/core/state_machine/mod.rs: set_state re-seeds an EXISTING head only; it does not know G and no longer manufactures a zero-root head.
  • dsm_sdk/sdk/core_sdk.rs: write_genesis_device_head sets genesis unconditionally on both branches and installs the head in memory; the synthetic-genesis path refuses to run over an identity that already has a canonical head. Three tests reproduce the defect at its ordering: the existing-head branch, set_state not fabricating, and the router build not overwriting a restored root.
  • dsm_sdk/sdk/kyber_identity.rs: a cold LOCAL_KYBER_PUBKEY slot is recovered from the canonical derivation (current_smaster() + DSM/kyber\0) instead of refused — first-run devices no longer park in PublicationPending until an app restart.
  • dsm_sdk/handlers/system_routes.rs: system.createGenesisV2 writes the publication row and drives publish_identity_now in-session (spawned; genesis never blocks on the network; the startup retry stays the backstop, not the only driver).
  • dsm_sdk/sdk/identity_publication.rs: after the Published row is written, post dsm-wallet-refresh so the Android host recomputes the session phase instead of showing "PUBLISHING IDENTITY…" until restart.

Hardware

On the 4-phone beta fleet (2026-09-13, wiped devices): system.createGenesisV2: identity PUBLISHED … (5/5 verified, quorum 3) in-session on all four; the ERA faucet claim then admits (100 ERA each) with no restart.

Verification

  • cargo test --release -p dsm --lib core::state_machine types::device_state
  • cargo test --release -p dsm_sdk --lib sdk::core_sdk sdk::kyber_identity sdk::identity_publication handlers::system_routes init:: (--test-threads=1)
  • fmt / clippy -D warnings / ci/production_safety_checks.sh on the branch
  • Board: CI on this PR.

… branch and finish first-run identity publication in-session

Every freshly created wallet persisted a device head whose genesis
authority root was [0u8; 32] while AppState held the real seed-rooted
v3.g; the ERA faucet's authority evidence, which re-derives the true G,
then failed closed on every first-run device.

Root cause, reproduced at its ordering by the new tests: install_v2_genesis
calls StateMachine::set_state BEFORE CoreSDK::write_genesis_device_head;
set_state materialised a zero-genesis head when none existed, and
write_genesis_device_head only wrote `genesis` on its construct-new branch,
so the existing-head branch — the one genesis install always takes — left
the persisted head claiming a zero root. AppRouterImpl::new's synthetic
genesis path would then have overwritten a restored canonical root on
every router build.

- DeviceState::set_genesis_digest installs G on an already-constructed head.
- StateMachine::set_state re-seeds an EXISTING head only; it does not know
  G and no longer manufactures one.
- write_genesis_device_head sets `genesis` on both branches and installs
  the head in memory; the synthetic path refuses to run over an identity
  that already has a canonical head.
- kyber_identity: a cold LOCAL_KYBER_PUBKEY slot is recovered from the
  canonical derivation instead of refused, so first-run devices no longer
  park in PublicationPending until an app restart.
- system.createGenesisV2 writes the publication row and drives
  publish_identity_now in-session (spawned; genesis never blocks on the
  network; the startup retry stays the backstop).
- identity_publication posts dsm-wallet-refresh after the Published row so
  the host recomputes the session phase.

Hardware (4-phone beta fleet, 2026-09-13): identity PUBLISHED in-session
(5/5 verified, quorum 3) on all four; the faucet admits with no restart.
… genesis they declare

StateMachine::set_state no longer manufactures a head — it does not know
the genesis authority root and must not invent one — so the three traces
that built a machine from a bare State (transfer chain, signature
rejection, fork divergence) had no head to advance and CI's
implementation-trace replay went 2/16 FAIL.

The traces build their state with State::new_genesis(seed, ..): the seed
IS the root they declare, so the head is now installed with it explicitly
through the real API (set_device_head), then set_state re-seeds the legacy
root exactly as before. A declared root in a validation trace is honest
where a fabricated one in production was not.

`cargo run --locked -p dsm_vertical_validation -- implementation-traces`:
16/16 PASS; the crate's tests: green.
@cryptskii
cryptskii merged commit 2e9c70c into main Sep 13, 2026
19 checks passed
@cryptskii
cryptskii deleted the fix/genesis-root-and-identity-publication branch September 13, 2026 13: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