Skip to content
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

ARC-63 Delegated multisig-account #303

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

Conversation

SudoWeezy
Copy link
Collaborator

@SudoWeezy SudoWeezy commented Jul 16, 2024

This ARC proposes creating delegated multisig-account control by a single account and a logic signature.

The motivation behind this ARC is to extend Algorand account features by enabling third-party "Plug-Ins" using a combination of delegated logic signatures (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 over the account.

This ARC proposes creating a delegated multisig-account control only by one account and a Logic signature.

The motivation behind this ARC is to extend algorand account feature by enabling third-party "Plug-Ins" using a combinaison 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 deterministic wallet and rekeying mechanisms.
@SudoWeezy SudoWeezy changed the title Delegated multisig-account controlled by one account ARC-63 Delegated multisig-account controlled by one account Jul 16, 2024
ARCs/arc-0063.md Outdated Show resolved Hide resolved
ARCs/arc-0063.md Show resolved Hide resolved
@HashMapsData2Value
Copy link

Regarding line 18, there are two issues. The small one is that it's not clear if signatures are "well distributed". We want the seed to be randomly sampled from a uniform distribution and signatures are not, particularly the last 32 bytes of a signature corresponding to group element R.

The bigger is that an adversary has access to both the LSigs address and the user's address. They can concatenate the two, and if they somehow can get the user to sign that (something that will be easier as we arbitrary signing becomes generally available), they now have access to the seed used to generate the new key pairs.

An improvement would be to do a type of one-sided Elliptic Curve Diffie Hellman (ECDH) with the Sig address.

So the user takes the LSig's address and does a scalar multiplication of it to get a new 32 byte "address" which serves as a shared secret. (Were the LSig an actual entity owning the secret key/scalar behind the LSig address, it could do the equivalent of this with the user's address.)

Once the shared secret has been calculated, a more secure way of generating the seed for the new key pair would be to do Hash(shared_secret || LSig_address || user_address). This can now be used to generate a new key pair.

--

However, if we take a step back, the point of this is to deterministically generate a new key pair, such that in case a user brings their seed to a new wallet, they can re-generate the key pair used for interacting with the LSig. This ties into an idea I had with HD-wallets.

For ARC 52 we use BIP44 which has the following (hierarchical) derivation paths:

m / purpose' / coin_type' / account' / change / address_index

(This is also explained in the paper we wrote with Chris P.)

In the traditional (Bitcoin) standard, change is limited to either 0 or 1. By default, since Algorand is not a uTXO but rather balance-based blockchain, we leave it to 0.

I had the idea that we could possibly use those 2^32 - 1 (change = 0 left dedicated to normal Algo txn:s) to specifically be used with specific applications. If we map them 1-to-1, each application then has 2^32 child addresses available to them.

2^32 is about 4.3 billion. If we think there is still risk of overlap - and we only want 1 LSig to allow you to generate 1 address per account' (according to the BIP-44 definition) - we could use change + address_index to get ~2^64 spots, around 1.8*10^19.

@SudoWeezy
Copy link
Collaborator Author

SudoWeezy commented Jul 17, 2024

Regarding line 18, there are two issues. The small one is that it's not clear if signatures are "well distributed". We want the seed to be randomly sampled from a uniform distribution and signatures are not, particularly the last 32 bytes of a signature corresponding to group element R.
...
2^32 is about 4.3 billion. If we think there is still risk of overlap - and we only want 1 LSig to allow you to generate 1 address per account' (according to the BIP-44 definition) - we could use change + address_index to get ~2^64 spots, around 1.8*10^19.

The more I think about it, the less I think a deterministic account is needed. It introduce unnecessary risks
As long as we share the information in the note field when creating the transaction, it should be good enough for wallets and dApps to resolve the account.
So maybe the simple approach of just generating a brand new account is good enough.

@SudoWeezy
Copy link
Collaborator Author

SudoWeezy commented Jul 19, 2024

After thinking more about it, I removed the rekey part, it does not improve the security because anyone with the plug-in private key can still sign LSIGs inside the Multisig Account.

So, if there are any concerns, the Multisig Account should be rekeyed to something else, either a normal account or another multi sig account. After this rekey, the Account will not be delegated anymore

@SudoWeezy SudoWeezy changed the title ARC-63 Delegated multisig-account controlled by one account ARC-63 Delegated multisig-account Jul 19, 2024
Copy link
Contributor

@nullun nullun left a comment

Choose a reason for hiding this comment

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

Am I right in thinking this update now includes two distinct implementation designs and one will win out, or are they both being proposed as optional ways to achieve it?

I still don't think I'm onboard with either concept, but maybe I need additional examples of how this would be used beyond just opting in someones vault into an ASA.

