Account Linking & AuthAccount Capabilities Management

Community activity in Flow forums, FLIPs and PRs in recent months has progressed an impressive number of new Cadence language features relating to accounts, account access and security concerns, and/or capabilities and their management. The creation of these features is, in part, to support the broader vision for walletless-onboarding and Hybrid Custody on Flow. Once implemented, we believe Flow can provide the best-in-class UX for friction-less engagement with dapps without upfront wallet setup or funding requirements, coupled with the smooth transition to a self-custody account later.

In parallel the Flow team have been iterating on a proof-of-concept implementation of Account Linking and Delegation to help understand design options, and to surface risks and limitations. However, Account Linking and Delegation as it is realized in the PoC today does not integrate any of the new proposed language features - and yes, some have yet to be finalized themselves. Given the complexities of these features, the security sensitive domain being handled, and the value of Account Linking and Delegation functionality to the broader ecosystem, it seems worthwhile and necessary to consider a Cadence contract standard. The standard would ensure interoperability for how dapps integrate the onboarding experience, reduce complexity for builders, and establish a robust and secure model for working with accounts.

Going forward, we want to involve the community to align on the role of the standard, help with agreeing the specification, and settle remaining open questions so it can be implemented. To that end we will propose a FLIP PR in the coming days, through which the items detailed below will need to be resolved. We will also be setting up specific community breakouts for this topic once the current round of breakouts has wrapped up.

Account Linking FLIP contents

The proposed standard sets down primitives through which well-known web2 account-to-application authorization schemes can be modeled in our decentralized context.

Non-Goals

Here’s what the FLIP will not propose:

  • Create a standard for shared access on a user’s primary account
  • Introduce guardrails for the use of AuthAccount Capabilities

For the purposes of the FLIP, we will focus on how to manage the AuthAccount Capabilities delegated to an account, not necessarily on the discussion of whether that delegation should occur or the mechanism for delegation. While those topics are of course important, relevant, and will have implications for the details of what is managed by the constructs discussed here, the AuthAccount Capability FLIP (implemented here) is allocated for such discussions.

Essential

We hope to cover the following in settling the design and best practices for the standard:

  • Child account creation
  • Child AuthAccount capability management
  • Viewing existing child accounts
  • Adding an account as a child account
  • Revoking hybrid custody approval/access granted to a child account
  • Identifying an account’s child accounts
  • Identifying an account’s parent account(s)

For Consideration

  • Delegating Capabilities to a child account from the parent’s ChildAccountManager
  • Easily viewing the assets in an user’s child accounts

Integration of new language features

A critical outcome we are looking for from the proposed FLIP is alignment on how each new language feature fits together, complementing each other to create a more secure yet flexible system.

If we’re going to implement AuthAccount Capabilities, we’ll need a secure standard for the management of linked accounts. By enabling AuthAccount Capabilities, we’ll also need a mechanism limiting their issuance, or at minimum distinguishing that linking the signer’s account is possible in a given transaction. This problem can be satisfied by the proposed SuperAuthAccount (@bluesign) or similar hierarchical and/or role-based AuthAccount interfaces, restricting/enabling potentially dangerous actions within transactions. These actions would include linking accounts, adding/revoking keys, deploying contracts, etc. within a given transaction.

Capability Controllers (CapCons), if implemented, would increase auditability and provide granular control over AuthAccount Capabilities. The existence of AuthAccount Capabilities has design implications for CapCons’ implementation details; however, enabling AuthAccount Capabilities is not dependent on the existence of CapCons. In a world without this feature, we’ll want to consider how AuthAccount Capabilities are retrieved and revoked, particularly since they cannot be retargeted and because a user lacks knowledge of secondary party’s access, much less their identity.

The question of how and where these should rightly be integrated will be a key focus of community discussions.

Other open questions

Child-account multi-parenting

There may be benefits to supporting multi-parenting of accounts, although this may also introduce complexities and edge cases that could be problematic or otherwise cause builders or users issues or concerns.

The role of the standard in offering utility functionality for working with child accounts

The conceptual exploration of the standard so far proposes a ChildAccountManager contract resource which provides access to child accounts under its management. The question of whether it should expose utility methods for working with child accounts has obvious pros and cons. Utility functionality that wraps the linked account’s AuthAccount Capability raises source of truth problems and also scalability and performance concerns. An example of such a utility function would be a method that returns all NFT metadata from owned NFTs in a child account. Resolving which utility functions are justified and necessary will be essential.

Source of truth concerns

