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

Do you support delegations in signing certificates when making calls to canisters? #58

Open
bodily11 opened this issue May 20, 2022 · 13 comments
Labels
enhancement New feature or request

Comments

@bodily11
Copy link

Would love to be able to pass in a delegation as a parameter to Identity or Canister or query_raw (or somewhere else) to be able to call on behalf of delegated principal. Is this possible right now?

@bodily11
Copy link
Author

I'm guessing no as when the signing occurs here:

envelop = {

In the envelope there is no option for sender_delegation, as defined here: https://smartcontracts.org/docs/current/references/ic-interface-spec/#authentication

Any plans to add sender_delegation?

@Myse1f Myse1f added the enhancement New feature or request label May 21, 2022
@Myse1f
Copy link
Contributor

Myse1f commented May 23, 2022

Try to support delegation in #59. It is just a simple implementation.

You can switch to that branch and test. Using the ic_delegation and ic_identity in the local storage can construct an II delegation identity.

from ic.Identity import DelegationIdentity
from ic.Agent import Agent
from ic.Client import Client

# delegation and identity get from browser local storage
ic_delegation = """
{
    "delegations": [
        {
            "delegation": {
                "expiration": "xxx",
                "pubkey": "xxx"
            },
            "signature": "xxx"
        }
    ],
    "publicKey": "xxx"
}
"""
ic_identity = """
[
  "xxx",
  "xxx"
]
"""

client = Client()
iden = DelegateIdentity.from_json(ic_identity, ic_delegation)
print('principal:', Principal.self_authenticating(iden.der_pubkey))
ag = Agent(iden, client)
# now can use the agent to do query or update call

Remember that delegation identity from II has an expiry, thus it can't be used forever.

@bodily11
Copy link
Author

Oh nice! This is awesome! I'll test this out this week. And yeah, I know it has an expiry, but I'm going to use the II backend to actually add my script as a new device so then I have permanent authenticated access. I had to figure out authenticated/delegated II calls first though as you need those to register a new device.

@bodily11
Copy link
Author

All of my testing appears to be working on the delegation branch. Loading in a delegated identity using the information from II in the browser works great.

@bodily11
Copy link
Author

I just tried to pull some data from a different canister using the same feat_delegation branch I used for my other testing, and it gave me an error reading the data using the candid. When I switched back to main it worked again, so something is probably still off on the feat_delegation branch. Maybe you have tests you can run to spruce it up before merging those changes in to main. 😀 Just figured I would let you know.

@Myse1f
Copy link
Contributor

Myse1f commented May 25, 2022

Can you paste your error here? Some snippets that can reproduce it would be great!

@bodily11
Copy link
Author

Yeah, here's an error:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/var/folders/yg/mzpfyl291vx30knxqlx43d2r0000gn/T/ipykernel_39454/2771234657.py in <module>
     22 canister = Canister(agent=agent, canister_id=canister_id, candid=canister_did)
     23 
---> 24 result = canister.getCollectionDump()

/opt/anaconda3/lib/python3.9/site-packages/ic/canister.py in __call__(self, *args, **kwargs)
     55         effective_cansiter_id = args[0]['canister_id'] if self.canister_id == 'aaaaa-aa' and len(args) > 0 and type(args[0]) == dict and 'canister_id' in args[0] else self.canister_id
     56         if self.anno == 'query':
---> 57             res = self.agent.query_raw(
     58                 self.canister_id,
     59                 self.name,

/opt/anaconda3/lib/python3.9/site-packages/ic/agent.py in query_raw(self, canister_id, method_name, arg, return_type, effective_canister_id)
     77             raise ValueError("Malformed result: " + str(result))
     78         if result['status'] == 'replied':
---> 79             return decode(result['reply']['arg'], return_type)
     80         elif result['status'] == 'rejected':
     81             return result['reject_message']

/opt/anaconda3/lib/python3.9/site-packages/ic/candid.py in decode(data, retTypes)
   1293     for i, t in enumerate(types if retTypes == None else retTypes):
   1294         outputs.append({
-> 1295             'type': t.name,
   1296             'value': t.decodeValue(b, types[i])
   1297             })

/opt/anaconda3/lib/python3.9/site-packages/ic/candid.py in name(self)
    599     @property
    600     def name(self) -> str:
--> 601         return 'opt ({})'.format(str(self._type.name))
    602 
    603     @property

/opt/anaconda3/lib/python3.9/site-packages/ic/candid.py in name(self)
    555     @property
    556     def name(self) -> str:
--> 557         return 'vec ({})'.format(str(self._type.name))
    558 
    559     @property

/opt/anaconda3/lib/python3.9/site-packages/ic/candid.py in name(self)
    677     @property
    678     def name(self) -> str:
--> 679         fields = ";".join(map(lambda kv: kv[0] + ":" + kv[1].name, self._fields.items()))
    680         return "record {{{}}}".format(fields)
    681 

/opt/anaconda3/lib/python3.9/site-packages/ic/candid.py in <lambda>(kv)
    677     @property
    678     def name(self) -> str:
--> 679         fields = ";".join(map(lambda kv: kv[0] + ":" + kv[1].name, self._fields.items()))
    680         return "record {{{}}}".format(fields)
    681 

/opt/anaconda3/lib/python3.9/site-packages/ic/candid.py in name(self)
    555     @property
    556     def name(self) -> str:
--> 557         return 'vec ({})'.format(str(self._type.name))
    558 
    559     @property

/opt/anaconda3/lib/python3.9/site-packages/ic/candid.py in name(self)
    677     @property
    678     def name(self) -> str:
--> 679         fields = ";".join(map(lambda kv: kv[0] + ":" + kv[1].name, self._fields.items()))
    680         return "record {{{}}}".format(fields)
    681 

/opt/anaconda3/lib/python3.9/site-packages/ic/candid.py in <lambda>(kv)
    677     @property
    678     def name(self) -> str:
--> 679         fields = ";".join(map(lambda kv: kv[0] + ":" + kv[1].name, self._fields.items()))
    680         return "record {{{}}}".format(fields)
    681 

TypeError: can only concatenate str (not "int") to str

And this happens when I call a few methods. Let me find you a public method to call to reproduce the error.

@bodily11
Copy link
Author

Here's a public reproducible error for you:

# governance_did can be downloaded from canlista here: 
# https://k7gat-daaaa-aaaae-qaahq-cai.ic0.app/listing/nns-governance-10222/qoctq-giaaa-aaaaa-aaaea-cai
governance_canister_id = 'rrkah-fqaaa-aaaaa-aaaaq-cai'
i1 = Identity()
client = Client(url = "https://ic0.app")
agent = Agent(i1, client)
testCanister = Canister(agent=agent, canister_id=governance_canister_id, candid=governance_did)
testCanister.get_monthly_node_provider_rewards()

@bodily11
Copy link
Author

Just as a follow-up, this code works fine on the main branch, but breaks with the error above on the feat-delegation branch. Maybe due to the type table parsing fix you pull into it to fix the other bug.

@Myse1f
Copy link
Contributor

Myse1f commented May 25, 2022

Should be fixed now. Thanks!

@bodily11
Copy link
Author

Yeah perfect. That solved the issue. Running into one more issue.

This is the governance canister (same .did as above)

governanceCanister.manage_neuron(
    {
    'id':[{
        'id':all_icp_neurons[0]
    }],
    
    'command':[{'MergeMaturity':{
        'percentage_to_merge':1
        }
    }],
    
    'neuron_id_or_subaccount':[]
    })

In this case, I get the following error:

TypeError                                 Traceback (most recent call last)
/var/folders/yg/mzpfyl291vx30knxqlx43d2r0000gn/T/ipykernel_41783/2057413411.py in <module>
----> 1 governanceCanister.manage_neuron(
      2     {
      3     'id':[{
      4         'id':all_icp_neurons[0]
      5     }],

/opt/anaconda3/lib/python3.9/site-packages/ic/canister.py in __call__(self, *args, **kwargs)
     66                 self.canister_id,
     67                 self.name,
---> 68                 encode(arguments),
     69                 self.rets,
     70                 effective_cansiter_id

/opt/anaconda3/lib/python3.9/site-packages/ic/candid.py in encode(params)
   1258     for i in range(len(args)):
   1259         t = argTypes[i]
-> 1260         if not t.covariant(args[i]):
   1261             raise TypeError("Invalid {} argument: {}".format(t.display(), str(args[i])))
   1262         vals += t.encodeValue(args[i])

/opt/anaconda3/lib/python3.9/site-packages/ic/candid.py in covariant(self, x)
    835 
    836     def covariant(self, x):
--> 837         return self._type if self._type.covariant(x) else False
    838 
    839     def encodeValue(self, val):

/opt/anaconda3/lib/python3.9/site-packages/ic/candid.py in covariant(self, x)
    630             if not k in x:
    631                 raise ValueError("Record is missing key {}".format(k))
--> 632             if v.covariant(x[k]):
    633                 continue
    634             else:

/opt/anaconda3/lib/python3.9/site-packages/ic/candid.py in covariant(self, x)
    571 
    572     def covariant(self, x):
--> 573         return type(x) == list and (len(x) == 0 | (len(x) == 1 and self._type.covariant(x[0])))
    574 
    575     def encodeValue(self, val):

TypeError: unsupported operand type(s) for |: 'int' and 'RecordClass'

Usually if I have a formatting error between types/values in candid I get a record formatting error, so it seems my values match the candid. But for some reason it is failing to correctly encode the data using the candid. So I thought maybe an ic-py issue?

@Myse1f
Copy link
Contributor

Myse1f commented May 25, 2022

Fixed! Thank you very much for the detail.

I think we should test it more carefully and thoroughly.

@bodily11
Copy link
Author

Yeah, that fixes it. Thanks! Everything seems to be working now on feat-delegation branch. I'm able to authenticate with II across apps and automate process. So I'm in really good shape. Thanks for all the help.

When are you thinking about merging this branch into production? Would love to get it in sooner rather than later (assuming all bugs have been fixed) to prevent too many conflicts from the main repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants