Skip to content

docs(tip-1061): specify native multisig accounts - #7242

Draft
joshieDo wants to merge 17 commits into
mainfrom
agent/native-multisig-docs
Draft

docs(tip-1061): specify native multisig accounts#7242
joshieDo wants to merge 17 commits into
mainfrom
agent/native-multisig-docs

Conversation

@joshieDo

@joshieDo joshieDo commented Aug 19, 2026

Copy link
Copy Markdown
Member

Defines counterfactual native multisig accounts for T12 using carried configuration witnesses and one-slot commitments. Adds the corresponding changelog entry; the stacked implementation begins with #7234.

@joshieDo

Copy link
Copy Markdown
Member Author

cyclops audit fast note="this belongs to the GH stack #7243 and the TIP 1061 is specified in the PR 7242. any bug not introduced by this PR should be clearly marked"

@tempoxyz-bot tempoxyz-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️ Cyclops Review

TIP-1061 is detailed and generally consistent, but two low-severity specification gaps should be resolved before independent implementations target T11.

Reviewer Callouts
  • Primitive sender collision assumption: The design intentionally omits a registry check for primitive signatures that recover to a registered multisig address and treats the approximately 2^80 address-collision attack as out of scope. A human should explicitly accept that account-takeover tradeoff.
  • Pool validation cost: Invalid transactions can pass the claimed sender's fee-affordability check before forcing bounded but expensive multisig verification. Confirm peer scoring or rate limiting handles repeated NativeMultisigValidationFailed submissions.

@joshieDo

Copy link
Copy Markdown
Member Author

cyclops audit fast note="this belongs to the GH stack #7243 and the TIP 1061 is specified in the PR 7242. any bug not introduced by this PR should be clearly marked"

@tempoxyz-bot tempoxyz-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️ Cyclops Review

Two actionable findings were verified in the T11 configurable-accounts specification.

Reviewer Callouts
  • State-dependent intrinsic gas: Ensure pool validation, estimation, block building, and execution use the same journaled block-prefix state and recompute after configuration changes.
  • Direct-frame enforcement: msg.sender == tx.origin does not block DELEGATECALL; require an explicit protocol-frame/depth guard.
  • Configuration storage updates: Clear old owner weight rows before overwriting the owner count or writing new rows.
  • Stateless signer recovery: Audit recover_signer and recover_authority callers so bare multisig encodings are not treated as proof of quorum.
  • Pool/executor consistency: Resolve whether unsupported multisig authorization-list signatures are rejected or skipped and enforce one rule everywhere.
  • Validation work bounds: Consider an explicit pre-verification budget for invalid transactions.
  • Bootstrap version wording: Clarify that the stored version is 0 during bootstrap before a header exists.
  • Configuration events: Include the new version in MultisigConfigUpdated.

@joshieDo

Copy link
Copy Markdown
Member Author

cyclops audit fast note="this belongs to the GH stack #7243 and the TIP 1061 is specified in the PR 7242. any bug not introduced by this PR should be clearly marked"

@tempoxyz-bot tempoxyz-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️ Cyclops Review

TIP-1061 is generally detailed and internally consistent, but one verified Medium gas-accounting issue should be fixed before implementation.

Reviewer Callouts
  • Invalid-transaction validation cost (tips/tip-1061.md:546): Fee affordability precedes owner-approval verification against a statelessly claimed account, allowing expensive junk trees to consume validation work before rejection. Consider checking derivable owner membership and ordering before P256/WebAuthn verification.
  • Non-minimal quorum sets (tips/tip-1061.md:211): Earlier removable signatures can produce multiple transaction hashes for the same authorization. Consider requiring every submitted signature to be necessary for quorum.

Comment thread tips/tip-1061.md
@joshieDo

Copy link
Copy Markdown
Member Author

cyclops audit fast note="this belongs to the GH stack #7243 and the TIP 1061 is specified in the PR 7242. any bug not introduced by this PR should be clearly marked"

@tempoxyz-bot tempoxyz-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️ Cyclops Review

TIP-1061 is generally detailed, but one verified Medium-severity ordering rule remains undefined. Prior Cyclops reviews already covered the bootstrap-version wording and the no-code/address-collision tradeoff, so those are not repeated here.

Reviewer Callouts
  • Authorization failure semantics: Quorum failure after fee-affordability validation must reject the transaction as invalid rather than execute as a charged revert; otherwise unauthenticated transactions could burn a funded multisig account's balance.
  • Intrinsic-gas read ordering: Warm pricing makes registry-gating gas depend on check order. Pin the evaluation order so independent implementations compute the same intrinsic gas.
  • Bootstrap gas headroom: The stated worst-case 48-owner bootstrap is roughly 29.0M against a 30M transaction cap before its required non-empty call; confirm the limit remains bootstrappable under future gas-schedule changes.
  • Encoding discriminator: State explicitly that bootstrap and initialized encodings are distinguished by the first RLP item's list-versus-address shape.

Comment thread tips/tip-1061.md Outdated
- The 3,000 gas subtraction MUST apply once at the outer account node, never to nested nodes or key authorization sidecars.
- Direct multisig authorization MUST NOT add the account keychain's 900 gas processing buffer.
- Bootstrap MUST charge one warm header SSTORE and `2 * owners.len()` cold SSTOREs, including the active TIP-1060 creditable portion, plus its warm header read, transient guard write, and `MultisigInitialized` event.
- State-dependent role restrictions MUST add `registry_gating_surcharge` to intrinsic gas.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ISSUE] Registry role checks are not ordered after same-transaction bootstrap

The TIP allows an account to be bootstrapped and registered in the same transaction while requiring registry checks for newly authorized access-key IDs, keychain authorization-list authorities, and keychain callers with code or delegation. It never specifies whether those checks observe state before or after the bootstrap header write. An implementation using the pre-bootstrap snapshot could treat the new account as unregistered and permit a restricted role, including installing EIP-7702 delegation on a live multisig account and violating the no-account-code invariant.

Recommended Fix:
Require every multisig-restricted role check to observe registry state after any same-transaction bootstrap header write. Explicitly sequence bootstrap before key-authorization registration and authorization-list application, and define an account bootstrapped in transaction T as registered for every role check in T.

@joshieDo
joshieDo force-pushed the agent/native-multisig-docs branch from d02b5cd to 0142fe0 Compare August 25, 2026 10:55
@joshieDo

Copy link
Copy Markdown
Member Author

cyclops audit fast note="this belongs to the GH stack #7243 and the TIP 1061 is specified in the PR 7242. any bug not introduced by this PR should be clearly marked"

@tempoxyz-bot tempoxyz-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️ Cyclops Review

TIP-1061 is generally detailed and internally consistent. One unverified, low-severity defense-in-depth specification gap remains; consensus validation is unaffected.

Reviewer Callouts
  • Implementation pool paths: In the stacked implementation, verify that sender indexing, RPC from, two-dimensional nonce slots, and key_authorization.key_id revocation subjects do not treat stateless recover_signer() output as authenticated.

Comment thread tips/tip-1061.md
- Keychain-signed authorization-list entries MUST retain the existing T0 behavior: they are skipped, MUST NOT install delegation, and MUST NOT require a multisig-commitment read.
- Subblock transactions MUST NOT use multisig outer or key authorization signatures.
- Sponsorship MUST use existing sponsored signing hashes.
- Pools MUST revalidate transactions carrying an initial or current signature for an account, or naming it as `key_authorization.key_id`, whenever that account's commitment changes, including after reorgs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛡️ [DEFENSE-IN-DEPTH] Require pool authentication before multisig sender attribution

A type-0x05 signature carries account as a caller-controlled field, and stateless recovery explicitly returns it without proving commitment equality, owner membership, or quorum. The specification requires stateful authentication before consensus authorization and requires pool revalidation after commitment changes, but it never explicitly requires pools and RPCs to authenticate the claim before attributing, indexing, or propagating the transaction. An implementation that indexes first could let forged transactions occupy another account's sender/nonce slots or pollute key-authorization revocation indexing, although block validation would still reject them.

Recommended Fix:
Add a normative pool-admission rule requiring commitment comparison and quorum verification against pool tip state before a multisig transaction—or a multisig-signed key_authorization—is attributed, indexed, or propagated under the claimed account. Keep any unauthenticated helper explicitly named or documented as returning only a claimed account.

@joshieDo
joshieDo force-pushed the agent/native-multisig-docs branch from ec9b966 to 6fb4542 Compare August 26, 2026 09:19
joshieDo added a commit that referenced this pull request Aug 31, 2026
Reopens #6640 against #7242.

Anchors initial multisig identity to a canonical CREATE2 recovery wallet
while preserving stateless versioned commitments. The baseline recovery
path remains bound to the initial owners and is restricted to
asset-transfer call shapes, with a reproducible init-code hash and
focused Foundry tests.

---------

Co-authored-by: joshieDo <93316087+joshieDo@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Changelog found on PR.

Edit changelog

@joshieDo

Copy link
Copy Markdown
Member Author

cyclops audit fast note="TIP-1061 spec/docs layer. Audit this boundary against main; implementation belongs to later PRs. Clearly mark cross-layer findings."

@tempoxyz-bot tempoxyz-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️ Cyclops Review

TIP-1061 specifies counterfactual native multisig accounts and cross-chain recovery with detailed, reproducible artifacts, but one verified Medium-severity security finding must be addressed before T12.

Reviewer Callouts
  • T12 transition ordering: Explicitly define how activation handles multisig addresses that already contain code; replacing only the factory does not clean up wallets deployed before activation.
  • Recovery-validator parity: The recovery contract does not enforce the Tempo rule that an account cannot own itself. Although constructing such a fixed point is impractical, document the intentional validator difference.
  • Canonical recovery signatures: recoverSigner accepts both v ∈ {0,1} and v ∈ {27,28}, despite the specification requiring canonical secp256k1 signatures.

Comment thread tips/tip-1061.md

- On a supported non-Tempo EVM chain, tooling MUST first verify that `MULTISIG_RECOVERY_SINGLETON_FACTORY` contains code with hash `MULTISIG_RECOVERY_SINGLETON_FACTORY_RUNTIME_HASH`. This is Safe's Singleton Factory; it is the deterministic deployer, not the recovery factory.
- The dedicated recovery factory MUST be deployed by sending `MULTISIG_RECOVERY_FACTORY_DEPLOYMENT_SALT || TempoMultisigRecoveryFactory.creationCode` as raw calldata to the Safe Singleton Factory. Its EIP-1014 address MUST equal `MULTISIG_RECOVERY_FACTORY`, and its runtime-code hash MUST equal `MULTISIG_RECOVERY_FACTORY_RUNTIME_HASH`. A missing singleton or dedicated factory, or any hash mismatch, means this TIP provides no recovery guarantee on that chain.
- On every Tempo chain, the T12 state transition MUST reserve `MULTISIG_RECOVERY_FACTORY` with the one-byte `0xEF` marker and a nonce of at least one before native multisig authorization is accepted. The transition MUST replace any code already present at that address while preserving its balance, storage, and any higher nonce. The canonical recovery-factory runtime MUST NOT be installed on Tempo. The Safe Singleton Factory MAY remain available for unrelated deployments.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 [SECURITY] Reserve the recovery factory before T12 to prevent multisig-address squatting

The proposal reserves MULTISIG_RECOVERY_FACTORY only during T12, but Tempo genesis already includes the permissionless Safe Singleton Factory that deterministically deploys the canonical recovery factory at that address. Before T12, anyone who knows an account salt can deploy canonical recovery-wallet bytecode at the corresponding future native multisig address. Replacing the factory at T12 does not remove those wallets, so the initial owner quorum could use the wallet's on-Tempo recover() after a Tempo owner rotation; alternatively, enforcing the no-code invariant would permanently reject the squatted account. A Foundry proof of concept reproduced the factory deployment, wallet deployment, and native-asset sweep.