For reasons of performance and simplicity it seems worthwhile to avoid repetitious iteration over linked accounts to access their metadata. Leveraging structs to retain local copies of child account metadata for use by ChildAccountManager introduces data synchronization considerations and questions relating to source of truth guarantees during runtime.

AuthAccount handling; by reference or capability?

The handling of child AuthAccount objects can be implemented in two ways; by reference, eg: &AuthAccount or the stored AuthAccount Capability. The latter introduces the risk of delayed attack vectors given that Capabilities can be copied and stored - a risk more prominent in a world without CapCons.

Object and type naming

The names for types and interfaces currently in use are best-effort but also need refinement so that they self-document and make sense to those learning about the standard.

The above list is not exhaustive but is representative of the main challenges still to be addressed on the path to an Account Linking standard. We expect to convene Cadence language breakout discussions to align and work through the resolution of these issues, as well as asynchronously through the FLIP PR.

Examples

There have been a number of requests to see use cases and examples showcasing why delegated AuthAccounts are even needed or useful and how builders might leverage them, so here it is. We’ve been exploring AuthAccount Capabilities in the flow-cli pre-release, and you can check out the work here:

Please check these examples out, play with them and leave your feedback below. We’re excited about the possibilities this brings to all the existing projects in the ecosystem and to Flow at large!

References

AuthAccount Capability implementation

AuthAccount Capability FLIP

Hybrid Custody forum post

Walletless Onboarding Blog Post

Capability Controllers FLIP

5 Likes

@gio_on_flow first of all thanks for this demo, I know it is very complicated system (it was a lot of code to read ) and I really appreciate your work on this. I have few comments and questions.

If we will make it as a standard I think covering some existing Cadence stuff would be very much needed. For example:

  • Storage enumeration on child accounts ( we already have this enumeration in Cadence, would be very nice to use a similar API for child account, via similar API, basically it will proxy the API to child account/accounts, without need to get the capability / capability ref )

  • Linking / Borrowing from storage ( this is a bit more tricky, but gating here has a lot of benefits )

  • Add / Revoke public key

I think child account creation via contract makes me little nervous, I feel it will be abused ( used as not intended via dapps ) I foresee they will create account via this, and assign themselves as parent instantly and then run all transactions over ChildAccountManager. ( instead of running transactions by child account private key ) and we will lose trace on chain.

After having ChildAccount implement AuthAccount methods ( like above and maybe more ) we can have familiar API to developers, I think it is a big plus. Also this then gives us ability to fine grain permissions to ChildAccount access in the future. ( also prevents dapps to use child account manager for transactions )

Answers to questions:

Child-account multi-parenting

I think multi-parenting is not a big issue if we use manager as authaccount wrapper.

The role of the standard in offering utility functionality for working with child accounts

I think utility functions limited to AuthAccount functions will give same abilities currently we have, I can show child account and then query what is inside ( check metadata etc )

Source of truth concerns

I think it will be almost same cost to query from child account vs retaining copy. ( also bytes are gold )

AuthAccount handling; by reference or capability?

I vote none here :slight_smile:

Object and type naming

I think if we keep authaccount naming, it is not a problem too.

1 Like

Thank you very much for your feedback @bluesign! Always very insightful.

A couple of clarifying points/questions:

I’m curious about the alternatives here. I’ve had some thoughts about integrating the notion of child accounts into the AuthAccount, something like AuthAccount.ChildAccounts and AuthAccount.ParentAccounts which I’ve briefly spoken to @bastian about. If not created at the contract layer, what other mechanism would satisfy the requirement that we know a child account’s parent & metadata about its associated dApp sharing hybrid custody?

This is an interesting consideration, and I think we could potentially leverage events to regain any visibility lost to transaction signing.

I question how we would be able to fully encapsulate the AuthAccount Capability within the ChildAccountManager resource. Considering Cadence is statically typed, I’m having trouble conceptualizing how we would enable something like

childAccountMgrRef.link(
  forAccount: 0x03,
  type: Type<&MyNFT.CollectionPublic>,
  capPath: /public/MyCollection,
  target: /storage/MyNFTCollection
)

considering that on the other side of that API we can’t use a runtime type to link

pub fun link(forAccount: Address, type: Type, capPath: CapabilityPath, target: StoragePath) {
  let accountCapRef: &AuthAccount = self.getChildAccountRef(address: forAccount)
  accountCapRef.link<type>(capPat, target: target)
}

Would be curious to hear thoughts around how to achieve gating here and around borrowing references from within stored child AuthAccount Capabilities.

Creating a simple to use iteration API within the ChildAccountManager is definitely an open question. The problem there is running into computation limits for accounts with a lot of resources. For example, some accounts have 10k plus NFTs. Building storage querying APIs into the standard contract would likely be a nice convenience method. Benchmarking could be helpful to get a better understanding of reasonable expectations around how much iteration would be supported by these APIs if included.

Just clarifying your perspective - is it that AuthAccount Capability management should fully encapsulate the Capability?

I think contract level is correct, but binding should happen when user wants to leave custody in my opinion.

If I had a dapp, ( ignoring storage fee changes for a moment ) It would be very easy to me create an account for each new user, and support both FCL compatible wallets and my custodial users at the same time. ( just by providing them backend signers on my server side ) I think magic.link is doing something similar. ( I just need server side signing function basically )

For this part I don’t see a need to link account to dapp, or create account via contract. It is just a db table with: user → address ( and some key management )

When user wants to link an account ( or maybe accounts ) to their dapp account, then I need to put a resource to their account, in a standard way, they will give them info/ability about: what dapp it is? and let them access functionality.

I think in this moment, I create a ChildAccountCollection if not exists, and create a ChildAccountController ( for my dapp ) and put into their account. Ideally, their wallet will detect, and add their private key to the account. ( So inventory management can be easily done via wallet )

For wallets I don’t believe ChildAccountController should be used.

ChildAccountController usage for me is for interaction with other dapps. Imagine I go to flowty to list my Flovatar on my newly linked Flovatar child account, dapps should be able to query and find out what child accounts I have, what NFTs and FTs I have there, and can provide access to them if needed.

Dapps already have generic code to enumerate account storage etc ( at least they should have ), now they just need to add another level to let user choose a child account if needed. Anything working on main account should be able to work on child account too. ( thinking it like choosing one persona )

If I have 5 child accounts, dapp can query 5 of them separately for example. ( doesn’t need to query them all at once )

Runtime linking can be solvable with Cadence change, but borrow is a bit tricky. ( though if you think with perspective of dapp not using ChildAccountController, linking is not even needed ) Btw if it was up to me, there would not be link/borrow. :slight_smile:

Eventually if wallet’s support ChildAccounts, there is not a need for it anyway. ( Wallet should have been collection of the keys, but this is a rant for another day )

is it that AuthAccount Capability management should fully encapsulate the Capability?

exactly.

I see what you’re saying with regards to the creation of the ChildAccountTag. I agree, it’s not necessary to use the ChildAccountCreator, though it filled a gap for the purposes of our demo so it stayed in the contract. Any account created by any means can be linked to another via ChildAccountManager without a prerequisite that the account being linked stores a ChildAccountTag.

Agreed, and this is the more scalable way to iterate across accounts & storage. The inclusion of convenience methods within the ChildAccountManager to get easy visibility into the storage of child accounts was an open question, but not necessary.

While wallets can maintain knowledge of linked accounts, the problem is that knowledge is not portable, at least not at the moment. I can link accounts using Blocto and my Blocto wallet might do everything needed to display my linked accounts. However, let’s say I want to authenticate to a dApp that wants to get context into my linked accounts, or I want to switch to Lilico wallet - in that case the dApp I authenticate to or the wallet I switch to will have no idea of my linked accounts.

Moreover, this requires wallet-level implementation with dependency on wallet partners to implement - something for which a solid business case must be made. While that might happen in time after child accounts have become a more widespread feature on Flow, we’ll need a standard to build on in the meantime. And I think that maintaining that knowledge on-chain, whether at the contract or account level, can make this feature set much easier to implement at the wallet level since it’s a matter of account & storage iteration + metadata resolution.

1 Like

Updating this forum post with the FLIP PR that was just published. More details can be found there on thinking around this topic.

Really great work on all of this! I like the idea of making management of all the account easier with a central registry like this, but some good points have been made about audit-ability of transactions that I need to consider. Its a lot to take in, so I’m gonna need to take some more time to digest, look at the example code, think about the implications for dapps and wallets, and have more detailed feedback, but this is a great start so far!

1 Like

There’s been some great feedback shared so far! Commenting to note that recent discussions have led to updates on contract events & an interesting suggestion around implementing NFT & Metadata standards into this proposed contract.

To recap a brief conversation had with @dete, his idea is that implementing the NFT & Metadata standards would make it easier for wallets & dApps to support the new linked account standard from the perspective of viewing a main account’s linked accounts.

