Telethon: Method to calculate hash for GetContactsRequest

Created on 4 Oct 2017  Â·  8Comments  Â·  Source: LonamiWebs/Telethon

In the following example on the wiki retrieving an entity#via GetContactsRequest it is recommended to use the user's access_hash to request users and contacts.

contacts = client(GetContactsRequest(self_user.access_hash))

client.get_me().access_hash is a _long_.

However, the API expect's an _int_ not a _long_, and throws an exception.
in a comment, it's mentioned that an empty hash works, this is not the case.

So what am I missing? The example in the wiki doesn't work, but is it a bug?

Am I not understanding the hash required by telethon.tl.functions.contacts.GetContactsRequest?

the code (relevant snippets):

cli = TelegramClient(conf['name'], api_id=conf['app_id'], api_hash=conf['api_hash'])
cli.connect()
me = cli.get_me()
...
getContacts = telethon.tl.functions.contacts.GetContactsRequest(me.access_hash)
Contacts = cli(getContacts)

error:

.../lib/python3.6/site-packages/telethon/tl/functions/contacts.py", line 233, in to_bytes
struct.pack('<i', self.hash),
struct.error: 'i' format requires -2147483648 <= number <= 2147483647

PS: thanks for Telethon, it's so much more stable and predictable than competing solutions

All 8 comments

Hey there I had a similar issue:
https://github.com/LonamiWebs/Telethon/issues/269

If you substitute you me.access_hash for 0 it works fine.

Its some undisclosed formed hash value they are expecting in order to retrieve only contacts not retrieved before, so by passing an empty hash ( 0 ) it works as @Lonami explained in the issue I opened.

it is recommended to use the user's access_hash to request users and contacts.

Whoops, that wrong. It's supposed to be a special hash calculated based on how the contacts you're aware of, so that Telegram can send only the latest changes. I need to update the wiki, or you can do it if you wish. Starting value should be 0. I don't know how to calculate such hash, but clients like Unigram should have the algorithm.

an empty hash works, this is not the case.

It doesn't? Passing 0 should return all.

struct.error: 'i' format requires -2147483648 <= number <= 2147483647

Yes, passing 0 should work then.

PS: thanks for Telethon, it's so much more stable and predictable than competing solutions

Thanks for this, some cheering is needed when all it seems is that everyone else has errors or offer other better/new alternatives…

Thanks to you both!
0 works indeed, although calling it an empty hash is misleading (for me).

I've modified the example in the wiki. I'd love to understand the hash and what we're supposed to put there though.

I'd love to understand the hash and what we're supposed to put there though.

Me too, let's keep this open and see if someone would like to help. To solve this issue, the task is to port to Python the method that calculates the hash based on the contacts you know already. Simply make a pull request with the algorithm and I'll try to figure out how to embed it nicely in the library (or you can do that too!).

These are @stek29's thoughts, cleaned up a bit.

Turns out -O2 changes the behaviour of the shift, while -O0 gives the same result. Shifting is defined as "unsigned divide by 2 X times" in the Intel's ASM manual. So why doesn't it return zero? […] Shifting by 33 actually behaves like shifting by 1, since shr is shift and rotate:

In a logical shift instruction (also referred to as unsigned shift), the bits that slide off the end disappear (except for the last, which goes into the carry flag), and the spaces are always filled with zeros. Logical shifts are best used with unsigned numbers.

"Always filled with zeroes" doesn't seem to be the case here.

[…]

Turns out the algorithm was originally designed for 64 bits, since the document IDs are longs. For 32 bits however it's a no-op. The developers didn't even read the algorithm. This code is equivalent:

func hashIdsOpt(_ ids: [Int32]) -> Int32 {
    var acc: UInt32 = 0

    for id in ids {
        acc = (acc &* 20261)
        acc = (acc &* 20261) &+ UInt32(bitPattern: id)
    }
    return Int32(bitPattern: acc & UInt32(0x7FFFFFFF))
}

So there were supposed to be a version for both 64 and 32 bits but they simply copy-pasted the 64 bit version for 32, which worked surprisingly only because they used high level language on x86_64 (Swift). Java was fine too and on iOS they saw no issues because ARM handles shift by integer size "correctly".

(official developer) It was written for int64 initially.

Seems like things changed between Swift3 and Swift4 as the later doesn't crash. Here's even another version:

def hash20261_32(ints):
    acc = 0
    for id in ints:
            acc = (acc * 20261 * 20261 + id) & 0xffff_ffff
    return acc & 0xffff_ffff

Even with these changes, the algorithm doesn't work. All contacts are resent, even with all possible combinations of versions for the algorithm.

TL;DR, even if we add the algorithm, it won't be of any use (yet).

Thanks for the follow up @Lonami and keep up the great job. Will keep an eye on their API changes as well.

@Ilhicas It's easier to do that [keep an eye on changes] with https://github.com/stek29/tl-schema :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

amir3code picture amir3code  Â·  6Comments

aminhyper picture aminhyper  Â·  3Comments

Lerbytech picture Lerbytech  Â·  5Comments

Humphry150 picture Humphry150  Â·  3Comments

saharokk picture saharokk  Â·  3Comments