I'd probably favour designing a simple interface that applications can implement and users can deploy from their own accounts. The complexities and increased risks involved with stateless contracts and generating delegate signatures just feels a bit archaic and messy given the features we have in the AVM today.

ARCs/arc-0063.md Outdated
Comment on lines 77 to 82
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()
Copy link
Contributor

Choose a reason for hiding this comment

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

This is creating a signature based on the logic signature program right? Aka producing a delegate signature for the newly generated account from step 1.

Is it worth using the Algorand SDK (LogicSigAccount(program).sign(secret_key)) in this example instead of directly using nacl? I suspect it would be clearer to our demographic.

Copy link
Collaborator Author

@SudoWeezy SudoWeezy Jul 20, 2024

Choose a reason for hiding this comment

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

From my understanding, this (LogicSigAccount(program).sign(secret_key)) method does not return the public signature def sign(self, private_key: str) -> None:, it will instead store it inside an lsig object like this:
lsig = transaction.LogicSigAccount(program).sign(secret_key)

But to extract the public signature, we shoud do the following:
plug_in_public_sig = lsig.lsig.sig
It works but it seems a bit more "magical"

But you are right, I will add it for reference, so it is easier to follow what I am doing here.

ARCs/arc-0063.md Outdated Show resolved Hide resolved
ARCs/arc-0063.md Outdated Show resolved Hide resolved
ARCs/arc-0063.md Outdated Show resolved Hide resolved
ARCs/arc-0063.md Outdated
Comment on lines 236 to 237
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I understand this section. The msig account hasn't been rekeyed to the application, so how does the application control the multisig vault at this point? All it has is a list of addresses to signatures, which haven't been verified to be accurate, presumably because it's an early design? Or have I misunderstood it, and this application is just there to provide a mapping service between address and signature?

Copy link
Collaborator Author

@SudoWeezy SudoWeezy Jul 20, 2024

Choose a reason for hiding this comment

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

With the signature plug_in_public_sig of the plug_in_addr that is now stored on the app thanks to the set_public_sig() method, anyone can get this value by looking at the box/ calling the get_public_sig() method.

{
    ...
    inner-txns:[...],
    signature:{
    logicsig:{
    logic:"CjEQgQYSMRiB7QcSEDEgMgMSEDEBMgASEEM=",
    multisig-signature:{
        version:1,
        threshold:1,
        subsignature:[
                {...},
                {
                    public-key:"24RB9iNUHfFDelIYDnw9URA2Pf6TYz8vg/vom9sUwAI=",
                    signature:"iPu354yel81lxlJTlFPG34AxDVR5Bc1MLL1zRwWwK7zM2p4Ehkf5ktOH2H67avgO31a0v8ejr/DL328jNAj4BA=="
                }
            ]
        }
    }
}

image

As plug_in_addr signed the teal_program, it is enough to allow an inner transaction (binded into a LogicSigTransaction) doing operations on the msig account.
To recontruct a multisignature to execute the application call an opt in on behalf of the user, we can just execute the code bellow.

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)

@SudoWeezy
Copy link
Collaborator Author

SudoWeezy commented Jul 20, 2024

Am I right in thinking this update now includes two distinct implementation designs, and one will win out, or are they both being proposed as optional ways to achieve it?

I would rather see it as one implementation (delegated account inside msig account) being used in two different ways

I still don't think I'm onboard with either concept, but maybe I need additional examples of how this would be used beyond just opting in someones vault into an ASA.

Sure, all of this can be done using an app with rekeying, etc.

But I try to look at it from a regulation perspective. With MICA coming, more projects will need to be regulated.
Is having an app that rekeys my account the same as giving this app the right to take some actions on my account directly?
Does saying this user received this Asa to this app but needs to claim it has the same meaning for an accountant/layer as he received it directly to his account?

We know behind the scenes it's just key/pair mapping anyway, but most of the time, regulators don't want to bother with technicalities

In terms of raw cost/memory usage, you can end up spending more by using a bunch of boxes/apps for something that could have been done differently.

I still think it is worth exploring just for the sake of it, and it can still be used as a reference when someone wants to know what the pros/cons and the risks involved in using lsig

&&
txn RekeyTo
global ZeroAddress
==

Choose a reason for hiding this comment

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

Suggested change
==
==
&&
txn AssetCloseTo
global ZeroAddress
==

Copy link
Collaborator Author

@SudoWeezy SudoWeezy Oct 1, 2024

Choose a reason for hiding this comment

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

Good catch, to be even more strict, we could also add this:

==
global GenesisHash
byte base64(wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=)
==
&&
==
&&
txn FreezeAssetAccount
global ZeroAddress
==

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

Successfully merging this pull request may close these issues.

4 participants