Skip to content

feat(tip-1061): configurable accounts - #4069

Open
jxom wants to merge 118 commits into
mainfrom
tip/1061
Open

feat(tip-1061): configurable accounts#4069
jxom wants to merge 118 commits into
mainfrom
tip/1061

Conversation

@jxom

@jxom jxom commented May 19, 2026

Copy link
Copy Markdown
Member

Adds a TIP for native multisig accounts using weighted threshold primitive owner signatures. The draft defines stable derived account identity, bootstrap validation, config updates through the multisig precompile, and consensus validation rules for current owner and threshold authorization.

@jxom jxom changed the title docs(tip): add native multisig accounts tip: native multisig accounts May 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR has been marked stale due to 7 days of inactivity.

@github-actions github-actions Bot added the stale label May 28, 2026
Comment thread tips/tip-1061.md Outdated
Comment thread tips/tip-1061.md Outdated
/// @param configId The permanent config ID for msg.sender. It is not a config version.
/// @param threshold The new threshold. Must be between 1 and the total owner weight.
/// @param owners The new sorted owner signature types, key IDs, and weights.
function updateMultisigConfig(

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.

Nitpick: we know this is the multisig precompile, so we don't need to put "multisig" in all the names.

Comment thread tips/tip-1061.md
account ||
config_id
)
```

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.

Unless we have a good reason, I think owners should probably sign over the multisig's configuration as it exists at signature time along with its permanent ID. Otherwise my old signature under one config can become valid in a new one.

A classic Safe mostly handles this because of its sequential nonce. Since config changes bump the nonce, an old signature can't accidentally become valid in a new config. (It does not totally handle this because a signature for a future nonce can become valid later, but that's a much weirder thing to do)

A simple option is an incrementing config version that we bump on every update.

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.

Since we removed the concept of config_id don't think this applies directly anymore.

But won't the owners also have to authorize the config change, in which case they might still want their signatures to be valid?

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.

still a bit weird no? i sign a tx back when it's 2-2 (say), then parallel nonce sign a tx making the multisig a 1-2, and depending on how it's ordered we could end up with my sig being sufficient?

@horsefacts horsefacts added the cyclops Trigger Cyclops PR audit label May 29, 2026

@tempoxyz-cyclops-bot tempoxyz-cyclops-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👁️ Cyclops Review

Found 1 actionable issue in the draft TIP text. The issue is inline on the affected invariant wording.

Reviewer Callouts
  • Gas accounting before activation: tips/tip-1061.md:529-541 and tips/tip-1061.md:943-945 leave gas accounting as a TODO. Before implementation/activation, the gas schedule should deterministically price up to 10 owner verifications and large WebAuthn approvals to avoid CPU/DoS underpricing.
  • Optional-tail decoder disambiguation: tips/tip-1061.md:150-159 adds a second optional transaction tail. Future implementation should spell out and test the exact key_authorization vs. multisig_init RLP-shape disambiguation so malformed tails cannot be accepted differently by different clients.
  • Signature byte parsing precedence: tips/tip-1061.md:170-183 reserves outer signature byte 0x05 for multisig. Future decoder changes should preserve existing raw 65-byte secp256k1 precedence so legacy signatures whose first byte is 0x05 are not misclassified.
  • Top-level precompile frame enforcement: tips/tip-1061.md:657-675 and tips/tip-1061.md:737-770 require updateMultisigConfig to run only in a protocol-created top-level frame. The eventual precompile implementation will need explicit call-depth/frame context to enforce this exactly.

Comment thread tips/tip-1061.md Outdated
Comment on lines +587 to +597
### Signature Verifier Restrictions

The TIP-1020 signature verifier precompile remains a stateless primitive signature verifier.

This TIP updates TIP-1020 by adding `TempoSignature::Multisig` to the rejected stateful signature forms.

Signature verifier rules:

- `ISignatureVerifier.recover` MUST reject `TempoSignature::Multisig` with `InvalidFormat()`.
- `ISignatureVerifier.verify` MUST reject `TempoSignature::Multisig` with `InvalidFormat()`.
- Multisig authorization MUST be performed by transaction validation, not by the signature verifier precompile.

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.

So a native multisig cannot sign messages at all? Safes have a mechanism for this, seems like we might need parity here.

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.

We just need to add a verify function to SignatureVerifier, then I think it should be gtg. We can do in a follow up TIP for T9

@0xalpharush 0xalpharush added the T7 label May 29, 2026
Comment thread tips/tip-1061.md Outdated
| `✗` | `✔︎` | `multisig_init` |
| `✔︎` | `✔︎` | `key_authorization, multisig_init` |

- `InitMultisig` is encoded as `rlp([threshold, owners])`.

@0xalpharush 0xalpharush May 29, 2026

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.

Suggested change
- `InitMultisig` is encoded as `rlp([threshold, owners])`.
- `InitMultisig` is encoded as `rlp([tag, threshold, owners])`.

Should we not add a tag here so it is unambiguous whether to try to parse as key_authorization or multisig_init ?

@github-actions github-actions Bot removed the stale label May 31, 2026
@legion2002 legion2002 removed the T7 label Jun 9, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR has been marked stale due to 7 days of inactivity.

@github-actions github-actions Bot added the stale label Jun 21, 2026
@jxom jxom removed the stale label Jun 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR has been marked stale due to 7 days of inactivity.

@github-actions github-actions Bot added the stale label Jun 29, 2026
@jxom jxom removed the stale label Jun 29, 2026
Comment thread tips/tip-1061.md Outdated
@legion2002
legion2002 marked this pull request as ready for review July 1, 2026 15:40

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ff9b0bf8b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tips/tip-1061.md Outdated

11. **Native multisig sender.** A transaction from a native multisig account MUST use `TempoSignature::Multisig` as the outer signature.

12. **Top-level multisig signatures.** `TempoSignature::Multisig` MUST be accepted only as the outer transaction signature.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clarify nested multisig signatures are permitted

This invariant says TempoSignature::Multisig is accepted only as the outer transaction signature, but the main owner-approval rules explicitly allow nested multisig owner approvals and recursively validate them. Taken literally, implementers following the invariants would reject every nested multisig approval, breaking the parent -> child -> grandchild authorization path and the nested-signature test cases defined earlier in the TIP.

Useful? React with 👍 / 👎.

Comment thread tips/tip-1061.md Outdated

19. **No config update signature.** Config updates MUST NOT accept an additional signature parameter.

## Copyright

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.

remove?

Comment thread tips/tip-1061.md Outdated

Canonical EVM multisigs are generally contract accounts with an owner set, owner weights, and a threshold. Tempo can provide the same core threshold-control model through native account and transaction validation, without requiring a contract wallet deployment.

## Specification

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.

remove? none of the other TIPs have this.

Comment thread tips/tip-1061.md Outdated
`derive_multisig_account(init) == signature.account`.
- Nested multisig owner approvals MUST NOT carry `init`; nested multisig accounts MUST already be initialized.

Flat M-of-N multisigs are represented by assigning every owner weight `1` and setting `threshold` to `M`. Because `threshold`, owner weights, and the owner count are `u8`, flat M-of-N configs can express thresholds up to 255 and owner counts up to 255.

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.

are we worried at all about the validation work of that many sigs (times 3 layers)? this is basically unpaid at the txpool level right?

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c8e3f97ac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +45 to +46
} else {
TempoSignature::default()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Build a keychain envelope for sidecar bootstrap simulations

When bootstrap is carried by key_authorization.signature, the request uses keyId for the newly authorized key and does not set the top-level multisigInit; none of these branches examines key_id, so this fallback creates a primitive outer signature. Even with the simulation sentinel, validation still requires a sidecar bootstrap to have a V2 keychain outer signature, causing both eth_simulateV1 and the delegating tempo_simulateV1 path to reject this valid authorize-and-use flow. Construct the keychain mock from keyId here as try_into_tempo_tx_env does.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0963527edf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +318 to +320
for owner in &config.owners {
if self.read_stored_owner_weight(account, owner.owner)? != owner.weight {
return Err(NativeMultisigError::invalid_config().into());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject weights for owners absent from the ordered config

When T11 starts from custom-genesis or pre-existing storage containing an otherwise valid ordered config plus a nonzero owner_weights[account][attacker] row, this validation passes because it checks direct weights only for owners present in config.owners. NativeMultisigAuthConfig::owner_weight later trusts the attacker's direct row without checking ordered-list membership, allowing an unconfigured signer to contribute weight. Fresh evidence beyond the earlier stored-config comment is that the new complete-config validation still cannot detect these extraneous direct-weight rows; authorization must establish membership from the ordered config or activation must sanitize them.

Useful? React with 👍 / 👎.

Comment on lines +1062 to +1066
let requires_native_multisig_state = outer_multisig_signature.is_some()
|| key_authorization_multisig_signature.is_some()
|| key_authorization_key_id.is_some()
|| tx.has_fee_payer_signature()
|| has_authorization_list;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include keychain senders in multisig state validation

When a custom genesis or pre-T11 state contains a registered multisig account with bytecode or an EIP-7702 delegation and a valid AccountKeychain key, an ordinary access-key transaction has only an outer keychain signature and therefore leaves requires_native_multisig_state false. The later caller code/delegation check is never reached, so the key executes as the code-bearing multisig account despite the no-code invariant; include keychain senders in the registry/code validation path.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 59d62306a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

impl NativeMultisig {
/// Creates the EVM precompile for this type.
pub fn create_precompile(env: &PrecompileEnv) -> DynPrecompile {
tempo_precompile!("NativeMultisig", env: env, |input| { Self::new() })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return UnauthorizedCaller for indirect config updates

When updateConfig is invoked through DELEGATECALL, this generic precompile wrapper rejects the call before ABI dispatch and returns DelegateCallNotAllowed, while TIP-1061 explicitly requires the UnauthorizedCaller error for an indirect configuration update. Contracts that decode INativeMultisig.UnauthorizedCaller therefore receive an unexpected selector; the native multisig wrapper needs to preserve the specified error for this path.

Useful? React with 👍 / 👎.

pull Bot pushed a commit to Dustin4444/tempo that referenced this pull request Aug 19, 2026
Moves existing primitive and keychain signature gas calculations out of
`handler.rs` into a dedicated internal module without changing gas
values or public behavior. This leaves the handler focused on
orchestration and makes additional signature schemes easier to add.
Extracted while reviewing tempoxyz#4069.
# Conflicts:
#	crates/revm/src/handler.rs
#	crates/revm/src/handler/tests.rs
#	crates/revm/src/signature_gas.rs
#	crates/revm/src/tx.rs
#	crates/transaction-pool/src/tempo_pool.rs
pull Bot pushed a commit to Dustin4444/tempo that referenced this pull request Aug 19, 2026
Marks Reth `eth_simulateV1` transactions as simulations when their
`WithEncoded` wrapper carries the empty envelope used by the
block-simulation pipeline. Applies the same rule to envelope and bare AA
conversions while preserving each transaction’s replay and channel
identity.

Extracted while reviewing tempoxyz#4069.
@joshieDo

joshieDo commented Aug 20, 2026

Copy link
Copy Markdown
Member

continued in a PR stack: #7242

Comment thread tips/tip-1061.md
---

# Specification

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.

could we add some text here explaining at a high-level. basically above you've explained what the idea is, and the threat model etc. next thing i read is types and limits and bootstrap. some more orientation for the humans still reading this manually could be useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cyclops Trigger Cyclops PR audit do not merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants