Skip to content

refactor(lee)!: derive immutable-mirror AccountId without npk/vpk - #903

Merged
jonesmarvin8 merged 2 commits into
marvin/privacy-and-shadow-executionsfrom
marvin/immutable-mirror-address-derivation
Sep 22, 2026
Merged

jonesmarvin8 merged 2 commits into
marvin/privacy-and-shadow-executionsfrom
marvin/immutable-mirror-address-derivation

Conversation

@jonesmarvin8

@jonesmarvin8 jonesmarvin8 commented Sep 19, 2026 •

Copy link
Copy Markdown
Collaborator

🎯 Purpose

Removes unnecessary npk and vpk used to generate the commitment for an immutable program.

BREAKING CHANGE: Breaking change over PR 824 in how AccountIds are computed for immutable programs (in the private state); does not affect anything in dev.

⚙️ Approach

  • Remove immutable_mirror_account_id, immutable_mirror_vpk, and the IMMUTABLE_MIRROR_NPK sentinel constant
  • Add AccountId::for_immutable_mirror: SHA256("/LEE/v0.3/AccountId/ImmutMirror/" || header_account_id)
  • Update immutable_mirror_commitment to call the new method (signature unchanged; all 5 existing callers unaffected)
  • Add unit tests.

🧪 How to Test

All previous tests should work; new tests added:

  • for_immutable_mirror_matches_pinned_value
  • for_immutable_mirror_differs_for_different_header

🔗 Dependencies

PR 824; should merge after it.

🔜 Future Work

N/A

📋 PR Completion Checklist

  • Complete PR description
  • Implement the core functionality
  • Add/update tests
  • Add/update documentation and inline comments

@jonesmarvin8 jonesmarvin8 changed the title refactor(lee): derive immutable-mirror AccountId directly, not via private-PDA reuse refactor(lee)!: derive immutable-mirror AccountId without npk/vpk Sep 19, 2026
@jonesmarvin8 jonesmarvin8 added the priority:medium Normal priority label Sep 19, 2026
jonesmarvin8 added 2 commits September 22, 2026 05:49
…ivate-PDA reuse

Replace immutable_mirror_account_id/immutable_mirror_vpk and the sentinel
IMMUTABLE_MIRROR_NPK with AccountId::for_immutable_mirror, a proper sibling
to for_shadow_program/for_private_pda. The mirror's address never had a
controlling key pair to begin with, so routing it through
AccountId::for_private_pda with a placeholder npk/vpk was misleading;
it's now its own domain-separated derivation
(SHA256("/LEE/v0.3/AccountId/ImmutMirror/" || header_account_id)).

Adds pinned-value and address-distinctness unit tests, and rebuilds the
privacy_preserving_circuit guest artifact to match.
rustls 0.23.40 is flagged by a newly-published advisory (TLS 1.3 handshake
messages accepted across encryption level boundaries); bump to 0.23.45,
the fixed version.

Also rebuilds all guest artifacts and the prebuilt sequencer db fixture:
CI's artifacts check rebuilds every ELF fresh and diffs against what's
committed, and all of them (not just the one this branch's own commit
touched) were already out of sync with a fresh build in CI's Docker image,
inherited from the base branch.
@jonesmarvin8
jonesmarvin8 force-pushed the marvin/immutable-mirror-address-derivation branch from 64de38f to 81e2c21 Compare September 22, 2026 09:50
@jonesmarvin8
jonesmarvin8 merged commit cf38db5 into marvin/privacy-and-shadow-executions Sep 22, 2026
10 of 13 checks passed
jonesmarvin8 pushed a commit that referenced this pull request Sep 22, 2026
…programs

Renamed ProgramImageWitness/ProgramImageClaim/ProgramKind's Public/Private
variants to Disclosed/Undisclosed: both are publicly deployed programs, the
distinction is only whether the claim discloses which one, and the old names
read as if Private meant not publicly deployed.

Asserted program_header.immutable in to_claim()'s Undisclosed arm, where
prover-supplied witness data first crosses the trust boundary -- fail-fast
defense in depth alongside the real invariant every other caller of
immutable_mirror_commitment already enforces.

Added account_id/self_account_id fields to FfiDependency and
FfiProgramWithDependencies: ffi_kind_account_id derived every account id from
kind + bytecode, silently assuming a Disclosed/Undisclosed program's header
always lives at its own bijection address. It doesn't -- CreateHeader lets a
deployer place a header at any address, and the native ProgramWithDependencies
API already takes that address as an explicit parameter, but the FFI had no
way to express it. Shadow keeps deriving from the bytecode, since that's
definitional and there's no header to consult.

Required is_authorized unconditionally for CreateHeader targets in
finalize_header, matching UpdateHeader's existing rule. CreateHeader
previously let a deployer place a header at any empty account with no
authorization check at all; since AccountId::for_shadow_program is a plain
forward hash, an attacker could compute a target program's shadow address in
advance, deploy an unrelated "honest" header there to collect real public
trust and funds, then dispatch a weaker program privately under the same
address later to drain them -- no hash needs breaking, since the deployer
chooses the target directly. The production wallet facade already always
signs for the header's own key, so this closes the gap without touching any
real deployment path; genesis bypasses CreateHeader entirely via
insert_program, so it's unaffected too.

Rebuilt the privacy_preserving_circuit RISC0 artifact, last regenerated at
#903, one commit before #905 changed lee_core's circuit-input types -- the
checked-in guest binary no longer matched the host's current encoding of
PrivacyPreservingCircuitInput, so any shadow-program proof failed in-guest
with a Borsh length mismatch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority:medium Normal priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants