Skip to content

test(node): cover TIP-1061 multisig flows - #7241

Draft
joshieDo wants to merge 2 commits into
agent/native-multisig-poolfrom
agent/native-multisig-integration
Draft

test(node): cover TIP-1061 multisig flows#7241
joshieDo wants to merge 2 commits into
agent/native-multisig-poolfrom
agent/native-multisig-integration

Conversation

@joshieDo

@joshieDo joshieDo commented Aug 19, 2026

Copy link
Copy Markdown
Member

Adds end-to-end TIP-1061 examples, RPC fill/sign/send coverage, and updated gas-estimation snapshots.

Stacked on #7240; completes the extraction from reference PR #4069.

@joshieDo
joshieDo force-pushed the agent/native-multisig-integration branch 2 times, most recently from fb8f1f8 to 3165b5a Compare August 19, 2026 15:33
@joshieDo
joshieDo force-pushed the agent/native-multisig-integration branch from 3165b5a to a47745c Compare August 19, 2026 16:53
@joshieDo
joshieDo force-pushed the agent/native-multisig-integration branch from a47745c to 1d7c0e3 Compare August 19, 2026 17:05
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

📊 Tempo Precompiles Coverage

precompiles

Coverage: 6457/11165 lines (57.83%)

File details
File Lines Coverage
src/account_keychain/dispatch.rs 16/42 38.10%
src/account_keychain/mod.rs 315/899 35.04%
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 126/152 82.89%
src/error.rs 52/173 30.06%
src/ip_validation.rs 10/10 100.00%
src/lib.rs 128/148 86.49%
src/native_multisig/auth.rs 0/179 0.00%
src/native_multisig/dispatch.rs 0/15 0.00%
src/native_multisig/mod.rs 3/348 0.86%
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 1001/1097 91.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/440 88.64%
src/storage/hashmap.rs 0/249 0.00%
src/storage/mod.rs 27/69 39.13%
src/storage/packing.rs 68/93 73.12%
src/storage/thread_local.rs 200/300 66.67%
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 57/155 36.77%
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/57 0.00%

contracts

Coverage: 1/202 lines (0.50%)

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/27 0.00%

Total: 6458/11367 lines (56.81%)

📦 Download full HTML report

@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

Cyclops Review — PR #7241

Summary

PR #7241 adds real-node integration coverage for the canonical TIP-1061 multisig examples and does not modify production code. The review found two low-severity, pre-existing gaps in the TIP-1061 implementation exercised by these tests: an order-dependent access-key invariant and incomplete transaction-pool revalidation after multisig initialization; neither finding received a separate verifier result.

Findings

🛡️ [DEFENSE-IN-DEPTH] Multisig access-key prohibition is order-dependent

Severity: Low
File: crates/precompiles/src/native_multisig/mod.rs:229
Summary: AccountKeychain::authorize_key_internal rejects an address only if it is already a multisig (crates/precompiles/src/account_keychain/mod.rs:271), but NativeMultisig::store_initial_config does not perform the reverse check. An attacker can therefore register a not-yet-bootstrapped derived multisig address as an access key and bootstrap it afterward, leaving state where the address is both a registered multisig and an active key for another account. Current signature validation prevents a demonstrated privilege escalation, but the state violates TIP-1061's hard invariant and is unsafe for future consumers that rely on key-registration state.
Recommended Fix: Track active key registrations by key_id and reject multisig bootstrap when the derived account is registered for another account, including an activation strategy for registrations created before T11. If reverse enforcement is intentionally unsupported, narrow the TIP invariant to explicitly permit this ordering instead.

⚠️ [ISSUE] Multisig initialization misses transactions gated by key_authorization.key_id

Severity: Low
File: crates/transaction-pool/src/transaction.rs:228
Summary: TempoPooledTransaction::multisig_accounts() indexes multisig signature accounts and nested owners, but omits key_authorization.key_id. Consensus validation rejects that key ID once it becomes a registered multisig (crates/revm/src/handler.rs:1133), while pool maintenance revalidates only transactions whose indexed accounts match a MultisigInitialized event (crates/transaction-pool/src/maintain.rs:293). A transaction admitted before its key ID is bootstrapped can consequently remain stale in the pool instead of being revalidated and evicted.
Recommended Fix: Include key_authorization.authorization.key_id in the registry-dependency set used by affects_multisig_transaction, and add a pool-maintenance test covering admission before MultisigInitialized followed by revalidation.

Reviewer Callouts

Reviewer Callouts
  • RPC simulation hints (crates/node/src/rpc/mod.rs:388): Hint generation recursively loads owner configs and runs allocation-heavy selection before gas or affordability checks. A 48-owner-of-48-owner account can amplify unauthenticated simulation requests; review whether per-block caching or a bound based on signature_count is needed.
  • Registry-gating reads (crates/precompiles/src/native_multisig/mod.rs:192): is_multisig_account loads the complete stored config rather than only the header, adding up to twice the owner count in storage reads beyond TIP-1061's documented header-read model. Reconcile the implementation and gas specification.
  • Keychain signature cache (crates/primitives/src/transaction/tt_signature.rs:506): KeychainSignature::key_id is cached without keying the cache by sig_hash. No current multi-hash caller was identified, but an assertion or input-keyed cache would prevent future stale recovery results.

@joshieDo
joshieDo force-pushed the agent/native-multisig-integration branch from 1d7c0e3 to 334ecfa Compare August 19, 2026 19:08
@joshieDo
joshieDo force-pushed the agent/native-multisig-integration branch from 334ecfa to db4d774 Compare August 19, 2026 21:02
@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

PR #7241 adds end-to-end TIP-1061 native-multisig coverage. No security or correctness defect was found in the changed test files; review of the underlying stacked implementation identified two unverified, low-severity issues that are not introduced by this PR.

🛡️ [DEFENSE-IN-DEPTH] Stateless multisig shape validation accepts disallowed keychain approvals

