I’ve written an adaptation of Arthur Breitman’s Generic Multisig Contract: a Multisig Wrapper Contract.
Blog post explaining Multisig and the Wrapped Multisig contract
TL;DR: A multisig contract requires a quorum of signers to execute operations. The Generic Multisig (in Tezos) acts like a user who can do anything (i.e. accept arbitrary code) as long as it’s signed by its quorum. The Multisig Wrapper enables a multisig to be specialized to a particular contract, limiting its functionality while retaining security and refining the user experience of signers.
A technical guide to the contract and its differences with the generic multisig
Example Wrapped Multisig, specialized to input and output types
- The Multisig Wrapper contract will need some changes to take full advantage of Babylon.
For example, Babylon removes the restriction on multiple
big_map's and supports entrypoints.
- The current version of Michelson does not support
SELF inside of lambda’s.
This means that contracts that include
SELF cannot be wrapped.
- All of the wrapped contract’s parameters require a quorum of signers to be called.
This means that even if a contract including
SELF were able to be wrapped,
calling back to itself would likely require a quorum of signers.
Is there a good way to use this to enforce multisig requirements on some parameters but not others?
Otherwise, a good follow-up contract could support different signers/thresholds for different parameters.
One of the biggest challenges to making multisig user-friendly is the off-chain coordination, i.e. collecting the signatures from each signer before submitting the parameter(s). What applications of multisig would benefit the most from on-chain, off-chain-public, off-chain-private signature and/or parameter management?
How would you have implemented it differently?
What features would be needed to make this production-ready for your application?
I think it will be a bit misleading to say that it can be used with
almost any contract.
For example, I think that almost any contract using AMOUNT will become broken when wrapped, due to the wrapper’s addition of a default entry point, and refusal of tez in the main entry point.
Even contracts which only assert AMOUNT=0 may be broken, by the addition of the default entry point, which does not have this assertion.
Even contracts which do not use AMOUNT can be broken by the addition of a default entry point. Perhaps they always validate SENDER, for example.
So, general questions:
- If I prove that my contract satisfies a certain spec, what spec will the multisig wrapper satisfy? Is user creativity required here to formulate the new spec, or can the multisig’s spec be induced automatically from the wrapped contract’s spec? Do I need to prove anything extra or will the multisig automatically satisfy its spec?
- When will the multisig’s spec be meaningful/useful? When will it be nonsense? (For example, when the wrapped contract uses AMOUNT?)
- Generally, how can users understand correctness of the multisig? I think, with the current design, the wrapped ‘contract’ must be designed or audited specifically for being wrapped in this way, by an expert who understands Michelson, the wrapped contract, and the wrapper.
- Are there design changes which might better preserve the wrapped contract’s semantics, making the above questions easier to answer? For example, my current vague ideas are:
- Don’t refuse tez in the main entry point.
- Don’t add a default entry point.
- Allow user to lift selected entry points to the top-level (no multisig.) If they want a default entry point, they have to lift it from the wrapped contract.
- Include AMOUNT, SENDER, SOURCE in the signed data, at least if the wrapped contract uses them. (?)
What other subtleties have I missed, I wonder?
Yes, the wrapped contract will force
AMOUNT to be zero in the main entry point.
Thus it seems that asserting
AMOUNT = 0 will not break, but requiring that
AMOUNT can be non-zero in a main entry point will break.
Indeed, the default entry point could conflict with what’s expected by the base contract.
A better version of the Multisig Wrapper could individually assign a threshold and signers list to each entry point. This would allow us to:
- Omit the default entry point
- Accept tez in the main entry point
Answers to your general questions:
- The spec of a multisig-wrapped contract (assuming the wrapping completes, so
SELF is not included, etc.) should be roughly:
- Can only send tez to the default entry point
- There is a change keys/threshold entrypoint that behaves as expected
- All other ways of calling the contract require a quorum of valid signatures
- Main-parameter calls are equivalent to calls to the base contract with some differences:
- The signatures and counter must be included in each call
AMOUNT is always zero
- Callbacks to the contract must match the wrapped entry points
- The derived spec should only be nonsense when when the base contract cannot be wrapped, but it may have entry points that are impossible to call, e.g. if they require
AMOUNT to be non-zero.
- I expect
STEPS_TO_QUOTA to behave differently, but it’s disabled in Babylon
A possible way to allow calling the wrapped contract with an original contract’s entry point is to include pre-authorized entry points:
- Each entry point from the base contract is associated with:
withAuthorizationEntryPoint: provide authorization with parameters, as is currently done
preAuthorizeEntryPoint: submit authorization for particular parameters to be sent to the entry point
preAuthorizedEntryPoint: call the entry point, using pre-authorized parameters and failing if not pre-authorized
- Assuming Babylon, the wrapped contract will be able to call back to itself using its original entry points, but the callback will fail unless the parameters have been pre-authorized
- If this idea is combined with per-entry-point permissions and a workaround to handle
SELF, the resulting spec should be very close to: “the original spec, but entry points have permission requirements and additional entry points are included to give permission”.