By implementing the MetadataViews standard at the Resolver & ResolverCollection levels, a linked account’s metadata (currently stored in ChildAccountInfo) could easily be resolved as a view, assuming we implement the attributes contained in the most commonly used NFT metadata views.

By implementing the NFT standard, we gain ease of delegation transfer from the main account - simply transfer the wrapping NFT to another account’s ChildAccountManager. The latter feature could also be a bug depending on your perspective.

Depending on consensus, we can take one standard without the other or both. Regardless, further implementing the MetadataViews standard would likely be a win either way. A section on this topic has been added to the FLIP.

If haven’t taken a look at the FLIP and provided feedback already, please do!

:point_right: FLIP #72

Hey everyone. We’re going to be spinning up a working group / breakout series to start diving into this topic. We’re looking to the community to help determine the best way to set this up and have provided a short survey here for those who wish to participate:

We would be grateful if those of you who want to be included in this, or any other, upcoming working group can complete the 8 questions (all but 1 are multiple choice). https://forms.gle/rjCYXy37inE8CTwh7

Thanks!

Really nice work on this, @gio_on_flow!

I’m still working on reading through all the materials, but based on an initial reading, I had a few comments and questions, mostly relating to capability-based security patterns.

  1. If a user has not created their own primary account yet, how are their child accounts (the ones created by various dapps) aggregated? There’s no current way to connect these, right? The documentation says “A wallet or marketplace wishing to discover all of a user’s accounts and assets within them can do so by first looking to the user’s ChildAccountManager” but I don’t think the documentation makes it clear that the user usually won’t yet have a ChildAccountManager. I might be misunderstanding this mechanism though.
  2. It was great to see this about restricting copying of a capability: “When it comes to accessing a user’s saved AuthAccount Capabilities, it is possible to restrict Capabilities to retrieval by reference - &AuthAccount instead of Capability<&AuthAccount>. However, in capability-based access, such restrictions on an issued Capability might be considered an anti-pattern.” To add on to this, the capability-based security community feels very strongly about this, and generally has the slogan “don’t prohibit what you can’t prevent”. If Bob can simply take actions on Alice’s behalf using Bob’s capability, then that’s not any better than Bob making a copy of a capability for Alice to use, as long as the access through the copy still is attributed to Bob, so that Bob’s access can be revoked if either Bob or Alice misbehave. There’s a good paper on capability approaches to restricting and revoking access, which I TLDR’d here.
  3. Re: ChildAccountInfo, have you looked into using petnames for capabilities generally? The CapCon FLIP has an example of how petnames might be implemented. It’s basically a mapping from human-readable names as decided by the user to capabilities/addresses/keys/anything non-human-readable. The important part is that the names are local, like a phone contact list rather than domain names. In the case of ChildAccountInfo, would the creator of the Child Account determine the name in the Info? If so, that would seem to be a phishing vector. You could imagine someone creating a child account that appears to be associated with a trusted dapp based on the ChildAccountInfo, and the user puts in money or nfts thinking it’s the trustworthy dapp managing it. Then the attacker simply takes the contents. Perhaps the solution is to have a petname system that attaches a human readable name to the originatingPublicKey. (More on petnames here)
  4. Given that a parent account can remove the account of other parent accounts, is there a way that we can prevent apps revoking the user’s primary account? When a primary account is created, can it downgrade the dapp’s access so that the dapp can no longer revoke parent accounts?

Thanks so much for your time! I’ll keep reading the docs and see if anything else occurs to me.

1 Like

Thanks so much for the thoughtful feedback and helpful resources @kate_sills!

A few follow up comments & questions…

  1. You’re right, there’s not a way to aggregate a user’s dapp created accounts until the user creates a primary, wallet-managed account. Thanks for pointing that out, this should be clarified in the docs.

  2. I was actually hoping to get some feedback on this from you, so thanks for bringing this up! Truthfully, I’m torn. Your point about “don’t prohibit what you can’t prevent” is valid. On one hand, I can see how attempting to restrict malicious usage of a Capability by limiting access via reference is an incomplete safeguard. On the other hand, if we allow access to Capabilities as they currently exist we lose an audit trail for who has access to those Capabilities. In your example between Alice & Bob, the key there is “as long as the access through the copy still is attributed to Bob”. The way I understand it, Capabilities aren’t currently attributable in this manner. Using Capabilities as they exist today, I can link an AuthAccount Capability at /private/AuthAccount and give that to Alice. Let’s say Alice stores it in some resource. Alice can then call a getter for the Capability and give it to Bob. I have no way to know that Bob now has that Capability and moreover, presented with both, they’re indistinguishable from each other (that is until CapabilityControllers are finalized). Further, if I want to revoke Bob’s access I have to also revoke Alice’s access by unlinking the Capability at that path. This is driven by two factors it seems will be addressed in CapCons - 1/ Capabilities linked at the same path are indistinguishable from each other and 2/ revocation of Capabilities linked at the same path requires unlinking both at the source, making more granular revocation impossible. The question then is, if I can’t distinguish copied Capabilities from those I issued, how can I create auditability in the construction of delegated account access via these Capabilities to prevent someone from inadvertently copying one I have access to? The most apparent (and perhaps naive) answer for me is to encapsulate the underlying Capability and allowing access by reference, restricting copied Capabilities altogether. Is my thinking about this considering the limitations of Cadence’s Capabilities sound here?

  3. Petnames are a good callout. My concern here is around storage usage. With the aforementioned NFT implementation, the resource holding all linked account representations would require a third mapping to support petnames on linked accounts {String: Address}. I wonder if it would be possible to implement petnames off-chain, say at the wallet layer :thinking: Aside from portability, is there a reason we would prefer petnames to be on-chain? I have considered the phishing vector, and for now my solution has been to limit addition of linked accounts to private access. I think there might be an off-chain solution to prevent these sorts of phishing attacks - event emission on adding a linked account + wallet notification on addition of a linked accounts, perhaps even a list of verified originating accounts + attached signatures on creation (though I guess that’s not very storage efficient), etc. Back to petnames, if you/others feel the storage (and scalability by extension) cost is worth the convenience/added security, I don’t see why it couldn’t be added.

  4. What you describe here is the ideal state! Unfortunately, the custodial party has ultimate authority of the child account. So, if a dApp creates an account to support walletless onboarding and later delegates access to the user’s wallet account, the dApp still has full signatory authority over that account. The user can do things with the account - they can even revoke the dApp’s key. But so long as the user still wants to share account access with the dApp, there’s currently no way to downgrade the dApp’s access. So you can see there is certainly a degree of trust involved in this construction, and I’ve struggled to figure out how to minimize with the current Cadence constructs & feature set. If you have further thoughts on this I’m all ears!

P.S. there’s a new version of this standard in PR here implementing the NFT & MetadataViews standards. Updates to the FLIP will follow by EOD Monday. Thanks again all for the input & feedback so far!

1 Like

yeah this can be possible by wrapping capability only ( if we don’t leak &AuthAccount or Capability<&AuthAccount> )

I don’t think this applies here, ephemeral capabilities ( not sure if correct term ) can be valid use case.

+1 for petnames also

1 Like

Awesome, thanks, @gio_on_flow!

re: prohibiting what you can’t prevent, I think even taking CapCons out of the discussion, you still have sufficient attribution as long as the capability is originally specifically given to a single entity, and that is recorded somewhere (even off-chain). So if I link an AuthAccount Capability at /private/AuthAccountAlice and give that to Alice, if Alice gives it to Bob and Bob misbehaves, I’m going to blame Alice and revoke both of their access by revoking Alice’s capability. I don’t even need to know that Bob exists. That seems fine - Alice shouldn’t have access if she’s not careful about how she uses it. The important thing here is that you really don’t want to limit access just to Alice, because limiting access kills composability. For instance, instead of Alice giving it to Bob, we can imagine that Alice gives access to a service that is operating on her behalf. That’s absolutely essential for composability and a healthy on-chain economy! (Btw, I think we switched in this example from Bob giving the capability to Alice to Alice giving it to Bob, but I’m good with keeping it this way.)

Re: petnames - yup, human-readable names can definitely be off-chain. I mostly wanted to not have the name in the on-chain info at all, since if that comes from someone other than the user, that’s the main cause of the phishing issue. The good news is that petname systems fix the issue of phishing generally, since I can use other people’s shared petnames to see if they recognize the thing as legitimate or not. For example, if I go to a website that I think is Uniswap, and my friend Bob has already confirmed it is in fact Uniswap, I’m protected from going to a look-alike “Un1swap” site, which Bob won’t recognize. Even better if “Bob” is an organization that purposefully publishes a list of legitimate names to stop spammers. This role is called a “naming hub” and serves the same purpose as a domain name registry, but is entirely decentralized and competitive.

re: a dapp removing the user’s primary account, I’ll think more about that! It would be interesting to see what might be possible with Entitlements & AuthAccounts, such as creating a childAccount so that the dapp account simply does not have a way to remove other parents.