Skip to content

Add multisig propose approve for a multisig that signs another multisig - #189

Open
exocognosis wants to merge 2 commits into
Quantus-Network:mainfrom
exocognosis:exocognosis/nested-multisig-approve
Open

exocognosis wants to merge 2 commits into
Quantus-Network:mainfrom
exocognosis:exocognosis/nested-multisig-approve

Conversation

@exocognosis

@exocognosis exocognosis commented Oct 20, 2026 •

Copy link
Copy Markdown

Recovery half of Quantus-Network/quantus-apps#708.

A multisig has no key, so as a signer of another multisig it cannot call approve. Its vote is Multisig::approve(target, proposal_id, call) dispatched with the member multisig as origin when one of the member's proposals executes. approve takes the target proposal's stored call, so the approval is bound to it.

propose custom cannot build that call (build_runtime_call only encodes Balances transfers and System remark), so recovering this setup meant hand-encoding call bytes.

quantus multisig propose approve --address --target --proposal-id --from

  • checks: proposer signs the member, member signs the target, target proposal exists, has not expired, and is not already approved by the member
  • fetches the target proposal's stored call and wraps it in Multisig::approve
  • default expiry is head + ~2h capped at the target proposal's expiry; an explicit later expiry warns
  • the rest is the existing approve and execute on the member, then execute on the target

Weight: approve(MaxCallSize) is about 23e9 ref time and 17 KB proof against MaxInnerCallWeight of 1e12 and 2.6 MB, so it passes the propose weight check. A high-security member multisig cannot propose it (HS whitelist; propose burns the fee on rejection), so the command refuses that case before submitting.

Tests: the call decodes back to Multisig::approve with the target, id and stored bytes, and uses the metadata's pallet and call indices. cargo test and clippy pass.
README: new section under the multisig commands.

Ran end to end against a local dev node (v1.0.2-Qm); results in the comment below. High-security refusal and the expiry warning not exercised live.

Side note: README still documents quantus multisig dissolve and a deposit "returned on dissolution". There is no dissolve call in the pallet or the bindings. Left alone here.

A multisig has no key, so when it is a signer of another multisig it
cannot call approve. Its vote is Multisig::approve dispatched from its
own account when one of its proposals executes. propose custom cannot
build that call, so recovering such a setup meant hand-encoding it.

propose approve checks that the proposer signs the member multisig,
that the member signs the target, and that the target proposal exists,
is live and is not already approved by the member. It fetches the
target proposal's stored call to bind the approval, and caps the
default expiry at the target proposal's expiry.

Refs Quantus-Network/quantus-apps#708
@exocognosis

Copy link
Copy Markdown
Author

Ran it end to end on a local dev chain (quantus-node v1.0.2-Qm, spec 153), same shape as the incident:

  • inner = 2-of-2 [alice, bob]; outer = 2-of-2 [inner, charlie]; outer funded with 100
  • charlie: propose transfer 10 on outer (1 of 2 approvals)
  • alice: multisig propose approve --address <inner> --target <outer> --proposal-id 0
  • bob: approve inner #0; alice: execute inner #0
  • outer #0 now Approved, approvals [charlie, inner]
  • charlie: execute outer #0: Balances.Transfer 10 to charlie, outer balance 100 to 90, ProposalExecuted result Ok(())

Refusals, all before submitting: member not a signer of target, proposal already executed, proposer not a signer of the member.

Not covered live: the high-security refusal and the expiry warning.

@exocognosis

Copy link
Copy Markdown
Author

With #13 in, the follow-through set from the October incident is complete on my side: #708 tracks it, #715 is the creation guard, #189 here is the recovery path, and #13 closes the import gap underneath them.

I do this kind of work as scoped engagements. If the wallet and CLI security follow-through is worth scoping as a two-week piece on your side, I am happy to walk through it on a short call. Is there a better channel than issue threads for that, email or Discord?

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.

1 participant