-
Notifications
You must be signed in to change notification settings - Fork 70
feat(kms): optional TCB UpToDate requirement for apps #498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
90140f9 to
d660bc0
Compare
d660bc0 to
bfeaa5b
Compare
… remove KMS backward-compat factory DstackApp: - Extract shared init logic into _initializeCommon() internal function - Add old 5-param initialize(address,bool,bool,bytes32,bytes32) overload for upgrade compatibility with existing proxies - Keep 6-param initialize with requireTcbUpToDate for new deployments - Add version() pure function returning 2 for capability detection DstackKms: - Remove 5-param deployAndRegisterApp backward-compat overload - Remove _deployAndRegisterApp internal function - Keep only 6-param deployAndRegisterApp with inline logic, callers must explicitly specify requireTcbUpToDate
…task - deployContract() accepts optional initializer signature param, passed to hre.upgrades.deployProxy for overload disambiguation - estimateDeploymentCost() accepts optional initializer param, passed to encodeFunctionData - app:deploy task adds --requireTcbUpToDate flag and passes 6 args with explicit initializer signature
…tion - setup.ts / DstackApp.test.ts: pass explicit initializer signature to deployContract and hre.upgrades.deployProxy - DstackApp.test.ts: add version() test, add TCB-via-initialize tests - DstackApp.upgrade.test.ts (new): 9 cases covering: - Old 5-param proxy upgrade preserves storage - version()=2 available after upgrade - requireTcbUpToDate defaults false (no silent behavior change) - setRequireTcbUpToDate works post-upgrade with access control - isAppAllowed TCB enforcement after owner opt-in - KMS factory deploys with TCB on/off, end-to-end verification
Manual Upgrade Test Plan (anvil fork of Base)SetupTerminal 1 — start anvil: anvil --fork-url https://mainnet.base.orgTerminal 2 — all commands below: cd kms/auth-eth
export RPC=http://127.0.0.1:8545
export PK=0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e
export IMPL_SLOT=0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcPhase 1: Deploy old version (master)git checkout master
npx hardhat clean && npx hardhat compile
npx hardhat kms:deploy --with-app-impl --network test
# Record output, set env vars:
export KMS_PROXY=<DstackKms proxy address>
export OLD_APP_IMPL=<DstackApp implementation address>
export KMS_CONTRACT_ADDRESS=$KMS_PROXY
npx hardhat kms:create-app \
--allow-any-device \
--hash 0x0000000000000000000000000000000000000000000000000000000000000001 \
--network test
# Record output, set env var:
export EXISTING_APP=<App proxy address>Phase 2: Record pre-upgrade baseline# Old version has no version(), expect revert
cast call $EXISTING_APP "version()(uint256)" --rpc-url $RPC
# Old version has no requireTcbUpToDate, expect revert
cast call $EXISTING_APP "requireTcbUpToDate()(bool)" --rpc-url $RPC
# Existing data should be present
cast call $EXISTING_APP "allowedComposeHashes(bytes32)(bool)" \
0x0000000000000000000000000000000000000000000000000000000000000001 \
--rpc-url $RPC
# Expected: true
cast call $EXISTING_APP "allowAnyDevice()(bool)" --rpc-url $RPC
# Expected: true
cast call $EXISTING_APP "owner()(address)" --rpc-url $RPC
# Expected: deployer address
# Record current implementation address (ERC1967 slot)
cast storage $EXISTING_APP $IMPL_SLOT --rpc-url $RPC
# Should match OLD_APP_IMPL (left-zero-padded to 32 bytes)Phase 3: Switch to new code and upgradegit checkout feature/app-tcb-toggle
npx hardhat clean && npx hardhat compile
# 3a. Deploy new DstackApp implementation
npx hardhat app:deploy-impl --network test
export NEW_APP_IMPL=<new DstackApp impl address>
# Verify old and new impl addresses differ
echo "OLD: $OLD_APP_IMPL"
echo "NEW: $NEW_APP_IMPL"
# These MUST be different; if identical, compilation is stale
# 3b. Deploy new DstackKms implementation and upgrade KMS proxy
npx hardhat kms:deploy-impl --network test
npx hardhat kms:upgrade --address $KMS_PROXY --network test
# 3c. Update KMS app implementation pointer
npx hardhat kms:set-app-implementation $NEW_APP_IMPL --network test
# 3d. Upgrade existing DstackApp proxy via direct UUPS call
# (bypasses hardhat-upgrades plugin manifest issues with factory-deployed proxies)
cast send $EXISTING_APP "upgradeToAndCall(address,bytes)" \
$NEW_APP_IMPL 0x \
--private-key $PK --rpc-url $RPCPhase 4: Post-upgrade verification# Implementation address updated
cast storage $EXISTING_APP $IMPL_SLOT --rpc-url $RPC
# Expected: NEW_APP_IMPL (left-zero-padded to 32 bytes)
# version() = 2
cast call $EXISTING_APP "version()(uint256)" --rpc-url $RPC
# Expected: 2
# requireTcbUpToDate defaults to false (no silent behavior change)
cast call $EXISTING_APP "requireTcbUpToDate()(bool)" --rpc-url $RPC
# Expected: false
# Old data preserved
cast call $EXISTING_APP "allowedComposeHashes(bytes32)(bool)" \
0x0000000000000000000000000000000000000000000000000000000000000001 \
--rpc-url $RPC
# Expected: true
cast call $EXISTING_APP "allowAnyDevice()(bool)" --rpc-url $RPC
# Expected: true
cast call $EXISTING_APP "owner()(address)" --rpc-url $RPC
# Expected: deployer address
# Enable TCB check
cast send $EXISTING_APP "setRequireTcbUpToDate(bool)" true \
--private-key $PK --rpc-url $RPC
cast call $EXISTING_APP "requireTcbUpToDate()(bool)" --rpc-url $RPC
# Expected: true
# Disable TCB check
cast send $EXISTING_APP "setRequireTcbUpToDate(bool)" false \
--private-key $PK --rpc-url $RPC
cast call $EXISTING_APP "requireTcbUpToDate()(bool)" --rpc-url $RPC
# Expected: falsePhase 5: Verify new factory path (6-param with TCB flag)# With TCB flag
npx hardhat kms:create-app \
--require-tcb-up-to-date \
--allow-any-device \
--hash 0x0000000000000000000000000000000000000000000000000000000000000002 \
--network test
export NEW_APP=<output address>
cast call $NEW_APP "version()(uint256)" --rpc-url $RPC
# Expected: 2
cast call $NEW_APP "requireTcbUpToDate()(bool)" --rpc-url $RPC
# Expected: true
# Without TCB flag
npx hardhat kms:create-app \
--allow-any-device \
--hash 0x0000000000000000000000000000000000000000000000000000000000000003 \
--network test
export NEW_APP2=<output address>
cast call $NEW_APP2 "requireTcbUpToDate()(bool)" --rpc-url $RPC
# Expected: falsePhase 6: Verify backward-compatible 5-param factory path (old SDK callers)This verifies that old SDK callers using the 5-param # Send tx via old 5-param overload, capture txhash with --json
TX_HASH=$(cast send $KMS_PROXY \
"deployAndRegisterApp(address,bool,bool,bytes32,bytes32)" \
$(cast wallet address --private-key $PK) \
false \
true \
0x0000000000000000000000000000000000000000000000000000000000000000 \
0x0000000000000000000000000000000000000000000000000000000000000005 \
--private-key $PK --rpc-url $RPC --json | jq -r '.transactionHash')
# Extract app address from AppDeployedViaFactory event (topics[1])
# Event signature: AppDeployedViaFactory(address indexed appId, address indexed deployer)
# Topic[0] = 0xfd86d7f6962eba3b7a3bf9129c06c0b2f885e1c61ef2c9f0dbb856be0deefdee
export OLD_SDK_APP=$(cast receipt $TX_HASH --json --rpc-url $RPC \
| jq -r '.logs[] | select(.topics[0] == "0xfd86d7f6962eba3b7a3bf9129c06c0b2f885e1c61ef2c9f0dbb856be0deefdee") | .topics[1]' \
| sed 's/0x000000000000000000000000/0x/')
echo "Old SDK app deployed at: $OLD_SDK_APP"
cast call $OLD_SDK_APP "version()(uint256)" --rpc-url $RPC
# Expected: 2
cast call $OLD_SDK_APP "requireTcbUpToDate()(bool)" --rpc-url $RPC
# Expected: false (5-param overload defaults to false)
cast call $OLD_SDK_APP "allowAnyDevice()(bool)" --rpc-url $RPC
# Expected: truePhase 7: Verify app:deploy standalone pathnpx hardhat app:deploy \
--require-tcb-up-to-date \
--allow-any-device \
--hash 0x0000000000000000000000000000000000000000000000000000000000000004 \
--network test
export STANDALONE_APP=<output address>
cast call $STANDALONE_APP "version()(uint256)" --rpc-url $RPC
# Expected: 2
cast call $STANDALONE_APP "requireTcbUpToDate()(bool)" --rpc-url $RPC
# Expected: trueChecklist
Notes
|
…rApp Keep the old 5-param deployAndRegisterApp(address,bool,bool,bytes32,bytes32) overload that defaults requireTcbUpToDate=false, so existing SDK callers (e.g. phala-cloud SDKs using viem) continue to work without changes. Fix ethers v6 ambiguous overload resolution by using explicit function signatures in kms:create-app task and upgrade tests.
Production Upgrade Safety Guide (Multisig)Owner operations go through the multisig. Implementation deployments can be done by any EOA independently. Pre-upgrade: Record all current implementation addressesexport IMPL_SLOT=0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
# Record KMS current implementation
OLD_KMS_IMPL=$(cast storage $KMS_PROXY $IMPL_SLOT --rpc-url $RPC)
echo "KMS impl: $OLD_KMS_IMPL"
# Record each App proxy current implementation
OLD_APP_IMPL=$(cast storage $APP_PROXY $IMPL_SLOT --rpc-url $RPC)
echo "App impl: $OLD_APP_IMPL"
# Record current factory app implementation pointer
OLD_FACTORY_APP_IMPL=$(cast call $KMS_PROXY "appImplementation()(address)" --rpc-url $RPC)
echo "Factory app impl: $OLD_FACTORY_APP_IMPL"Save these values offline before proceeding. They are your rollback targets. Phase A: Deploy new implementations (any EOA, no multisig needed)Implementation contracts are just bytecode on chain — deploying them has zero effect on existing proxies. Any team member with an EOA and gas can do this independently, ahead of the multisig signing session. npx hardhat app:deploy-impl --network <network>
# Record: NEW_APP_IMPL=<address>
npx hardhat kms:deploy-impl --network <network>
# Record: NEW_KMS_IMPL=<address>Verify the deployed code is correct: # Should return 2
cast call $NEW_APP_IMPL "version()(uint256)" --rpc-url $RPC
# Confirm implementation addresses differ from current
echo "Old app impl: $OLD_APP_IMPL"
echo "New app impl: $NEW_APP_IMPL"These addresses can sit on chain indefinitely until the multisig is ready to proceed. Phase B: Full dry run on anvil fork (any team member)Run the complete Manual Upgrade Test Plan (see comment above) on a fork. This also requires no multisig. Phase C: Multisig operations (requires signature collection)Pre-encode ALL calldata (including rollback) before starting the signing session. Tx #1: Upgrade KMS proxycast calldata "upgradeToAndCall(address,bytes)" $NEW_KMS_IMPL 0xSubmit to Safe UI:
After execution, verify: cast storage $KMS_PROXY $IMPL_SLOT --rpc-url $RPC
# Must match NEW_KMS_IMPL
cast call $KMS_PROXY "appImplementation()(address)" --rpc-url $RPC
# Must still return OLD_FACTORY_APP_IMPL (unchanged)Tx #2: Update factory app implementation pointercast calldata "setAppImplementation(address)" $NEW_APP_IMPLSubmit to Safe UI:
This only affects NEW apps created via factory. Existing apps are untouched. Tx #3: Canary — upgrade ONE non-critical app proxycast calldata "upgradeToAndCall(address,bytes)" $NEW_APP_IMPL 0xSubmit to Safe UI:
Verify: cast call $CANARY_APP "version()(uint256)" --rpc-url $RPC
# Expected: 2
cast call $CANARY_APP "requireTcbUpToDate()(bool)" --rpc-url $RPC
# Expected: false
cast call $CANARY_APP "owner()(address)" --rpc-url $RPC
# Expected: multisig address (unchanged)Tx #4+: Upgrade remaining app proxiesRepeat tx #3 for each app. Can be batched in a single multisig approval using Safe Transaction Builder. Rollback commands (multisig transactions)
Pre-encode these before starting the upgrade so they're ready if needed: Rollback KMS proxy: cast calldata "upgradeToAndCall(address,bytes)" $OLD_KMS_IMPL 0x
# Submit to Safe: To=$KMS_PROXY, Value=0, Data=<output>Rollback App proxy: cast calldata "upgradeToAndCall(address,bytes)" $OLD_APP_IMPL 0x
# Submit to Safe: To=$APP_PROXY, Value=0, Data=<output>Rollback factory pointer: cast calldata "setAppImplementation(address)" $OLD_FACTORY_APP_IMPL
# Submit to Safe: To=$KMS_PROXY, Value=0, Data=<output>What cannot be rolled backIf a DstackApp owner has previously called Post-upgrade behavior guarantees
Checklist before signing session
|
Summary
requireTcbUpToDateflag to DstackApp (initializer + setter) and enforce it inisAppAllowedUpgrade notes (existing KMS deployments)
npx hardhat kms:deploy-impl).upgradeTo(address)on the existing KMS proxy.DstackAppimplementation, deploy a newDstackAppimplementation and callsetAppImplementation(address)on the KMS proxy. Existing app proxies can be upgraded separately via their ownupgradeTo(if upgrades are not disabled).Testing
cd kms/auth-eth && npm test