|
| 1 | +# Movement Core Resource Signer Deprecation |
| 2 | + |
| 3 | +## Status |
| 4 | + |
| 5 | +Draft. |
| 6 | + |
| 7 | +## Summary |
| 8 | + |
| 9 | +Movement mainnet should stop relying on the core resource signer as a privileged path for framework |
| 10 | +governance execution. Mainnet framework changes should be authorized by validator governance, using |
| 11 | +the existing multi-step proposal flow. Testnet should keep the core resource signer path because it |
| 12 | +is useful for operational testing and fast iteration. |
| 13 | + |
| 14 | +The migration has three phases: |
| 15 | + |
| 16 | +1. Prove delegation-pool governance can create proposals and vote. |
| 17 | +2. Execute a mainnet framework upgrade that decommissions core resource authority on mainnet. |
| 18 | +3. Rotate the core resource account authentication key to an unrecoverable key as defense in depth. |
| 19 | + |
| 20 | +## Goals |
| 21 | + |
| 22 | +- Mainnet framework upgrades are executable only after a governance proposal receives sufficient |
| 23 | + validator voting power. |
| 24 | +- A core resource account signature can no longer obtain a framework signer on Movement mainnet. |
| 25 | +- Delegation-pool owners and voters can participate in proposal creation and voting through the |
| 26 | + delegation-pool governance path. |
| 27 | +- Testnet root-signer workflows remain available. |
| 28 | +- Historical proposal artifacts under `movement-migration/` remain unchanged. |
| 29 | + |
| 30 | +## Non-Goals |
| 31 | + |
| 32 | +- Remove the core resource account from genesis. |
| 33 | +- Remove testnet root-signer workflows. |
| 34 | +- Introduce a new release-builder execution mode if the existing `MultiStep` governance mode is |
| 35 | + sufficient. |
| 36 | +- Rewrite historical proposals that have already been generated or executed. |
| 37 | + |
| 38 | +## Current State |
| 39 | + |
| 40 | +Mainnet currently has two relevant authority paths: |
| 41 | + |
| 42 | +- Governance path: proposals are created, voted on, and resolved by `aptos_governance`. |
| 43 | +- Core resource shortcut: scripts signed by `@core_resources` can call |
| 44 | + `aptos_governance::get_signer_testnet_only(core_resources, signer_address)` and receive a signer |
| 45 | + for a framework-controlled address. |
| 46 | + |
| 47 | +The shortcut is intended for tests and testnets, but it is still callable wherever |
| 48 | +`system_addresses::assert_core_resource` accepts `@core_resources`. |
| 49 | + |
| 50 | +The framework already contains a transient feature flag: |
| 51 | + |
| 52 | +```move |
| 53 | +std::features::get_decommission_core_resources_enabled() |
| 54 | +``` |
| 55 | + |
| 56 | +`system_addresses::is_core_resource_address` returns false when this feature flag is enabled. Since |
| 57 | +`get_signer_testnet_only` calls `system_addresses::assert_core_resource`, enabling this feature |
| 58 | +prevents the core resource signer from using that function. |
| 59 | + |
| 60 | +Movement chain IDs in Rust are: |
| 61 | + |
| 62 | +- Movement mainnet: `126` |
| 63 | +- Movement testnet: `250` |
| 64 | + |
| 65 | +## Proposed Design |
| 66 | + |
| 67 | +### 1. Validate Delegation-Pool Governance |
| 68 | + |
| 69 | +Add and run a focused test that verifies a delegation-pool owner can: |
| 70 | + |
| 71 | +- create a governance proposal through `delegation_pool::create_proposal`; |
| 72 | +- vote on the proposal through `delegation_pool::vote`; |
| 73 | +- consume the expected voting power. |
| 74 | + |
| 75 | +This validates the operator-facing path that Movement expects to use for mainnet proposal |
| 76 | +participation. |
| 77 | + |
| 78 | +Validation command: |
| 79 | + |
| 80 | +```bash |
| 81 | +RUST_MIN_STACK=16777216 TEST_FILTER=test_delegation_pool_owner_can_create_and_vote \ |
| 82 | + cargo test -p aptos-framework --test move_unit_test move_framework_unit_tests -- --nocapture |
| 83 | +``` |
| 84 | + |
| 85 | +### 2. Use Existing Multi-Step Governance for Mainnet Releases |
| 86 | + |
| 87 | +Movement mainnet should use the existing `MultiStep` proposal mode, even for a one-script proposal. |
| 88 | +For a single executable step, the proposal is still created with governance-v2 semantics and the |
| 89 | +last step uses an empty next execution hash. |
| 90 | + |
| 91 | +The generated script should resolve governance before obtaining the framework signer: |
| 92 | + |
| 93 | +```move |
| 94 | +let framework_signer = aptos_governance::resolve_multi_step_proposal( |
| 95 | + proposal_id, |
| 96 | + @aptos_framework, |
| 97 | + x"", |
| 98 | +); |
| 99 | +``` |
| 100 | + |
| 101 | +This means no new release-builder execution mode is required for the initial migration. Existing |
| 102 | +testnet `RootSigner` behavior should remain unchanged. |
| 103 | + |
| 104 | +### 3. Mainnet Framework Upgrade: Decommission Core Resources |
| 105 | + |
| 106 | +The first mainnet governance upgrade should enable the existing |
| 107 | +`DECOMMISSION_CORE_RESOURCES` feature flag. |
| 108 | + |
| 109 | +After this feature flag is enabled: |
| 110 | + |
| 111 | +- `system_addresses::is_core_resource_address(@core_resources)` returns false; |
| 112 | +- `system_addresses::assert_core_resource(core_resources)` aborts; |
| 113 | +- `aptos_governance::get_signer_testnet_only(core_resources, signer_address)` aborts before |
| 114 | + returning any signer; |
| 115 | +- scripts signed only by `@core_resources` can no longer mint a framework signer on mainnet. |
| 116 | + |
| 117 | +This is the actual security cutover. Key rotation alone is not sufficient before this step, because |
| 118 | +the framework would still contain the privileged conversion path. |
| 119 | + |
| 120 | +The implementation should use governance to enable the feature flag. If desired, a follow-up code |
| 121 | +change can make `get_signer_testnet_only` explicitly reject Movement mainnet by checking |
| 122 | +`chain_id::get() == 126`, but the feature flag is already wired into the core resource assertion and |
| 123 | +keeps testnet behavior intact. |
| 124 | + |
| 125 | +### 4. Rotate Core Resource Authentication Key |
| 126 | + |
| 127 | +After the decommissioning feature is active on mainnet, submit a separate governance proposal that |
| 128 | +rotates the `@core_resources` authentication key to an unrecoverable value. |
| 129 | + |
| 130 | +This step is defense in depth: |
| 131 | + |
| 132 | +- the framework already rejects core resource signer authority after phase 3; |
| 133 | +- the key rotation prevents the old key from submitting ordinary transactions as `@core_resources`; |
| 134 | +- operators can verify that transactions signed by the old key are rejected. |
| 135 | + |
| 136 | +The key rotation proposal must resolve governance for the core resource address. It should not use |
| 137 | +the old core resource private key to perform the rotation. |
| 138 | + |
| 139 | +## Operator Flow |
| 140 | + |
| 141 | +### Proposal Creation |
| 142 | + |
| 143 | +For stake-pool governance: |
| 144 | + |
| 145 | +```bash |
| 146 | +movement governance propose \ |
| 147 | + --pool-address <stake-pool-address> \ |
| 148 | + --metadata-url <metadata-url> \ |
| 149 | + --script-path <script-path> \ |
| 150 | + --is-multi-step \ |
| 151 | + --sender-account <delegated-voter-address> |
| 152 | +``` |
| 153 | + |
| 154 | +For delegation-pool governance: |
| 155 | + |
| 156 | +```bash |
| 157 | +movement governance delegation-pool propose \ |
| 158 | + --delegation-pool-address <delegation-pool-address> \ |
| 159 | + --metadata-url <metadata-url> \ |
| 160 | + --script-path <script-path> \ |
| 161 | + --is-multi-step \ |
| 162 | + --sender-account <voter-address> |
| 163 | +``` |
| 164 | + |
| 165 | +### Voting |
| 166 | + |
| 167 | +For stake-pool governance: |
| 168 | + |
| 169 | +```bash |
| 170 | +movement governance vote \ |
| 171 | + --pool-addresses <stake-pool-address> \ |
| 172 | + --proposal-id <proposal-id> \ |
| 173 | + --yes \ |
| 174 | + --sender-account <delegated-voter-address> |
| 175 | +``` |
| 176 | + |
| 177 | +For delegation-pool governance: |
| 178 | + |
| 179 | +```bash |
| 180 | +movement governance delegation-pool vote \ |
| 181 | + --delegation-pool-address <delegation-pool-address> \ |
| 182 | + --proposal-id <proposal-id> \ |
| 183 | + --yes \ |
| 184 | + --sender-account <voter-address> |
| 185 | +``` |
| 186 | + |
| 187 | +### Execution |
| 188 | + |
| 189 | +After the proposal succeeds: |
| 190 | + |
| 191 | +```bash |
| 192 | +movement governance execute-proposal \ |
| 193 | + --proposal-id <proposal-id> \ |
| 194 | + --script-path <script-path> \ |
| 195 | + --sender-account <executor-address> |
| 196 | +``` |
| 197 | + |
| 198 | +The executor does not need core resource authority. The script itself must resolve the successful |
| 199 | +governance proposal before obtaining the framework signer. |
| 200 | + |
| 201 | +## Verification Checklist |
| 202 | + |
| 203 | +Before mainnet execution: |
| 204 | + |
| 205 | +- Delegation-pool owner create/vote test passes. |
| 206 | +- Generated proposal scripts use `resolve_multi_step_proposal`. |
| 207 | +- Proposal metadata and script hashes are published and independently verified by validators. |
| 208 | +- The decommission proposal enables `DECOMMISSION_CORE_RESOURCES`. |
| 209 | +- Testnet `RootSigner` generation is unchanged. |
| 210 | + |
| 211 | +After mainnet execution: |
| 212 | + |
| 213 | +- `std::features::get_decommission_core_resources_enabled()` returns true. |
| 214 | +- `system_addresses::is_core_resource_address(@core_resources)` returns false. |
| 215 | +- A script calling `get_signer_testnet_only` with `@core_resources` aborts on mainnet. |
| 216 | +- The old core resource key cannot execute privileged framework operations. |
| 217 | +- Testnet root-signer workflows continue to work on Movement testnet. |
| 218 | + |
| 219 | +## Risks and Mitigations |
| 220 | + |
| 221 | +### Incorrectly Breaking Testnet |
| 222 | + |
| 223 | +Risk: disabling the core resource signer globally would break testnet operations. |
| 224 | + |
| 225 | +Mitigation: use the feature flag only on Movement mainnet, and do not remove the testnet script |
| 226 | +generation path. |
| 227 | + |
| 228 | +### Governance Participation Misconfiguration |
| 229 | + |
| 230 | +Risk: delegation-pool operators may assume operator status is enough to vote. |
| 231 | + |
| 232 | +Mitigation: document that delegation-pool voting depends on delegated voting power. The operator can |
| 233 | +vote only if they have their own voting power or delegators delegate voting power to them. |
| 234 | + |
| 235 | +### Irreversible Key Rotation Before Framework Cutover |
| 236 | + |
| 237 | +Risk: rotating the core resource key before decommissioning the framework path leaves the privileged |
| 238 | +path in code and may complicate rollback. |
| 239 | + |
| 240 | +Mitigation: upgrade the framework first, verify the feature is active, then rotate the auth key in a |
| 241 | +separate proposal. |
| 242 | + |
| 243 | +### Proposal Execution Failure |
| 244 | + |
| 245 | +Risk: the decommission proposal succeeds but execution fails due to script or hash mismatch. |
| 246 | + |
| 247 | +Mitigation: use the existing proposal verification tooling and require validators to independently |
| 248 | +verify script hash, metadata hash, and expected feature flag changes before voting. |
| 249 | + |
| 250 | +## Rollback |
| 251 | + |
| 252 | +Before execution, rollback is simple: do not execute the proposal. |
| 253 | + |
| 254 | +After the decommissioning proposal executes, rollback would require another successful governance |
| 255 | +proposal to disable `DECOMMISSION_CORE_RESOURCES`. After the core resource key is rotated to an |
| 256 | +unknown key, rollback must not depend on the core resource account. |
| 257 | + |
| 258 | +## Open Questions |
| 259 | + |
| 260 | +- Should `aptos_governance::get_signer_testnet_only` also include an explicit Movement mainnet |
| 261 | + chain-id guard, or is the existing feature flag gate sufficient? |
| 262 | +- What exact unrecoverable authentication key should be used for `@core_resources` rotation? |
| 263 | +- Should operator runbooks require delegation-pool vote delegation to be configured before the |
| 264 | + decommission proposal is submitted? |
0 commit comments