-
Notifications
You must be signed in to change notification settings - Fork 120
ARC-63 Delegated multisig-account #303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
SudoWeezy
wants to merge
10
commits into
main
Choose a base branch
from
arc-63-Delegated-Msig
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
e771398
Delegated multisig-account controlled by one account
SudoWeezy 4d08e6e
Adding rekey to 0 Address using Msig
SudoWeezy 0cfc5e6
Fix linting
SudoWeezy 921511b
Adding Vault Owner and updating title
SudoWeezy ae05b6c
Update 3Sig to 2Sig, generate random wallet instead of deterministic
SudoWeezy 0c6dc12
Add an application version of the opt-in
SudoWeezy 5b14218
Wording
SudoWeezy 1e1fd0c
Formating + fix + removing the rekey part + mermaid graph
SudoWeezy a26ca7a
Improve Security consideration
SudoWeezy 16418d8
Formating, remove duplicate and fix sections
SudoWeezy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,320 @@ | ||||||||||||||
| --- | ||||||||||||||
| arc: 63 | ||||||||||||||
| title: Lsig Plug-In Signer for Msig Vault | ||||||||||||||
| description: Delegated multisig-account controlled by one account | ||||||||||||||
| author: Stéphane BARROSO (@SudoWeezy) | ||||||||||||||
| discussions-to: https://github.com/algorandfoundation/ARCs/issues/303 | ||||||||||||||
| status: Draft | ||||||||||||||
| type: Standards Track | ||||||||||||||
| category: ARC | ||||||||||||||
| created: 2024-07-16 | ||||||||||||||
| --- | ||||||||||||||
|
|
||||||||||||||
| ## Abstract | ||||||||||||||
|
|
||||||||||||||
| This ARC proposes a method for creating a delegated multisig account controlled by one account and a Logic Signature (Lsig). | ||||||||||||||
|
|
||||||||||||||
| ## Motivation | ||||||||||||||
|
|
||||||||||||||
| The motivation behind this ARC is to extend Algorand account features by enabling third-party "Plug-Ins" using a combination of delegated Lsig and Multi-Signature accounts, which act as vaults. This approach allows anyone to sign the Lsig for the vault, while maintaining security and control through a classic algorand account. | ||||||||||||||
|
|
||||||||||||||
| ## Specification | ||||||||||||||
|
|
||||||||||||||
| The key words "**MUST**", "**MUST NOT**", "**REQUIRED**", "**SHALL**", "**SHALL NOT**", "**SHOULD**", "**SHOULD NOT**", "**RECOMMENDED**", "**MAY**", and "**OPTIONAL**" in this document are to be interpreted as described in <a href = "https://www.ietf.org/rfc/rfc2119.txt">RFC-2119</a> | ||||||||||||||
|
|
||||||||||||||
| ### Components | ||||||||||||||
|
|
||||||||||||||
| 1. **Owner Account**: Vault's Owner | ||||||||||||||
| 1. **Lsig Plug-In**: Provided by a third party. | ||||||||||||||
| 1. **Plug-In Signer**: Created by generating a new key pair | ||||||||||||||
| 1. **1/2 Msig Account**: Comprises the owner's address, and the plug-in signer. | ||||||||||||||
|
|
||||||||||||||
| ### Implementation 1 | ||||||||||||||
|
|
||||||||||||||
| We will use the following Logic plug-in for illustrative purposes: | ||||||||||||||
|
|
||||||||||||||
| **DO NOT USE IN PRODUCTION** | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| teal_program = """ | ||||||||||||||
| #pragma version 10 | ||||||||||||||
| txn TypeEnum | ||||||||||||||
| pushint 4 | ||||||||||||||
| == | ||||||||||||||
| txn AssetAmount | ||||||||||||||
| pushint 0 | ||||||||||||||
| == | ||||||||||||||
| && | ||||||||||||||
| txn RekeyTo | ||||||||||||||
| global ZeroAddress | ||||||||||||||
| == | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, to be even more strict, we could also add this: |
||||||||||||||
| && | ||||||||||||||
| txn Fee | ||||||||||||||
| global MinTxnFee | ||||||||||||||
| == | ||||||||||||||
| && | ||||||||||||||
| return | ||||||||||||||
| """ | ||||||||||||||
| compiled_program = client.compile(teal_program) | ||||||||||||||
| program = base64.b64decode(compiled_program["result"]) | ||||||||||||||
| lsig = transaction.LogicSigAccount(program) | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| > This give opt-in control over the signer | ||||||||||||||
|
|
||||||||||||||
| #### 1. **Generate Plug-In Signer** | ||||||||||||||
|
|
||||||||||||||
| - Generate a random new account that we will only use once. | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| plug_in_sk, plug_in_addr = account.generate_account() | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| #### 2. **Sign Lsig with Plug-In Signer** | ||||||||||||||
|
|
||||||||||||||
| - Sign the Lsig using the plug-in signer. | ||||||||||||||
| - Publish the public signature on the blockchain. | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| public_key, secret_key = nacl.bindings.crypto_sign_seed_keypair(base64.b64decode(plug_in_sk)[: constants.key_len_bytes]) | ||||||||||||||
| message = constants.logic_prefix + program | ||||||||||||||
| raw_signed = nacl.bindings.crypto_sign(message, secret_key) | ||||||||||||||
| crypto_sign_BYTES = nacl.bindings.crypto_sign_BYTES | ||||||||||||||
| signature = nacl.encoding.RawEncoder.encode(raw_signed[:crypto_sign_BYTES]) | ||||||||||||||
| plug_in_public_sig = base64.b64encode(signature).decode() | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| > You can achieve the same result like this: | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| lsig = transaction.LogicSigAccount(program).sign(plug_in_sk) | ||||||||||||||
| plug_in_public_sig = lsig.lsig.sig | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| #### 3. **Create 1/2 Msig Account** | ||||||||||||||
|
|
||||||||||||||
| - Create a multi-signature account with owner address, and the plug-in signer. | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| owner_vault_msig = transaction.Multisig(1,1,[owner_addr, plug_in_addr]) | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| - Add a transaction note to the transaction to help third party to retrieve signer and vault information. | ||||||||||||||
|
|
||||||||||||||
| ```json | ||||||||||||||
| { | ||||||||||||||
| "pk": plug_in_addr, | ||||||||||||||
| "sk": plug_in_public_sig, | ||||||||||||||
| "lsig": lsig.address(), | ||||||||||||||
| } | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| - Prefix the note following the [ARC-2](./arc-0002.md) standard. `arc63:j` | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| ptxn = transaction.PaymentTxn( | ||||||||||||||
| owner_addr, sp, owner_vault_msig.address(), int(1e6), note=f"arc_63:j{note_field}" | ||||||||||||||
| ).sign(owner_sk) | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| #### 4. **Opt-In to Msig Vault** | ||||||||||||||
|
|
||||||||||||||
| - Anyone can opt-in to the Msig vault using the plug-in signer’s public address and the published signature. | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| optin_txn = AssetTransferTxn( | ||||||||||||||
| sender=owner_vault_msig.address(), | ||||||||||||||
| sp=sp, | ||||||||||||||
| receiver=owner_vault_msig.address(), | ||||||||||||||
| amt=0, | ||||||||||||||
| index=a_id, | ||||||||||||||
| ) | ||||||||||||||
| lsig.lsig.msig = owner_vault_msig | ||||||||||||||
| lsig.append_to_multisig(plug_in_sk) # signature from plug_in_public | ||||||||||||||
| lstx = LogicSigTransaction(optin_txn, lsig) | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| ### Implementation 2 | ||||||||||||||
|
|
||||||||||||||
| We will use the following Lsig plug-in for our illustrative purposes: | ||||||||||||||
|
|
||||||||||||||
| **DO NOT USE IN PRODUCTION** | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| teal_program = f""" | ||||||||||||||
| #pragma version 10 | ||||||||||||||
| txn TypeEnum | ||||||||||||||
| int appl | ||||||||||||||
| == | ||||||||||||||
| txn ApplicationID | ||||||||||||||
| int {app_client.app_id} | ||||||||||||||
| == | ||||||||||||||
| && | ||||||||||||||
| txn RekeyTo | ||||||||||||||
| global ZeroAddress | ||||||||||||||
| == | ||||||||||||||
| && | ||||||||||||||
| txn Fee | ||||||||||||||
| global MinTxnFee | ||||||||||||||
| == | ||||||||||||||
| && | ||||||||||||||
| return | ||||||||||||||
| """ | ||||||||||||||
| compiled_program = client.compile(teal_program) | ||||||||||||||
| program = base64.b64decode(compiled_program["result"]) | ||||||||||||||
| lsig = transaction.LogicSigAccount(program) | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| > This allow the application with the id `app_client.app_id` to control the signer | ||||||||||||||
|
|
||||||||||||||
| To be consistant with the previous example, we will use a similar opt-in process. | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| class SmartApp(ARC4Contract): | ||||||||||||||
| def __init__(self) -> None: | ||||||||||||||
| self.db = BoxMap(Account, String, key_prefix="") | ||||||||||||||
|
|
||||||||||||||
| @abimethod() | ||||||||||||||
| def opt_in(self, id: UInt64, account: Account) -> None: | ||||||||||||||
| itxn.AssetTransfer( | ||||||||||||||
| asset_amount=0, | ||||||||||||||
| xfer_asset=id, | ||||||||||||||
| sender=account, | ||||||||||||||
| asset_receiver=account, | ||||||||||||||
| fee=1000, | ||||||||||||||
| ).submit() | ||||||||||||||
|
|
||||||||||||||
| @abimethod() | ||||||||||||||
| def set_public_sig(self, account: Account, sig: String) -> bool: | ||||||||||||||
| self.db[account] = sig | ||||||||||||||
| return self.db[account] == sig | ||||||||||||||
|
|
||||||||||||||
| @abimethod(readonly=True) | ||||||||||||||
| def get_public_sig(self, account: Account) -> String: | ||||||||||||||
| return self.db[account] | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| ### 1. [Generate Plug-In Signer](./arc-0063.md#1-generate-plug-in-signer) | ||||||||||||||
|
|
||||||||||||||
| ### 2. [Sign Lsig with Plug-In Signer](./arc-0063.md#2-sign-lsig-with-plug-in-signer) | ||||||||||||||
|
|
||||||||||||||
| #### 3. **Create 1/2 Msig Account** and execute an app Call | ||||||||||||||
|
|
||||||||||||||
| - Create a multi-signature account with owner address, and the plug-in signer. | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| owner_vault_msig = transaction.Multisig(1,1,[owner_addr, plug_in_addr]) | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| - Publish the public signature by using set_public_sig to the app. | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| response = app_client.set_public_sig( | ||||||||||||||
| account=owner_addr, | ||||||||||||||
| sig=plug_in_public_sig, | ||||||||||||||
| transaction_parameters=algokit_utils.TransactionParameters( | ||||||||||||||
| boxes=[(app_client.app_id, encoding.decode_address(owner_addr))], | ||||||||||||||
| accounts=[owner_addr] | ||||||||||||||
| ) | ||||||||||||||
| ) | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| - Get the transaction set_public_sig from the app. | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| response = app_client.get_public_sig( | ||||||||||||||
| account=owner_addr, | ||||||||||||||
| transaction_parameters=algokit_utils.TransactionParameters( | ||||||||||||||
| boxes=[(app_client.app_id, encoding.decode_address(owner_addr))] | ||||||||||||||
| ), | ||||||||||||||
| ) | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| #### 4. **App in control of Msig Vault** | ||||||||||||||
|
|
||||||||||||||
| - Anyone can now call the app to opt-in any asset to the Msig vault using the plug-in signer’s public address and the published signature. | ||||||||||||||
|
|
||||||||||||||
| ```python | ||||||||||||||
| composer.opt_in( | ||||||||||||||
| id=a_id, | ||||||||||||||
| account=app_client.app_address, | ||||||||||||||
| transaction_parameters=algokit_utils.TransactionParameters( | ||||||||||||||
| foreign_assets=[a_id], signer=app_client.signer, | ||||||||||||||
| sender=owner_vault_msig.address() | ||||||||||||||
| ), | ||||||||||||||
| ) | ||||||||||||||
| opt_in_txn = composer.atc.txn_list[0].txn | ||||||||||||||
|
|
||||||||||||||
| lsig.lsig.msig = owner_vault_msig | ||||||||||||||
| lsig.lsig.msig.subsigs[1].signature = base64.b64decode(plug_in_public_sig) | ||||||||||||||
| lstx = transaction.LogicSigTransaction(opt_in_txn, lsig) | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| ### Diagram | ||||||||||||||
|
|
||||||||||||||
| ```mermaid | ||||||||||||||
| graph TD | ||||||||||||||
| subgraph signer | ||||||||||||||
| PKS[PK] | ||||||||||||||
| SKS[SK] | ||||||||||||||
| PKS ~~~ SKS | ||||||||||||||
| end | ||||||||||||||
| subgraph msigVault | ||||||||||||||
| SKVS[Public Signature] | ||||||||||||||
| SKVA[Public Address] | ||||||||||||||
| PKV[ownerPK] | ||||||||||||||
| PKV ~~~ SKVA | ||||||||||||||
| PKV ~~~ SKVS | ||||||||||||||
| end | ||||||||||||||
| subgraph owner | ||||||||||||||
| PKO[PK] | ||||||||||||||
| end | ||||||||||||||
|
|
||||||||||||||
| ta[[Throwaway Account]] | ||||||||||||||
|
|
||||||||||||||
| lsig((Lsig Plug In)) | ||||||||||||||
|
|
||||||||||||||
| sign((Sign)) | ||||||||||||||
|
|
||||||||||||||
| ta --> signer | ||||||||||||||
| PKO --> PKV | ||||||||||||||
| SKS --> sign | ||||||||||||||
| lsig --- sign | ||||||||||||||
| sign --> SKVS | ||||||||||||||
| PKS --> SKVA | ||||||||||||||
| msigVault ~~~ lsig | ||||||||||||||
| ``` | ||||||||||||||
|
|
||||||||||||||
| ## Rationale | ||||||||||||||
|
|
||||||||||||||
| The rationale for this design is to leverage third-party Lsig plug-ins. By note storing the plug-in signer private key, we mitigate risks associated with its misuse, while the multi-signature account setup ensures controlled access and flexibility in asset management. | ||||||||||||||
|
|
||||||||||||||
| ## Backwards Compatibility | ||||||||||||||
|
|
||||||||||||||
| This ARC introduces no backward incompatibilities. It builds upon existing Algorand functionalities, ensuring seamless integration with current systems. | ||||||||||||||
|
|
||||||||||||||
| ## Reference Implementation | ||||||||||||||
|
|
||||||||||||||
| An example implementation in Python is provided, demonstrating the creation of a plug-in signer, signing an Lsig, and opting into a multi-signature vault. | ||||||||||||||
|
|
||||||||||||||
| ### Only with Lsig | ||||||||||||||
|
|
||||||||||||||
| [Create_Opt_in_Plug_in](../assets/arc-0063/create_plugin.py) | ||||||||||||||
|
|
||||||||||||||
| ### Delegating the Lsig to an App | ||||||||||||||
|
|
||||||||||||||
| [Deploy config](../assets/arc-0063/deploy_config.py) | ||||||||||||||
| [Contract](../assets/arc-0063/contract.py) | ||||||||||||||
|
|
||||||||||||||
| > This need to be run with Algokit | ||||||||||||||
|
|
||||||||||||||
| ## Security Considerations | ||||||||||||||
|
|
||||||||||||||
| Even if the plug-in signer is rekeyed, the private key can still sign new lsigs, which is why the private key should not be accessible by anyone after the signature. | ||||||||||||||
| This step is crucial to prevent any unauthorized use of the signer post-creation. | ||||||||||||||
|
|
||||||||||||||
| If the Multi-signature vault accounts is rekeyd to any other account (Msig or traditional), it will keep the same public address and will not be delegated anymore. | ||||||||||||||
|
|
||||||||||||||
| ## Copyright | ||||||||||||||
|
|
||||||||||||||
| Copyright and related rights waived via <a href="https://creativecommons.org/publicdomain/zero/1.0/">CCO</a>. | ||||||||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| from algopy import ARC4Contract, Account, UInt64, itxn, BoxMap | ||
| from algopy.arc4 import abimethod, String | ||
|
|
||
|
|
||
| class SmartApp(ARC4Contract): | ||
| def __init__(self) -> None: | ||
| self.db = BoxMap(Account, String, key_prefix="") | ||
|
|
||
| @abimethod() | ||
| def opt_in(self, id: UInt64, account: Account) -> None: | ||
| itxn.AssetTransfer( | ||
| asset_amount=0, | ||
| xfer_asset=id, | ||
| sender=account, | ||
| asset_receiver=account, | ||
| fee=1000, | ||
| ).submit() | ||
|
|
||
| @abimethod() | ||
| def set_public_sig(self, account: Account, sig: String) -> bool: | ||
| self.db[account] = sig | ||
| return self.db[account] == sig | ||
|
|
||
| @abimethod(readonly=True) | ||
| def get_public_sig(self, account: Account) -> String: | ||
| return self.db[account] |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.