Multichain friendly account abstraction

The goal of Polkadot is always a multichain project. i.e. While the relaychain is the core of Polkadot, system parachains / common good parachains are also an essential component of the Polkadot. In fact, the end goal of Polkadot is that the realychain should not need to process transactions at all. Read more here: Transactionless Relay-chain · Issue #323 · paritytech/polkadot · GitHub

However, currently, most if not all the functionality in Polkadot are not multichain ready. I would like to focus on account abstraction in this post.

Polkadot offers multiple tools to manage accounts, namely proxy, multisig, and derivatives. This allows a public key can control multiple accounts (via utility.as_derivatives). Allow an account to be controlled by multiple public keys (via proxy). Allow more advanced account ownership management (via pure proxy and multisig).

In additional to that, there is also identity pallet to allow accounts to have human friendly names and have verified emails and other metadata attached to it. This is super useful for many operations, including governance and staking. Users are able to vote/nominate verified accounts to clearly indicate the account owner, without referring to hard to read base58 addresses.

Those features work nicely within a single chain environment. Unfortunately, not any more in a multichain environment.

Unlike multisg, which is a multichain friendly feature, pure proxy only lives on a single chain. I can create a pure proxy on Polkadot, and it is impractical for me to control the same pure proxy address on other system parachains. This means any crosschain interactions are super dangerous, as if I send DOT from the pure proxy on Polkadot to the same addresses on Statemint, I will lose those DOT.

On top of that, the identity pallet only lives on a single chain (the relaychain currently, no reason why we can’t migrate it to a system parachain in future). It is simply wasteful to duplicate this pallet on all the system parachains and requires registrar and users to verify multiple time on different chains. Then we have an issue. Because pure proxy is inaccessible on other parachain, it is not possible to verify the identity of a pure proxy on common good parachains.

One solution to this is to allow pure proxy on one chain to be able to XCM transaction on another chain to gain full control of the same address. This could be an opt-in configuration from trusted chains only. This will allow a pure proxy on Statemint to send an identity verification request on the identity pallet on Polkadot and have its identity verified.

Another way is maybe combined with DID or something similar, to standardize the account abstraction across multiple chains. One chain can access identity documents from the source chain and grant a public key to access an account according to the identity document. The source chain could push any modify of the identity document to other chains. But this is obviously lot more work compare to just make XCM transact to work with pure proxy.

8 Likes

I think the security implications of allowing another chain to take ownership of an on chain account without doing any sort of verification. Hijacking is way too dangerous as there simply is no way to know whether account X is a valid proxy on parachain Y or parachain Y is just maliciously trying to steal funds in account X on the other chain.

Perhaps a simpler fix for losing funds sent to the wrong address would be to just always use multi-location addresses throughout Polkadot so that the frontend can validate what address is specified? (Technically the chain prefixes though already fix this, no?)

3 Likes

Yes hijacking is indeed an issue. We need some proper identity solution to solve that.

But for between trusted chains (i.e. relaychain and system parachains), we don’t need to worry about this. System parachains can assume relaychain have performed the necessary verifications about the account before dispatch the XCM

1 Like

I’d suggest tx contain Merkle proofs into the account state on the other chain, presumably as some fixed moment in time defined by relay chain state root. We then declare the proxy in the account state on the account chain, prove it’s validity and other properties, and then sign using the proxy key.

As an alternative, we could’ve off-chain certificates instead of on-chain proxies, which maybe optimizes for the typical use case: It reduces the certifying account chain’s state size, but increases the referencing chain’s block size. We’d then need on-chain revocation lists in the certifying accounts for the rare cases certificates get compromised.

We’ve afaik never cleanly exposed in runtimes the ability to do merkle proofs into substrate chains, presumably because we were instead focused on making the system “relatively” easy to use, and this is clearly a deeply expert feature.

At W3F Alistair and I would basically never design a protocol that depends upon messaging. We’ll always optimize everything to use direct state proofs, so substrate making direct state proofs difficult resulting in asynchronous backing being hard, and contributed to XCMP not yet being developed.

In my view, substrate needs to make Merkle proofs in arbitrary chain relatively user friendly, including Merkle proofs into the current chain’s past state. I’d expect asynchronous backing and XCMP shold then be written or rewritten around this functionality. I’d expect doing this optimally means adding a PoV-like data section to the block itself, so that the block can record information it knew about other chains, and that all this hashing can be done in parallel to the block, like PoV hashing.

