diff --git a/ic/agent.py b/ic/agent.py index 5e61fd8..c778752 100644 --- a/ic/agent.py +++ b/ic/agent.py @@ -6,7 +6,9 @@ from .constants import * from .utils import to_request_id from .certificate import lookup - +import hashlib +import re +from termcolor import colored def sign_request(req, iden): req_id = to_request_id(req) @@ -17,7 +19,40 @@ def sign_request(req, iden): 'sender_pubkey': sig[0], 'sender_sig': sig[1] } - return req_id, cbor2.dumps(envelop) + + if iden.delegation != None: + print("Delegation representation independent hash", + iden.delegation["delegation"], + hashlib.sha256(to_request_id(iden.delegation["delegation"])).digest().hex()) + print(colored(f"Signing request with delegation", "blue")) + envelop.update({ + 'sender_delegation': [iden.delegation], + }) + + # Need to byte-encode various fields + envelop["sender_delegation"][0]["signature"] = bytes(envelop["sender_delegation"][0]["signature"]) + envelop["sender_delegation"][0]["delegation"]["pubkey"] = \ + bytes(envelop["sender_delegation"][0]["delegation"]["pubkey"]) + + print(colored("Sender public key for delegation " + \ + bytes(iden.delegation_sender_pubkey).hex(), "yellow")) + print(colored("Sender delegation signature " \ + + bytes(envelop["sender_delegation"][0]["signature"]).hex(), "yellow")) + print(colored("Sender delegation signature, decoded cbor: ", "red"), + cbor2.loads(bytes(envelop["sender_delegation"][0]["signature"]))) + print(colored("Sender delegation delegation pubkey " \ + + bytes(envelop["sender_delegation"][0]["delegation"]["pubkey"]).hex(), "yellow")) + print(colored("sender_sig " \ + + bytes(sig[1]).hex(), "yellow")) + print(colored("sender_pubkey " \ + + bytes(sig[0]).hex(), "yellow")) + + print(colored(envelop, "yellow")) + print("cbor encoding", colored("evenlop", "red"), envelop) + + c = cbor2.dumps(envelop) + print("cbord encoded", colored("evenlop", "red"), c.hex()) + return req_id, c # According to did, get the method returned param type def getType(method:str): @@ -49,6 +84,27 @@ def get_expiry_date(self): def query_endpoint(self, canister_id, data): ret = self.client.query(canister_id, data) + print("query_endpoint ret", ret) + try: + ret_str = ret.decode('utf-8') + if 'Failed to authenticate' in ret_str: + print('Failed to authenticate .. analysing string', ret_str) + + m = re.search("signature d9d9[a-z0-9]*", ret_str) + if m: + cbor = m[0][len("signature")+1:] + cbor_decoded = cbor2.loads(bytes.fromhex(cbor)) + print("CBOR ", cbor_decoded) + print(bytes(cbor_decoded["certificate"]).hex()) + + # Certificate is naother CBOR encoded value + inner_cbor = cbor2.loads(bytes(cbor_decoded["certificate"])) + print("tree", inner_cbor["tree"][1]) + print("signature", bytes(inner_cbor["signature"]).hex()) + print(inner_cbor.keys()) + + except (UnicodeDecodeError, AttributeError): + pass return cbor2.loads(ret) def call_endpoint(self, canister_id, request_id, data): @@ -71,6 +127,10 @@ def query_raw(self, canister_id, method_name, *arg): } _, data = sign_request(req, self.identity) result = self.query_endpoint(canister_id, data) + print("result in query_raw", result) + # result in query_raw {'status': 'replied', 'reply': {'arg': b'DIDL\ + if isinstance(result, dict) and "reply" in result: + print('candid encoded result, which we decode later', result["reply"]["arg"]) if result['status'] == 'replied': if len(arg) == 1: res = decode(result['reply']['arg']) @@ -91,8 +151,8 @@ def update_raw(self, canister_id, method_name, *arg): 'ingress_expiry': self.get_expiry_date() } req_id, data = sign_request(req, self.identity) - _ = self.call_endpoint(canister_id, req_id, data) - # print('update.req_id:', req_id.hex()) + req = self.call_endpoint(canister_id, req_id, data) + print(colored('update_raw - req', 'blue'), req) status, result = self.poll(canister_id, req_id) if status != 'replied': return status @@ -114,6 +174,7 @@ def read_state_raw(self, canister_id, paths): _, data = sign_request(req, self.identity) ret = self.read_state_endpoint(canister_id, data) d = cbor2.loads(ret) + print(d) cert = cbor2.loads(d['certificate']) return cert @@ -128,7 +189,7 @@ def request_status_raw(self, canister_id, req_id): else: return status.decode(), cert - def poll(self, canister_id, req_id, delay=1, timeout=10): + def poll(self, canister_id, req_id, delay=1, timeout=30): status = None for _ in wait(delay, timeout): status, cert = self.request_status_raw(canister_id, req_id) diff --git a/ic/identity.py b/ic/identity.py index f71384f..62dabb7 100644 --- a/ic/identity.py +++ b/ic/identity.py @@ -4,6 +4,8 @@ from .principal import Principal import ecdsa +from termcolor import colored + class Identity: def __init__(self, privkey = "", type = "ed25519", anonymous = False): privkey = bytes(bytearray.fromhex(privkey)) @@ -32,6 +34,11 @@ def __init__(self, privkey = "", type = "ed25519", anonymous = False): else: raise 'unsupported identity type' + self.delegation = None + self.delegation_principal = None + self.session_key = None + self.delegation_sender_pubkey = None + @staticmethod def from_pem(pem: str): key = ecdsa.SigningKey.from_pem(pem) @@ -50,11 +57,25 @@ def to_pem(self): def sender(self): if self.anonymous: return Principal.anonymous() + elif self.delegation: + principal = self.delegation_principal + print(colored(f"Returning sender {principal} for delegation", "blue")) + return principal return Principal.self_authenticating(self._der_pubkey) + def add_delegation(self, delegation, delegation_principal, session_key, delegation_sender_pubkey): + self.delegation = delegation + self.delegation_principal = delegation_principal + self.session_key = session_key + self.delegation_sender_pubkey = delegation_sender_pubkey + def sign(self, msg: bytes): if self.anonymous: return (None, None) + if self.session_key: + print(colored(f"Using session key {self.session_key} to sign message", "blue")) + sig = self.session_key.sign(msg) + return (bytes(self.delegation_sender_pubkey), sig) if self.key_type == 'ed25519': sig = self.sk.sign(msg) return (self._der_pubkey, sig) diff --git a/ic/utils.py b/ic/utils.py index 66bce0c..7cd7b30 100755 --- a/ic/utils.py +++ b/ic/utils.py @@ -34,6 +34,8 @@ def to_request_id(d): pass vec = [] for k, v in d.items(): + if isinstance(v, dict): + v = to_request_id(v) if isinstance(v, list): v = encode_list(v) if isinstance(v, int):