mtproto-core icon indicating copy to clipboard operation
mtproto-core copied to clipboard

Adding react-native environment

Open happymaskterriblefate opened this issue 3 years ago • 6 comments

This adds a working react-native environment to mtproto-core. I was unable to get things working with the canonical expo-crypto because it does not accept Uint8Arrays as inputs to it's hashing methods, so I grabbed some off-the-shelf modules to polyfill all the required methods and built-ins.

happymaskterriblefate avatar May 20 '21 04:05 happymaskterriblefate

Switched this PR to use text-encoding instead of web-encoding because web-encoding broke after ejecting my expo app.

happymaskterriblefate avatar Jun 09 '21 12:06 happymaskterriblefate

Friendly bump on this :)

happymaskterriblefate avatar Jul 24 '21 16:07 happymaskterriblefate

Hello, any updates? :) By the way, can I somehow extract this PR into my own local copy of repository and merge it? Maybe make a patch file from this PR for patch-package? I really want to use it in my project! (expo)

VityaSchel avatar Oct 01 '22 07:10 VityaSchel

I was able to use this but I ran into some problems:

  1. You have to replace deprecated asyncstorage from react-native with community package
  2. Some methods are sooooooo slow. Like crypto.getSRPParams takes 2 minutes just to generate parameters. Maybe I'm doing something wrong but I tried running it in isolation with timer and it takes too much time and this method is required for 2fa. Imagine wasting 5 minutes on trying different passwords in order to log in while official clients and nodejs allows you to make requests almost immediately :(

VityaSchel avatar Oct 02 '22 17:10 VityaSchel

It seems to be problem with TextEncoder

Benchmark:

class Crypto {
  constructor({ SHA1, SHA256, PBKDF2, getRandomBytes }) {
    this.SHA1 = SHA1;
    this.SHA256 = SHA256;
    this.PBKDF2 = PBKDF2;
    this.getRandomBytes = getRandomBytes;

    this.rsa = new RSA({ SHA1 });
  }

  async getSRPParams({ g, p, salt1, salt2, gB, password }) {
    const H = this.SHA256;
    const SH = (data, salt) => {
      return this.SHA256(concatBytes(salt, data, salt));
    };
    const PH1 = async (password, salt1, salt2) => {
      return await SH(await SH(password, salt1), salt2);
    };
    const PH2 = async (password, salt1, salt2) => {
      return await SH(
        await this.PBKDF2(await PH1(password, salt1, salt2), salt1, 100000),
        salt2
      );
    };

    const encoder = new TextEncoder();

    // == SECTION 1 == //
    const gBigInt = bigInt(g);
    const gBytes = bigIntToBytes(gBigInt, 256);
    const pBigInt = bytesToBigInt(p);
    const aBigInt = bytesToBigInt(this.getRandomBytes(256));
    const gABigInt = gBigInt.modPow(aBigInt, pBigInt);
    const gABytes = bigIntToBytes(gABigInt);
    const gBBytes = bytesToBigInt(gB);
    // == SECTION 2 == //
    const [k, u, x] = await Promise.all([
      H(concatBytes(p, gBytes)),
      H(concatBytes(gABytes, gB)),
      PH2(encoder.encode(password), salt1, salt2),
    ]);
    // == SECTION 3 == //
    const kBigInt = bytesToBigInt(k);
    const uBigInt = bytesToBigInt(u);
    const xBigInt = bytesToBigInt(x);
    const vBigInt = gBigInt.modPow(xBigInt, pBigInt);
    const kVBigInt = kBigInt.multiply(vBigInt).mod(pBigInt);
    let tBigInt = gBBytes.subtract(kVBigInt).mod(pBigInt);
    if (tBigInt.isNegative()) {
      tBigInt = tBigInt.add(pBigInt);
    }
    const sABigInt = tBigInt.modPow(
      aBigInt.add(uBigInt.multiply(xBigInt)),
      pBigInt
    );
    const sABytes = bigIntToBytes(sABigInt);
    // == SECTION 4 == //
    const kA = await H(sABytes);
    // == SECTION 5 == //
    const M1 = await H(
      concatBytes(
        xorBytes(await H(p), await H(gBytes)),
        await H(salt1),
        await H(salt2),
        gABytes,
        gB,
        kA
      )
    );
    // == SECTION 6 == //

    return { A: gABytes, M1 };
  }
}
Section Timer
1-2 20.97 seconds
2-3 136.16 seconds
3-4 23.15 seconds
4-5 0.01 seconds
5-6 0.00 seconds

Result: 180.29 seconds (3 minutes)

VityaSchel avatar Oct 03 '22 13:10 VityaSchel

I ran additional test to find execution time for this line: PH2(encoder.encode(password), salt1, salt2),:

TextEncoder.encode() function takes 0.01 seconds to complete, and PH2 is 135.61 seconds, so it's a problem with crypto module.

I noticed PBKDF2 takes iterations number as argument in function, which is hardcoded to 100000. Is there any way to optimize it?

Also account.getPassword method takes about 30 seconds to complete. Maybe it's because I'm running it twice? First time I execute it with new socket, it responds immediately.

Update: I think PBKDF2 doesn't work at all. It always gives me incorrect 2fa password error.

VityaSchel avatar Oct 03 '22 13:10 VityaSchel