We’ll want to expose other proof strategies too however, which maybe complicates this further. We could maybe try to define some traits for general state proofs, but this gets kinda tricky since their key types differ, like elliptic curve scalar fields for KZG vs [u8]s for tries.

Anyways…

We face a serious risk that developers become too dependent upon messages, which then makes parachian code inefficient, when in fact they should be doing messages less and doing proofs into chain state more, albeit perhaps with greater risks of major security blunders.

3 Likes

There not necessarily a way to prepare a tx that require Merkle proofs into some foreign chain state right now, but also how we want to define these matters. In particular, I’ve seemingly said one thing wrong above…

We might not want parts of the tx to be detached and shoved into a separate PoV-like part of the block, like because we’d want the signature to authenticate intent to use a particular proof. This is similar to the back cert problem, which comes under replay attacks broadly interpreted.

This is also critical for Interchain Proof Oracle Network, which could be a foundation for many crosschain use cases including this one

1 Like

When you would require the proxy private key to sign some data. It would make the proxy useless as you would still need the key of the proxied account. Pure proxies also don’t have any known private key, making this completley impossible to sign anything.

This is really easy and we support this. Doing merke proofs is really easy. Assuming you don’t mean that you want to generate them from the runtime. For things in the current state it should also be relative easily possible to create a proof. However, I don’t know if we should do this on chain and not in some offchain context.

The main problem I see with storage proofs is that there is no “generic proof”. You always need to know how the proof was build. This includes which kind of hashing did you use, which kind of trie etc. All of that needs to be fixed. We also can not just change the proof format, because this also means that our storage root would need to be calculated in the same format or we would need to have multiple different storage roots for each potential trie we support. Or can we support this better?

1 Like

How do we actually use pure proxies? Something needs to be signed somewhere, or else it goes through some on-chain voting process, or else it originates from the block producer.

We’d need to generate the cross chain proofs in RPC calls or something. It’s maybe worth having a discussion more focused upon storage, so I started Generalized storage proofs

Thanks for highlighting this @bkchr - I fundamentally believe that sooner or later the vast majority of users will interact primarily through pure accounts. The idea that you won’t ever be able to change a private key of an account will go out of fashion I’m convinced and we should design Polkadot to support that from the beginning.

There’s one more crazy idea I’ve been about that I wanted to throw out there: allowing an account from another parachain to transact via XCM directly. This still means the third party parachain is trusted by the owner of this account but at least there is no threat of the third party parachain to mess with anything that isn’t technically controlled by this specific third party parachain.

It could be as simple as a way to allow a parachain to take control of an account it specifies that is prefixed with the multi-location of the parachain:

TransactAsAccount(MultiAccount account, ExtrinsicPayload payload) 

We could derive a local account id from the hash of mutliaccount:

// Acala sends this XCM message
TransactAsAccount("/0xFADED82....", "Balances.Transfer(1000 ACA)")

internally the chain would do:

  1. Prefix the source parachain to the account: "/polkadot/acala/+account"
  2. Hash this account
  3. Execute the extrinsic with the derived address as the origin

I think this offers a lot of benefits for allowing users to do stuff across parachains without the hard to avoid messiness of trying to sync account abstraction across parachains.

1 Like

This is already supported or maybe I misunderstood you.

One use case that is not supported is to allow Alice on Polkadot to transact Alice on Statemint. In that way pure proxy on Polkadot can access token sent to that address on other parachains.

Yeah, I think this is a misunderstanding. Let me try to explain with a simple example.

Alice on Para1 wants to execute a transaction on Para2 with XCM Transact. Para2 only knows the oigin is Para1 and nothing about who triggered it on Para1. This makes sense as generally Para2 can’t really know how Para2’s account model works.

Now let’s assume that Alice actually has 100 XCM bridged PARA2 tokens on Para1 and wants to participate in Governance on Para2. Alice wants to vote YAY on Motion 42.

Bob also has 50 PARA2 tokens on Para1 and wants to vote NAY on Motion 42.

Because Para2 just knows that Para1 owns 150 PARA2 tokens there’s no way to actually do this.

Hence I think it would be great if Para1 could operate several accounts through XCM:

Instead of Alice bridging PARA2 to Para1 and Para2 moving the PARA2 balance into the Para1 XCM account, Alice could send PARA2 to the account Para1/Alice. Then Alice could manipulate this account by sending an XCM message TransactAs("Para1/Alice", VoteOnMotion(42, YAY) to Para2 from her account Alice on Para1.

The key difference here would be that instead of setting the origin as Para1 when parsing the XCM call it would set the origin as Para1/Alice thus allowing Alice to directly interact with Para2 through her account on Para1.

@bkchr Does that clarify what I mean?

1 Like

You mean that you can send from a pure proxy on Polkadot to Statemint, but on Statemint you don’t have a way to control the account directly because you don’t have any way to access it. But you could still control the pure proxy from the original pure proxy on Polkadot? Aka sending XCM messages to transfer tokens on Statemint from your pure proxy there to another account etc?

1 Like

Yes. This is a related issue I raised Limit pure proxy to the source chain · Issue #8454 · polkadot-js/apps · GitHub

If not implemented correctly, this could be a security issue if the source of the XCM is not trusted. However, relaychain and common good parachains are already trusting each other so we can safely enable this feature. Parachains are also trusting relaychain so we can also allow relaychain account take control on parachain account.

It is just that we cannot allow community parachain account take control on relaychain / other parachain account.

1 Like

This is why I’d propose that we don’t actually allow any cross-chain account control but always limit the scope of what a third party account can do to not be able to control anything. I think the overhead of making it different for a relay chain and a parachain is not worth the hassle. In the end the relay chain shouldn’t have any activity at all and most logic will be pushed out to parachains so this will become less and less useful.

I would like to give my and KILT’s two cents here. I know not everyone likes DIDs, but we do think they fulfil some of the issues that have also been discussed in this thread. We are definitely super interested in the development of a generalised way to generate and verify storage proofs, as we are in the development of the Interchain Proof of Oracle. We think a DID can fulfil the job of abstracting over specific accounts, allowing, for instance, multiple accounts, potentially from different chains, to be linked under the same DID (feature has been live for months already).

Given there is a way for target chains to retrieve the state of a DID or simply verify a Merkle proof over some representation of the DID document, it becomes quite easy to do some of the things mentioned here, most importantly probably having a single source of truth for someone’s identity, and being able to link accounts (including multiple proxies) to the same DID and operate as such.
Clearly this does not take into account privacy, but we think that there are use cases where linking multiple bits of information is actually beneficial, with the most important use case being linking multiple collator accounts from multiple blockchains under the same umbrella, which we have identified as being a Decentralised Identifier, or DID, which also provides a standard and especially extensible way of exposing information.

This is really easy and we support this. Doing merke proofs is really easy. Assuming you don’t mean that you want to generate them from the runtime. For things in the current state it should also be relative easily possible to create a proof. However, I don’t know if we should do this on chain and not in some offchain context.

On the side, I wanted to ask @bkchr if there have been any metrics on the block weight and time that the runtime takes to generate merkle proofs depending on the size of the input data, be it passed as a parameter e.g. via some extrinsic or being retrieved from the storage. In any case, what would be the best place to look for existing code to re-use or to take inspiration?

1 Like

I think TransactAs should be implemented on frame/polkadot and I opened an issue to discuss this:

@bkchr can you elaborate more on that. I get the hasher part, but isn’t the trie the same accross parachains.

E.g. we are already using the relay-chain state proof in cumulus by creating an in-memory TrieBackend. Couldn’t inter parachain proofs be used like that too?

With the account converter merged into Polkadot, I would love to push for broader adoption of this in the especially relevant Asset Hub / Statemint.

@joepetrowski Would you be open to adding it to your roadmap?

I would love to push for broader adoption of this in the especially relevant Asset Hub / Statemint.

It seem’s that Asset Hub / Statemint started to implement another standard introduced by this PR: https://github.com/paritytech/polkadot/pull/7329

And the parachain runtime Asset Hub Kusama use it:

As for moonbeam, we don’t know what to choose. We were about to push the use of the converter ForeignChainAliasAccount into production, but we’re afraid of having to maintain forever something “legacy” that no one else will use.

Which brings us to another problem: how do we go about changing the way we derive XCM accounts in production? It seem’s complex, because it’s too hard to perform a migration in that case, we may have funds hold or freeze in many pallets.
We have some ideas to version the xcm account derivation, like using a Topic instruction for that, or a prefixed DescendOrigin before the real one, but maybe the cleanest way is to add something in xcm v4 to handle xcm account derivation versioning properly?