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

Ideas for Component access to Proofs in caller's AuthZone #285

Open
devmannic opened this issue May 4, 2022 · 0 comments
Open

Ideas for Component access to Proofs in caller's AuthZone #285

devmannic opened this issue May 4, 2022 · 0 comments

Comments

@devmannic
Copy link
Contributor

I would really like to see the Proofs from the caller's AuthZone which matched an AccessRule on a method call be available at the callee method as an alternative to (but not replacing) explicitly passing Proofs as parameters.

This would really help when accessing NonFungibleData but useful for fungable badges too when more than one resource address is allowed but component logic wants to know which is passed. Think of it like having CallerAuthZone available which you can do create_proof*(..) to extract specific Proofs (actually compositions of proofs, which would help too) for use in the method. This is basically the same style API that works with AuthZone only it's immutable (no push/pop) and of course any Proofs you extract would need to be restricted. You might also just create something like this which is not related to AccessRule matching, but to avoid copying proofs around that would be unused it makes more sense to only do this when there is some matching in place, and skip allow_all matches too.

There is a downside here which means the caller loses the ability to selectively decide what proof gets passed and needs to explicitly remove them from the AuthZone before the method call, which maybe isn't reasonable. But I think on the callee side I want this feature, while on the caller side I wouldn't want to lose that ability (because it helps with security). I think this could be handled on the caller size by adding "push/pop authzone" instructions which pushes the new new empty authzone onto a stack and makes all future operations apply to this new top zone.

The reason for all of this is to allow convenient multi-component interactions with proof passing without the caller having to do anything explicit except optionally push and pop authzones, and on the callee side still getting all the benefits of AuthRules without losing out on access to the specific proofs which is needed for business logic, particularly for NonFungibles.

Here's an example of how I see it being used (Fungible example):

// ...

let access_rules = AccessRules::new()
                .method("free_tokens", rule!(require_amount(dec!(100), user_reputation.resource_address()) || require(admin_badge.resource_address()))) // or maybe this is .authzone_method(...) to indicate to populate `CallerAuthZone` when checking.  Though I think it's better as an attribute of the method itself, though that makes things a little more spread out.
                .default(rule!(allow_all));
// ...

// this code could be simplified a little
pub fn free_tokens(&mut self) -> Bucket {
  let result: Bucket = self.tokens.take(1); // base tokens
  let maybe_proof: Option<Proof> = CallerAuthZone::try_create_proof(self.admin_vault.resource_address());
  if let Some(proof) = maybe_proof {
    info!("called with admin badge");
    assert!(proof.is_restricted());
    result.put(self.tokens.take(100)); // admins get an extra 100
    result
  } else { // admins with rep dont actually get both bonuses
    info!("called with enough user reputation based on AuthRule (might be Soft amount), but only give tokens bonus tokens if they proved 200 or more");
    let maybe_proof = CallerAuthZone::try_create_proof_of_amount(dec!(200), self.user_reputation_vault.resource_address());
    if maybe_proof.is_ok() {
      result.put(self.tokens.take(10)); // strong reputation grants you 10 extra tokens
    }
    result
  }
}

// or maybe
pub fn free_tokens_*&mut self, caller_auth_zone: CallerAuthZone) -> Bucket { ... }

// or maybe the above but also with a hidden parameter like #[auth(...)] used to work
#[CallerAuthZone]
pub fn free_tokens(&mut self) -> Bucket { ... }

So to address the points @russellharvey already brought up

Specifying which elements in the zone satisfied a rule is not practical. There can be many sets which do so, and establishing any minimum set is computationally worky anyway.

Yes, I agree with the difficulty here but it's avoided with this design (I think). The create_proof code in the AuthZone already handles "composing" the a list of proof "evidence" (vocabulary from the existing implementation) into a new Proof and is exactly the same code as what a user would have to do to create a Proof to pass by intent, the difference is just that this happens on the callee side. When the comparison against the Rule happens it is already walking that AuthRuleNode tree and checking against every proof in AuthZone, so it simply needs to cache the result. It could be optimized by having the callee method indicate it wants this result, either by specifying an extra value in the AccessRule configuration or at the method declaration (by say adding another parameter to mark the method as receiving the CallerAuthZone specifically)

Easy to cause user confusion or developer error when multiple resources might be present that match the resource you're looking for. Your game has a RobotParts resource and the user wants to use a sword NFT and a shield NFT for an attack call and a block call, gets very fiddly with managing the authorization zone, and it's hard to communicate what's going on conceptually. Whereas it's understandable to the layperson that their TX directly sends the sword into the attack method and the shield into the block method.

So for this example I agree the explicit parameter is better, but I think that's because of the example, but actually having an explicit Proof is only better on the caller side. The callee has to explicitly check the resource anyway, so whether they do it via CallerAuthZone::create_proof or via assert!(proof.resource_address() == ...) doesn't matter much, and for the caller, you might argue that not having to explicitly list the same proof when doing attack(); block(); attack(); is actually better. So you do new_authzone(); get_attack_proof(); get_defend_proof(); attack(); block(); attack(); drop_authzone(). On the caller side it can go either way depending on the details and having it up to the developer seems ok to me.