Recommended Fix:
Reserve MULTISIG_RECOVERY_FACTORY with the 0xEF marker in a hardfork before T12 (or in chainspec/genesis where possible). Add a normative rule requiring multisig authorization and updateConfig to reject accounts with bytecode or EIP-7702 delegation, and require derivation tooling to report a pre-existing wallet as an unusable account identity.

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

📊 Tempo Precompiles Coverage

precompiles

Coverage: 6455/10661 lines (60.55%)

File details
File Lines Coverage
src/account_keychain/dispatch.rs 16/45 35.56%
src/account_keychain/mod.rs 310/941 32.94%
src/address_registry/dispatch.rs 21/27 77.78%
src/address_registry/mod.rs 50/60 83.33%
src/current_committee/dispatch.rs 0/9 0.00%
src/current_committee/mod.rs 0/18 0.00%
src/dispatch.rs 140/163 85.89%
src/error.rs 52/166 31.33%
src/ip_validation.rs 10/10 100.00%
src/lib.rs 132/149 88.59%
src/nonce/dispatch.rs 6/7 85.71%
src/nonce/mod.rs 44/58 75.86%
src/receive_policy_guard/dispatch.rs 0/12 0.00%
src/receive_policy_guard/mod.rs 0/139 0.00%
src/signature_verifier/dispatch.rs 15/23 65.22%
src/signature_verifier/mod.rs 13/55 23.64%
src/stablecoin_dex/dispatch.rs 59/71 83.10%
src/stablecoin_dex/mod.rs 990/1097 90.25%
src/stablecoin_dex/order/mod.rs 120/154 77.92%
src/stablecoin_dex/order/storage.rs 178/249 71.49%
src/stablecoin_dex/orderbook.rs 183/242 75.62%
src/storage/actions.rs 11/90 12.22%
src/storage/evm.rs 390/437 89.24%
src/storage/hashmap.rs 0/241 0.00%
src/storage/mod.rs 27/69 39.13%
src/storage/packing.rs 68/93 73.12%
src/storage/thread_local.rs 200/296 67.57%
src/storage/types/array.rs 0/72 0.00%
src/storage/types/bytes_like.rs 135/179 75.42%
src/storage/types/cache.rs 65/122 53.28%
src/storage/types/mapping.rs 27/48 56.25%
src/storage/types/mod.rs 47/71 66.20%
src/storage/types/primitives.rs 21/24 87.50%
src/storage/types/set.rs 28/192 14.58%
src/storage/types/slot.rs 80/101 79.21%
src/storage/types/vec.rs 103/261 39.46%
src/storage_credits/accounting.rs 79/92 85.87%
src/storage_credits/dispatch.rs 16/16 100.00%
src/storage_credits/mod.rs 138/193 71.50%
src/tip20/dispatch.rs 64/73 87.67%
src/tip20/mod.rs 657/801 82.02%
src/tip20/rewards.rs 242/260 93.08%
src/tip20/roles.rs 85/88 96.59%
src/tip20_channel_reserve/dispatch.rs 0/42 0.00%
src/tip20_channel_reserve/mod.rs 3/548 0.55%
src/tip20_factory/dispatch.rs 9/11 81.82%
src/tip20_factory/mod.rs 117/140 83.57%
src/tip403_registry/dispatch.rs 29/46 63.04%
src/tip403_registry/mod.rs 347/544 63.79%
src/tip_fee_manager/amm.rs 295/465 63.44%
src/tip_fee_manager/dispatch.rs 35/37 94.59%
src/tip_fee_manager/mod.rs 56/155 36.13%
src/validator_config/dispatch.rs 18/28 64.29%
src/validator_config/mod.rs 171/227 75.33%
src/validator_config_v2/dispatch.rs 30/33 90.91%
src/validator_config_v2/mod.rs 523/581 90.02%
src/zone_factory/dispatch.rs 0/15 0.00%
src/zone_factory/mod.rs 0/215 0.00%
src/zone_factory/portal.rs 0/60 0.00%

contracts

Coverage: 1/223 lines (0.45%)

File details
File Lines Coverage
src/lib.rs 1/1 100.00%
src/precompiles/receive_policy_guard.rs 0/78 0.00%
src/precompiles/storage_credits.rs 0/7 0.00%
src/precompiles/tip20.rs 0/50 0.00%
src/precompiles/tip20_channel_reserve.rs 0/27 0.00%
src/precompiles/tip403_registry.rs 0/9 0.00%
src/precompiles/validator_config_v2.rs 0/3 0.00%
src/precompiles/zone_factory.rs 0/48 0.00%

Total: 6456/10884 lines (59.32%)

📦 Download full HTML report

joshieDo and others added 10 commits August 31, 2026 20:27
Reopens #6640 against #7242.

Anchors initial multisig identity to a canonical CREATE2 recovery wallet
while preserving stateless versioned commitments. The baseline recovery
path remains bound to the initial owners and is restricted to
asset-transfer call shapes, with a reproducible init-code hash and
focused Foundry tests.

---------

Co-authored-by: joshieDo <93316087+joshieDo@users.noreply.github.com>
@joshieDo
joshieDo force-pushed the agent/native-multisig-docs branch from cd4dae5 to f83bf62 Compare August 31, 2026 18:58
@joshieDo

Copy link
Copy Markdown
Member Author

cyclops audit fast note="TIP-1061 spec/docs layer. Audit this boundary against main; implementation belongs to later PRs. Clearly mark cross-layer findings."

@tempoxyz-bot tempoxyz-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👁️ Cyclops Review

TIP-1061 specifies native multisig accounts and adds a reproducible cross-chain recovery wallet. Two verified low-severity recovery-call behaviors should be clarified or tightened. The verified factory-squatting vulnerability is not repeated because Cyclops already reported the same root cause on this PR.

Reviewer Callouts
  • Changelog timing: .changelog/native-multisig.md announces T12 activation and minor releases for crates untouched by this specification-only PR; confirm the fragment will not publish before the implementation lands.
  • Artifact-check CI scope: When recovery constants are added under crates/, extend the specs workflow path filter so Rust-only constant changes still run the canonical artifact check.

/// bridging, arbitrary calls) is rejected.
function _isAllowedRecoveryCall(Call calldata call_) internal view returns (bool) {
if (call_.data.length == 0) {
return call_.value != 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ISSUE] Native sweeps can invoke arbitrary contract receive or fallback logic

This accepts every nonzero-value call with empty calldata, including calls to contracts, and recover forwards all remaining gas. The target can therefore execute arbitrary state-changing receive() or fallback behavior as the multisig address—for example wrapping or bridging ETH—despite the comments claiming that bridging and arbitrary calls are rejected.

Recommended Fix:
Prefer correcting the comments and specification to state that selector filtering cannot constrain empty-calldata contract semantics. If the stronger containment guarantee is required, reject contract targets for native sweeps, recognizing that this prevents transfers to smart accounts.

if (!ok) {
revert CallFailed(i, returndata);
}
if (calls[i].data.length >= 4 && bytes4(calls[i].data[:4]) == ERC20_TRANSFER) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 [SUGGESTION] Check the shared ERC-20/ERC-721 transferFrom return value

Selector 0x23b872dd, named ERC721_TRANSFER_FROM, is also ERC-20 transferFrom(address,address,uint256) and is accepted when from is the wallet. This return check only runs for ERC20_TRANSFER, so a token that returns false from transferFrom without reverting makes recovery report success and consume the nonce even though no tokens moved.

Recommended Fix:
Apply the same optional-return validation to successful 0x23b872dd calls when return data is present, or explicitly disallow/document ERC-20 use of this selector.

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.

4 participants