File: crates/primitives/src/transaction/multisig.rs:684 (not in this PR's diff)

MultisigSignature::validate_shape() applies the owner-signature size limit only through TempoSignature::as_primitive(), so decoded Keychain approvals bypass both the TIP-1061 Primitive-or-Multisig type restriction and the per-approval byte cap. Stateful authorization rejects keychain approvals before quorum, but stateless callers inherit a weaker contract and always-invalid transactions can carry oversized approvals.

Recommended Fix: Reject TempoSignature::Keychain in validate_shape() and enforce MAX_MULTISIG_OWNER_SIGNATURE_BYTES for primitive approvals.

⚠️ [ISSUE] Native-multisig RPC simulation hint construction performs superlinear work

File: crates/node/src/rpc/mod.rs:519 (not in this PR's diff)

RPC simulation recursively loads a hint for every owner before running a dynamic program whose transitions clone approval vectors. At supported owner and nesting limits, one unauthenticated simulation request can trigger millions of deep clones and thousands of state reads, creating avoidable public-RPC CPU amplification.

Recommended Fix: Memoize hints by account, restrict nested resolution to quorum candidates, and use predecessor indices instead of cloning approval vectors during DP transitions.

Reviewer Callouts
  • RPC simulation context boundary: Confirm every committing path constructs ExecutionContext::Transaction; a future path using the simulation-valued default would skip quorum verification.
  • Rejected-transaction CPU policy: Decide whether pool policy should account for funded multisig transactions that perform many P256/WebAuthn checks before a final invalid approval rejects them without gas charge or sender penalty.
  • End-to-end negative coverage: Add same-transaction updateConfig and access-key owner-rotation rejection cases to cover the transient guards through the real handler.

@joshieDo
joshieDo force-pushed the agent/native-multisig-integration branch 2 times, most recently from 5edc0f1 to 986ca13 Compare August 20, 2026 13:40
@joshieDo
joshieDo force-pushed the agent/native-multisig-integration branch from 986ca13 to 6503c45 Compare August 20, 2026 13:56
@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"

@joshieDo
joshieDo force-pushed the agent/native-multisig-integration branch from 6503c45 to c1e8883 Compare August 20, 2026 14:31

@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

PR #7241 adds real-node integration coverage for TIP-1061 and changes only test files. The test changes are well scoped, but the production paths they exercise retain two pre-existing defense-in-depth weaknesses on the current head. These production files are outside the PR diff, so the findings are body-only.

🛡️ [DEFENSE-IN-DEPTH] Multisig authorization fails open for a default or empty-encoded execution context

Severity: Medium
File: crates/revm/src/tx.rs:80

ExecutionContext::Simulation is the default for TempoTxEnv, and empty EIP-2718 bytes also select it (tx.rs:467-470). In T11 validation, this is the sole guard around owner-quorum verification (crates/revm/src/handler.rs:1326-1354, 1440-1451). A future consensus, replay, or tracing path that uses a default environment or lacks encoded bytes could execute a multisig transaction without checking owner membership, threshold, or digest binding. Current canonical paths explicitly use ExecutionContext::Transaction, so no live exploit path was established.

Recommended Fix: Make the secure transaction context the default or require explicit construction, stop inferring multisig simulation from empty encoding, and reject simulation contexts at the canonical block-execution boundary.

🛡️ [DEFENSE-IN-DEPTH] The per-transaction multisig bootstrap marker is not explicitly reset

Severity: Low
File: crates/revm/src/handler.rs:429

Transaction setup resets the keychain transaction key and seeds precompile origins, but does not clear NativeMultisig.bootstrapped_account. Revm currently clears transient storage at finalization, so this is fail-closed; journal reuse or a future lifecycle change could retain the marker and incorrectly reject a later legitimate configuration update.

Recommended Fix: Explicitly clear bootstrapped_account in seed_precompile_tx_context.

Reviewer Callouts
  • Canonical executor boundary: Confirm against pinned upstream Reth that block re-execution never constructs an AA WithEncoded transaction with empty bytes.
  • EIP-7702 ordering: Pin authorization-list application versus caller-state validation ordering with a test.
  • Multisig gas accounting: Confirm that charging config-validation gas for both signatures when outer and key-authorization signatures name the same caller is intentional.

@joshieDo
joshieDo force-pushed the agent/native-multisig-integration branch from c1e8883 to d6904cb Compare August 20, 2026 15:26
@joshieDo
joshieDo force-pushed the agent/native-multisig-integration branch from d6904cb to 7cd9e8b Compare August 20, 2026 16:12
@joshieDo
joshieDo force-pushed the agent/native-multisig-integration branch from 7cd9e8b to b1fdef0 Compare August 20, 2026 16:28
@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

Cyclops Review — PR #7241

Summary

PR #7241 adds real-node integration tests for TIP-1061 and changes no production code. No defect was found in the changed tests; after removing findings already covered by prior Cyclops reviews, one low-severity, pre-existing robustness issue remains in the underlying RPC simulation-hint implementation.

Findings

⚠️ [ISSUE] Simulation-hint selection panics on a zero threshold

Severity: Low
File: crates/node/src/rpc/mod.rs:558
Summary: select_native_multisig_simulation_approvals allocates each state row with threshold entries and then unconditionally writes states[0][0]. A zero-threshold config therefore causes an index-out-of-bounds panic. Both current callers validate the config first, so this is not presently reachable through RPC input, but the private helper's unchecked precondition makes a future caller or validation-order change a worker-panic footgun.
Recommended Fix: Reject config.threshold == 0 at the start of select_native_multisig_simulation_approvals before allocating or indexing the state table.

Reviewer Callouts

Reviewer Callouts
  • Nested quorum reachability: Config validation considers the top eight direct owner weights but cannot detect a nested owner cycle whose quorum depends on the parent account. Review whether initialization and updates should prevent or explicitly document configurations that can permanently make their own update quorum unreachable.

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.

2 participants