There's a skin-crawliness aspect to "why does the component logic get to see everything? What business does it have seeing my signature badges unless I explicitly passed them?" There's a divide between the system seeing everything and the application layer seeing everything. If you don't completely comprehend how Proofs turn into restricted Proofs which can't be passed on to other components, there's a kneejerk "oh no" reaction to reading "Everything in the authorization zone is visible to whatever you call"

I agree this could seem scary at first. But I think if the idea of "fresh auth zones" should make it clear that the callee only ever gets to see the auth zone you allow. and maybe you make a flag to opt in to "always fresh authzones" or something if you want the default to be clean slate and opting to share instead of my proposed, optional to not share. I don't think it changes the transaction-manifest / caller side mechanics too much.

Ok, that's it for the initial writeup. I might try some concrete implementation to see if this has any legs.


The original discussion thread from discord is copied below.

devmannic
 — 
Today at 1:52 PM
@Clement - RDX Works  @0xOmar - RDX Works So I've been going over how the AuthZone works and I think I've got enough understanding to request a specific feature/change.  I would really like to see the Proofs from the caller's AuthZone which matched an AccessRule on a method call be available at the callee method as an alternative to (but not replacing) explicitly  passing Proofs as parameters.  This would really help when accessing NonFungibleData but useful for fungable badges too when more than one resource address is allowed but component logic wants to know which is passed.  Think of it like having CallerAuthZone available which you can do create_proof*(..) to extract specific Proofs (actually compositions of proofs, which would help too) for use in the method.  This is basically the same style API that works with AuthZone only it's immutable (no push/pop) and of course any Proofs you extract would need to restricted.  You might also just create something like this which is not related to AccessRule matching, but to avoid copying proofs around that would be unused it makes more sense to only do this when there is some matching in place, and skip allow_all matches too.
--
  There is a downside here which means the caller loses the ability to selectively decide what proof gets passed and needs to explicitly remove them from the AuthZone before the method call, which maybe isn't reasonable.  But I think on the callee side I want this feature, while on the caller side I wouldn't want to lose that ability (because it helps with security).  I think this could be handled on the caller size by adding "push/pop authzone" instructions which pushes the new new empty authzone onto a stack and makes all future operations apply to this new top zone. --- The reason for all of this is to allow convenient multi-component interactions with proof passing without the caller having to do anything explicit except optionally push and pop authzones, and on the callee side still getting all the benefits of AuthRules without losing out on access to the specific proofs which is needed for business logic, particularly for NonFungibles. --- I can put this into a Github issue, but wanted to send it first to see if something similar isn't already being planned. 


0xOmar - RDX Works
 — 
Today at 2:04 PM
This is really similar to something that was in one of the versions of v0.4.0 where you were able to see all of the badges in the Auth Zone from components. I believe that this was quickly changed through so that the auth zone badges would not be visible inside components.

I think that this is something we could revisit to be honest as I really like this idea of being able to only see badges which match a certain AccessRule inside the caller's AuthZone from components. It could be that you get a vector of vectors of Proof (Vec<Vec<Proof>>) of the proofs which matched, and if you need it you can use them to determine how the method/function flows.

I think that this would make passing proofs by intent obsolete, but if done right passing by intent should be no longer required. I will definitely discuss this with the team. Would you like to open a GitHub issue and we can continue the discussion there? 


Russell - RDX Works
 — 
Today at 2:40 PM
Whether the caller's authorization zone is visible to components is something we have gone back and forth on, and we decided to take the approach of "Let's see if things work without exposing it," without taking a hard line.  Some things to think about:

• Specifying which elements in the zone satisfied a rule is not practical.  There can be many sets which do so, and establishing any minimum set is computationally worky anyway.
• Easy to cause user confusion or developer error when multiple resources might be present that match the resource you're looking for.  Your game has a RobotParts resource and the user wants to use a sword NFT and a shield NFT for an attack call and a block call, gets very fiddly with managing the authorization zone, and it's hard to communicate what's going on conceptually.  Whereas it's understandable to the layperson that their TX directly sends the sword into the attack method and the shield into the block method.
• There's a skin-crawliness aspect to "why does the component logic get to see everything?  What business does it have seeing my signature badges unless I explicitly passed them?"  There's a divide between the system seeing everything and the application layer seeing everything.  If you don't completely comprehend how Proofs turn into restricted Proofs which can't be passed on to other components, there's a kneejerk "oh no" reaction to reading "Everything in the authorization zone is visible to whatever you call"


Russell - RDX Works
 — 
Today at 2:43 PM
There are some other points I'm failing to bring to mind that were part of this discussion.  Anyhow, interesting conversations to be had here, very happy to hear feedback on this, and we will see how things go as people start doing chewy things with the authorization zone

 — 
Today at 3:33 PM
devmannic
 — 
Today at 3:40 PM
Those are all good points, and I'm not surprised to hear this kind of thing has already been under discussion.  I think my rough idea actually works even with those concerns (though I'm sure there are others).  I think the current "lets see" approach is a good one though as I'm really only coming to this conclusion trying to build a more complicated authentication system and trying to reason about how to do it with the current Scrypto feature set.  It might be that there's just a better way to do it anyway without these changes.

  @0xOmar - RDX Works I'll open an issue for this to discuss in more detail.  There's certainly a lot of nuance and I'd like to try to address the points @Russell - RDX Works already brought up.  I might also try to submit a PR as a rough example of how my idea would work as something more concrete to talk about. -- The best way to understand the implementation is to hack on it a bit. 🙂
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant