chore: embraced native bigints
This commit is contained in:
parent
1c60082d61
commit
70f4e40ef5
14 changed files with 380 additions and 204 deletions
|
@ -48,7 +48,6 @@
|
||||||
"@mtcute/tl-runtime": "workspace:^",
|
"@mtcute/tl-runtime": "workspace:^",
|
||||||
"@mtcute/wasm": "workspace:^",
|
"@mtcute/wasm": "workspace:^",
|
||||||
"@types/events": "3.0.0",
|
"@types/events": "3.0.0",
|
||||||
"big-integer": "1.6.51",
|
|
||||||
"events": "3.2.0",
|
"events": "3.2.0",
|
||||||
"long": "5.2.3"
|
"long": "5.2.3"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import bigInt from 'big-integer'
|
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
|
|
||||||
import { mtp } from '@mtcute/tl'
|
import { mtp } from '@mtcute/tl'
|
||||||
|
@ -11,45 +10,46 @@ import { findKeyByFingerprints } from '../utils/crypto/keys.js'
|
||||||
import { millerRabin } from '../utils/crypto/miller-rabin.js'
|
import { millerRabin } from '../utils/crypto/miller-rabin.js'
|
||||||
import { generateKeyAndIvFromNonce } from '../utils/crypto/mtproto.js'
|
import { generateKeyAndIvFromNonce } from '../utils/crypto/mtproto.js'
|
||||||
import { xorBuffer, xorBufferInPlace } from '../utils/crypto/utils.js'
|
import { xorBuffer, xorBufferInPlace } from '../utils/crypto/utils.js'
|
||||||
import { bigIntToBuffer, bufferToBigInt, ICryptoProvider, Logger } from '../utils/index.js'
|
import { bigIntModPow, bigIntToBuffer, bufferToBigInt, ICryptoProvider, Logger } from '../utils/index.js'
|
||||||
import { mtpAssertTypeIs } from '../utils/type-assertions.js'
|
import { mtpAssertTypeIs } from '../utils/type-assertions.js'
|
||||||
import { SessionConnection } from './session-connection.js'
|
import { SessionConnection } from './session-connection.js'
|
||||||
|
|
||||||
// Heavily based on code from https://github.com/LonamiWebs/Telethon/blob/master/telethon/network/authenticator.py
|
// Heavily based on code from https://github.com/LonamiWebs/Telethon/blob/master/telethon/network/authenticator.py
|
||||||
|
|
||||||
// see https://core.telegram.org/mtproto/security_guidelines
|
// see https://core.telegram.org/mtproto/security_guidelines
|
||||||
const DH_SAFETY_RANGE = bigInt[2].pow(2048 - 64)
|
// const DH_SAFETY_RANGE = bigInt[2].pow(2048 - 64)
|
||||||
const KNOWN_DH_PRIME = bigInt(
|
const DH_SAFETY_RANGE = 2n ** (2048n - 64n)
|
||||||
'C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C3720FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F642477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5B',
|
const KNOWN_DH_PRIME =
|
||||||
16,
|
// eslint-disable-next-line max-len
|
||||||
)
|
0xc71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c3720fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f642477fe96bb2a941d5bcd1d4ac8cc49880708fa9b378e3c4f3a9060bee67cf9a4a4a695811051907e162753b56b0f6b410dba74d8a84b2a14b3144e0ef1284754fd17ed950d5965b4b9dd46582db1178d169c6bc465b0d6ff9ca3928fef5b9ae4e418fc15e83ebea0f87fa9ff5eed70050ded2849f47bf959d956850ce929851f0d8115f635b105ee2e4e15d04b2454bf6f4fadf034b10403119cd8e3b92fcc5bn
|
||||||
const TWO_POW_2047 = bigInt[2].pow(2047)
|
const TWO_POW_2047 = 2n ** 2047n
|
||||||
const TWO_POW_2048 = bigInt[2].pow(2048)
|
const TWO_POW_2048 = 2n ** 2048n
|
||||||
|
|
||||||
interface CheckedPrime {
|
interface CheckedPrime {
|
||||||
prime: bigInt.BigInteger
|
prime: bigint
|
||||||
generators: number[]
|
generators: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkedPrimesCache: CheckedPrime[] = []
|
const checkedPrimesCache: CheckedPrime[] = []
|
||||||
|
|
||||||
function checkDhPrime(log: Logger, dhPrime: bigInt.BigInteger, g: number) {
|
function checkDhPrime(log: Logger, dhPrime: bigint, g: number) {
|
||||||
if (KNOWN_DH_PRIME.eq(dhPrime)) {
|
if (KNOWN_DH_PRIME === dhPrime) {
|
||||||
log.debug('server is using known dh prime, skipping validation')
|
log.debug('server is using known dh prime, skipping validation')
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let checkedPrime = checkedPrimesCache.find((x) => x.prime.eq(dhPrime))
|
let checkedPrime = checkedPrimesCache.find((x) => x.prime === dhPrime)
|
||||||
|
|
||||||
if (!checkedPrime) {
|
if (!checkedPrime) {
|
||||||
if (dhPrime.lesserOrEquals(TWO_POW_2047) || dhPrime.greaterOrEquals(TWO_POW_2048)) {
|
if (dhPrime <= TWO_POW_2047 || dhPrime >= TWO_POW_2048) {
|
||||||
throw new MtSecurityError('Step 3: dh_prime is not in the 2048-bit range')
|
throw new MtSecurityError('Step 3: dh_prime is not in the 2048-bit range')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!millerRabin(dhPrime)) {
|
if (!millerRabin(dhPrime)) {
|
||||||
throw new MtSecurityError('Step 3: dh_prime is not prime')
|
throw new MtSecurityError('Step 3: dh_prime is not prime')
|
||||||
}
|
}
|
||||||
if (!millerRabin(dhPrime.minus(1).divide(2))) {
|
if (!millerRabin((dhPrime - 1n) / 2n)) {
|
||||||
throw new MtSecurityError('Step 3: dh_prime is not a safe prime - (dh_prime-1)/2 is not prime')
|
throw new MtSecurityError('Step 3: dh_prime is not a safe prime - (dh_prime-1)/2 is not prime')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,37 +74,37 @@ function checkDhPrime(log: Logger, dhPrime: bigInt.BigInteger, g: number) {
|
||||||
|
|
||||||
switch (g) {
|
switch (g) {
|
||||||
case 2:
|
case 2:
|
||||||
if (dhPrime.mod(8).notEquals(7)) {
|
if (dhPrime % 8n !== 7n) {
|
||||||
throw new MtSecurityError('Step 3: ivalid g - dh_prime mod 8 != 7')
|
throw new MtSecurityError('Step 3: ivalid g - dh_prime mod 8 != 7')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 3:
|
case 3:
|
||||||
if (dhPrime.mod(3).notEquals(2)) {
|
if (dhPrime % 3n !== 2n) {
|
||||||
throw new MtSecurityError('Step 3: ivalid g - dh_prime mod 3 != 2')
|
throw new MtSecurityError('Step 3: ivalid g - dh_prime mod 3 != 2')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 4:
|
case 4:
|
||||||
break
|
break
|
||||||
case 5: {
|
case 5: {
|
||||||
const mod = dhPrime.mod(5)
|
const mod = dhPrime % 5n
|
||||||
|
|
||||||
if (mod.notEquals(1) && mod.notEquals(4)) {
|
if (mod !== 1n && mod !== 4n) {
|
||||||
throw new MtSecurityError('Step 3: ivalid g - dh_prime mod 5 != 1 && dh_prime mod 5 != 4')
|
throw new MtSecurityError('Step 3: ivalid g - dh_prime mod 5 != 1 && dh_prime mod 5 != 4')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 6: {
|
case 6: {
|
||||||
const mod = dhPrime.mod(24)
|
const mod = dhPrime % 24n
|
||||||
|
|
||||||
if (mod.notEquals(19) && mod.notEquals(23)) {
|
if (mod !== 19n && mod !== 23n) {
|
||||||
throw new MtSecurityError('Step 3: ivalid g - dh_prime mod 24 != 19 && dh_prime mod 24 != 23')
|
throw new MtSecurityError('Step 3: ivalid g - dh_prime mod 24 != 19 && dh_prime mod 24 != 23')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 7: {
|
case 7: {
|
||||||
const mod = dhPrime.mod(7)
|
const mod = dhPrime % 7n
|
||||||
|
|
||||||
if (mod.notEquals(3) && mod.notEquals(5) && mod.notEquals(6)) {
|
if (mod !== 3n && mod !== 5n && mod !== 6n) {
|
||||||
throw new MtSecurityError(
|
throw new MtSecurityError(
|
||||||
'Step 3: ivalid g - dh_prime mod 7 != 3 && dh_prime mod 7 != 5 && dh_prime mod 7 != 6',
|
'Step 3: ivalid g - dh_prime mod 7 != 3 && dh_prime mod 7 != 5 && dh_prime mod 7 != 6',
|
||||||
)
|
)
|
||||||
|
@ -123,8 +123,8 @@ function checkDhPrime(log: Logger, dhPrime: bigInt.BigInteger, g: number) {
|
||||||
async function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Promise<Uint8Array> {
|
async function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Promise<Uint8Array> {
|
||||||
// since Summer 2021, they use "version of RSA with a variant of OAEP+ padding explained below"
|
// since Summer 2021, they use "version of RSA with a variant of OAEP+ padding explained below"
|
||||||
|
|
||||||
const keyModulus = bigInt(key.modulus, 16)
|
const keyModulus = BigInt(`0x${key.modulus}`)
|
||||||
const keyExponent = bigInt(key.exponent, 16)
|
const keyExponent = BigInt(`0x${key.exponent}`)
|
||||||
|
|
||||||
if (data.length > 144) {
|
if (data.length > 144) {
|
||||||
throw new MtArgumentError('Failed to pad: too big data')
|
throw new MtArgumentError('Failed to pad: too big data')
|
||||||
|
@ -150,11 +150,11 @@ async function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKe
|
||||||
|
|
||||||
const decryptedDataBigint = bufferToBigInt(decryptedData)
|
const decryptedDataBigint = bufferToBigInt(decryptedData)
|
||||||
|
|
||||||
if (decryptedDataBigint.geq(keyModulus)) {
|
if (decryptedDataBigint >= keyModulus) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const encryptedBigint = decryptedDataBigint.modPow(keyExponent, keyModulus)
|
const encryptedBigint = bigIntModPow(decryptedDataBigint, keyExponent, keyModulus)
|
||||||
|
|
||||||
return bigIntToBuffer(encryptedBigint, 256)
|
return bigIntToBuffer(encryptedBigint, 256)
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,11 @@ async function rsaEncrypt(data: Uint8Array, crypto: ICryptoProvider, key: TlPubl
|
||||||
randomBytes(235 - data.length),
|
randomBytes(235 - data.length),
|
||||||
])
|
])
|
||||||
|
|
||||||
const encryptedBigInt = bufferToBigInt(toEncrypt).modPow(bigInt(key.exponent, 16), bigInt(key.modulus, 16))
|
const encryptedBigInt = bigIntModPow(
|
||||||
|
bufferToBigInt(toEncrypt),
|
||||||
|
BigInt(`0x${key.exponent}`),
|
||||||
|
BigInt(`0x${key.modulus}`),
|
||||||
|
)
|
||||||
|
|
||||||
return bigIntToBuffer(encryptedBigInt)
|
return bigIntToBuffer(encryptedBigInt)
|
||||||
}
|
}
|
||||||
|
@ -323,7 +327,7 @@ export async function doAuthorization(
|
||||||
const dhPrime = bufferToBigInt(serverDhInner.dhPrime)
|
const dhPrime = bufferToBigInt(serverDhInner.dhPrime)
|
||||||
const timeOffset = Math.floor(Date.now() / 1000) - serverDhInner.serverTime
|
const timeOffset = Math.floor(Date.now() / 1000) - serverDhInner.serverTime
|
||||||
|
|
||||||
const g = bigInt(serverDhInner.g)
|
const g = BigInt(serverDhInner.g)
|
||||||
const gA = bufferToBigInt(serverDhInner.gA)
|
const gA = bufferToBigInt(serverDhInner.gA)
|
||||||
|
|
||||||
checkDhPrime(log, dhPrime, serverDhInner.g)
|
checkDhPrime(log, dhPrime, serverDhInner.g)
|
||||||
|
@ -333,26 +337,26 @@ export async function doAuthorization(
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const b = bufferToBigInt(randomBytes(256))
|
const b = bufferToBigInt(randomBytes(256))
|
||||||
const gB = g.modPow(b, dhPrime)
|
const gB = bigIntModPow(g, b, dhPrime)
|
||||||
|
|
||||||
const authKey = bigIntToBuffer(gA.modPow(b, dhPrime))
|
const authKey = bigIntToBuffer(bigIntModPow(gA, b, dhPrime))
|
||||||
const authKeyAuxHash = (await crypto.sha1(authKey)).subarray(0, 8)
|
const authKeyAuxHash = (await crypto.sha1(authKey)).subarray(0, 8)
|
||||||
|
|
||||||
// validate DH params
|
// validate DH params
|
||||||
if (g.lesserOrEquals(1) || g.greaterOrEquals(dhPrime.minus(bigInt.one))) {
|
if (g <= 1 || g >= dhPrime - 1n) {
|
||||||
throw new MtSecurityError('g is not within (1, dh_prime - 1)')
|
throw new MtSecurityError('g is not within (1, dh_prime - 1)')
|
||||||
}
|
}
|
||||||
if (gA.lesserOrEquals(1) || gA.greaterOrEquals(dhPrime.minus(bigInt.one))) {
|
if (gA <= 1 || gA >= dhPrime - 1n) {
|
||||||
throw new MtSecurityError('g_a is not within (1, dh_prime - 1)')
|
throw new MtSecurityError('g_a is not within (1, dh_prime - 1)')
|
||||||
}
|
}
|
||||||
if (gB.lesserOrEquals(1) || gB.greaterOrEquals(dhPrime.minus(bigInt.one))) {
|
if (gB <= 1 || gB >= dhPrime - 1n) {
|
||||||
throw new MtSecurityError('g_b is not within (1, dh_prime - 1)')
|
throw new MtSecurityError('g_b is not within (1, dh_prime - 1)')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gA.lt(DH_SAFETY_RANGE) || gA.gt(dhPrime.minus(DH_SAFETY_RANGE))) {
|
if (gA <= DH_SAFETY_RANGE || gA >= dhPrime - DH_SAFETY_RANGE) {
|
||||||
throw new MtSecurityError('g_a is not within (2^{2048-64}, dh_prime - 2^{2048-64})')
|
throw new MtSecurityError('g_a is not within (2^{2048-64}, dh_prime - 2^{2048-64})')
|
||||||
}
|
}
|
||||||
if (gB.lt(DH_SAFETY_RANGE) || gB.gt(dhPrime.minus(DH_SAFETY_RANGE))) {
|
if (gB <= DH_SAFETY_RANGE || gB >= dhPrime - DH_SAFETY_RANGE) {
|
||||||
throw new MtSecurityError('g_b is not within (2^{2048-64}, dh_prime - 2^{2048-64})')
|
throw new MtSecurityError('g_b is not within (2^{2048-64}, dh_prime - 2^{2048-64})')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { concatBuffers, dataViewFromBuffer } from '../../utils/buffer-utils.js'
|
import { bufferToReversed, concatBuffers, dataViewFromBuffer } from '../../utils/buffer-utils.js'
|
||||||
import { IAesCtr, randomBytes } from '../../utils/index.js'
|
import { IAesCtr, randomBytes } from '../../utils/index.js'
|
||||||
import { IPacketCodec } from './abstract.js'
|
import { IPacketCodec } from './abstract.js'
|
||||||
import { WrappedCodec } from './wrapped.js'
|
import { WrappedCodec } from './wrapped.js'
|
||||||
|
@ -64,8 +64,7 @@ export class ObfuscatedPacketCodec extends WrappedCodec implements IPacketCodec
|
||||||
dv.setInt16(60, dcId, true)
|
dv.setInt16(60, dcId, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// randomBytes may return a Buffer in Node.js, whose .slice() doesn't copy
|
const randomRev = bufferToReversed(random, 8, 56)
|
||||||
const randomRev = Uint8Array.prototype.slice.call(random, 8, 56).reverse()
|
|
||||||
|
|
||||||
let encryptKey = random.subarray(8, 40)
|
let encryptKey = random.subarray(8, 40)
|
||||||
const encryptIv = random.subarray(40, 56)
|
const encryptIv = random.subarray(40, 56)
|
||||||
|
|
|
@ -1,55 +1,91 @@
|
||||||
import bigInt, { BigInteger } from 'big-integer'
|
import { bufferToReversed, randomBytes } from './buffer-utils.js'
|
||||||
|
|
||||||
import { randomBytes } from './buffer-utils.js'
|
/**
|
||||||
|
* Get the minimum number of bits required to represent a number
|
||||||
|
*/
|
||||||
|
export function bigIntBitLength(n: bigint) {
|
||||||
|
// not the fastest way, but at least not .toString(2) and not too complex
|
||||||
|
// taken from: https://stackoverflow.com/a/76616288/22656950
|
||||||
|
|
||||||
|
const i = (n.toString(16).length - 1) * 4
|
||||||
|
|
||||||
|
return i + 32 - Math.clz32(Number(n >> BigInt(i)))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a big integer to a buffer
|
* Convert a big integer to a buffer
|
||||||
*
|
*
|
||||||
* @param value Value to convert
|
* @param value Value to convert
|
||||||
* @param length Length of the resulting buffer (by default it's computed automatically)
|
* @param length Length of the resulting buffer (by default it's the minimum required)
|
||||||
* @param le Whether to use little-endian encoding
|
* @param le Whether to use little-endian encoding
|
||||||
*/
|
*/
|
||||||
export function bigIntToBuffer(value: BigInteger, length = 0, le = false): Uint8Array {
|
export function bigIntToBuffer(value: bigint, length = 0, le = false): Uint8Array {
|
||||||
const array = value.toArray(256).value
|
const bits = bigIntBitLength(value)
|
||||||
|
const bytes = Math.ceil(bits / 8)
|
||||||
|
|
||||||
if (length !== 0 && array.length > length) {
|
if (length !== 0 && bytes > length) {
|
||||||
throw new Error('Value out of bounds')
|
throw new Error('Value out of bounds')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length !== 0) {
|
if (length === 0) length = bytes
|
||||||
// padding
|
|
||||||
while (array.length !== length) array.unshift(0)
|
const buf = new ArrayBuffer(length)
|
||||||
|
const u8 = new Uint8Array(buf)
|
||||||
|
|
||||||
|
const unaligned = length % 8
|
||||||
|
const dv = new DataView(buf, 0, length - unaligned)
|
||||||
|
|
||||||
|
// it is faster to work with 64-bit words than with bytes directly
|
||||||
|
for (let i = 0; i < dv.byteLength; i += 8) {
|
||||||
|
dv.setBigUint64(i, value & 0xffffffffffffffffn, true)
|
||||||
|
value >>= 64n
|
||||||
}
|
}
|
||||||
|
|
||||||
if (le) array.reverse()
|
if (unaligned > 0) {
|
||||||
|
for (let i = length - unaligned; i < length; i++) {
|
||||||
|
u8[i] = Number(value & 0xffn)
|
||||||
|
value >>= 8n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const buffer = new Uint8Array(length || array.length)
|
if (!le) u8.reverse()
|
||||||
buffer.set(array, 0)
|
|
||||||
|
|
||||||
return buffer
|
return u8
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a buffer to a big integer
|
* Convert a buffer to a big integer
|
||||||
*
|
*
|
||||||
* @param buffer Buffer to convert
|
* @param buffer Buffer to convert
|
||||||
* @param offset Offset to start reading from
|
|
||||||
* @param length Length to read
|
|
||||||
* @param le Whether to use little-endian encoding
|
* @param le Whether to use little-endian encoding
|
||||||
*/
|
*/
|
||||||
export function bufferToBigInt(buffer: Uint8Array, offset = 0, length = buffer.length, le = false): BigInteger {
|
export function bufferToBigInt(buffer: Uint8Array, le = false): bigint {
|
||||||
const arr = [...buffer.subarray(offset, offset + length)]
|
if (le) buffer = bufferToReversed(buffer)
|
||||||
|
|
||||||
if (le) arr.reverse()
|
const unaligned = buffer.length % 8
|
||||||
|
const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength - unaligned)
|
||||||
|
|
||||||
return bigInt.fromArray(arr as unknown as number[], 256)
|
let res = 0n
|
||||||
|
|
||||||
|
// it is faster to work with 64-bit words than with bytes directly
|
||||||
|
for (let i = 0; i < dv.byteLength; i += 8) {
|
||||||
|
res = (res << 64n) | BigInt(dv.getBigUint64(i, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unaligned > 0) {
|
||||||
|
for (let i = buffer.length - unaligned; i < buffer.length; i++) {
|
||||||
|
res = (res << 8n) | BigInt(buffer[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a random big integer of the given size (in bytes)
|
* Generate a random big integer of the given size (in bytes)
|
||||||
* @param size Size in bytes
|
* @param size Size in bytes
|
||||||
*/
|
*/
|
||||||
export function randomBigInt(size: number): BigInteger {
|
export function randomBigInt(size: number): bigint {
|
||||||
return bufferToBigInt(randomBytes(size))
|
return bufferToBigInt(randomBytes(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,14 +93,14 @@ export function randomBigInt(size: number): BigInteger {
|
||||||
* Generate a random big integer of the given size (in bits)
|
* Generate a random big integer of the given size (in bits)
|
||||||
* @param bits
|
* @param bits
|
||||||
*/
|
*/
|
||||||
export function randomBigIntBits(bits: number): BigInteger {
|
export function randomBigIntBits(bits: number): bigint {
|
||||||
let num = randomBigInt(Math.ceil(bits / 8))
|
let num = randomBigInt(Math.ceil(bits / 8))
|
||||||
|
|
||||||
const bitLength = num.bitLength()
|
const bitLength = bigIntBitLength(num)
|
||||||
|
|
||||||
if (bitLength.gt(bits)) {
|
if (bitLength > bits) {
|
||||||
const toTrim = bigInt.randBetween(bitLength.minus(bits), 8)
|
const toTrim = bitLength - bits
|
||||||
num = num.shiftRight(toTrim)
|
num >>= BigInt(toTrim)
|
||||||
}
|
}
|
||||||
|
|
||||||
return num
|
return num
|
||||||
|
@ -76,31 +112,119 @@ export function randomBigIntBits(bits: number): BigInteger {
|
||||||
* @param max Maximum value (exclusive)
|
* @param max Maximum value (exclusive)
|
||||||
* @param min Minimum value (inclusive)
|
* @param min Minimum value (inclusive)
|
||||||
*/
|
*/
|
||||||
export function randomBigIntInRange(max: BigInteger, min = bigInt.one): BigInteger {
|
export function randomBigIntInRange(max: bigint, min = 1n): bigint {
|
||||||
const interval = max.minus(min)
|
const interval = max - min
|
||||||
if (interval.isNegative()) throw new Error('expected min < max')
|
if (interval < 0n) throw new Error('expected min < max')
|
||||||
|
|
||||||
const byteSize = interval.bitLength().divide(8).toJSNumber()
|
const byteSize = bigIntBitLength(interval) / 8
|
||||||
|
|
||||||
let result = randomBigInt(byteSize)
|
let result = randomBigInt(byteSize)
|
||||||
while (result.gt(interval)) result = result.minus(interval)
|
while (result > interval) result -= interval
|
||||||
|
|
||||||
return min.plus(result)
|
return min + result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the multiplicity of 2 in the prime factorization of n
|
* Compute the multiplicity of 2 in the prime factorization of n
|
||||||
* @param n
|
* @param n
|
||||||
*/
|
*/
|
||||||
export function twoMultiplicity(n: BigInteger): BigInteger {
|
export function twoMultiplicity(n: bigint): bigint {
|
||||||
if (n === bigInt.zero) return bigInt.zero
|
if (n === 0n) return 0n
|
||||||
|
|
||||||
let m = bigInt.zero
|
let m = 0n
|
||||||
let pow = bigInt.one
|
let pow = 1n
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!n.and(pow).isZero()) return m
|
if ((n & pow) !== 0n) return m
|
||||||
m = m.plus(bigInt.one)
|
m += 1n
|
||||||
pow = pow.shiftLeft(1)
|
pow <<= 1n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bigIntMin(a: bigint, b: bigint): bigint {
|
||||||
|
return a < b ? a : b
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bigIntAbs(a: bigint): bigint {
|
||||||
|
return a < 0n ? -a : a
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bigIntGcd(a: bigint, b: bigint): bigint {
|
||||||
|
// using euclidean algorithm is fast enough on smaller numbers
|
||||||
|
// https://en.wikipedia.org/wiki/Euclidean_algorithm#Implementations
|
||||||
|
|
||||||
|
while (b !== 0n) {
|
||||||
|
const t = b
|
||||||
|
b = a % b
|
||||||
|
a = t
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bigIntModPow(base: bigint, exp: bigint, mod: bigint): bigint {
|
||||||
|
// using the binary method is good enough for our use case
|
||||||
|
// https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method
|
||||||
|
|
||||||
|
base %= mod
|
||||||
|
|
||||||
|
let result = 1n
|
||||||
|
|
||||||
|
while (exp > 0n) {
|
||||||
|
if (exp % 2n === 1n) {
|
||||||
|
result = (result * base) % mod
|
||||||
|
}
|
||||||
|
|
||||||
|
exp >>= 1n
|
||||||
|
base = base ** 2n % mod
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// below code is based on https://github.com/juanelas/bigint-mod-arith, MIT license
|
||||||
|
|
||||||
|
function eGcd(a: bigint, b: bigint): [bigint, bigint, bigint] {
|
||||||
|
let x = 0n
|
||||||
|
let y = 1n
|
||||||
|
let u = 1n
|
||||||
|
let v = 0n
|
||||||
|
|
||||||
|
while (a !== 0n) {
|
||||||
|
const q = b / a
|
||||||
|
const r: bigint = b % a
|
||||||
|
const m = x - u * q
|
||||||
|
const n = y - v * q
|
||||||
|
b = a
|
||||||
|
a = r
|
||||||
|
x = u
|
||||||
|
y = v
|
||||||
|
u = m
|
||||||
|
v = n
|
||||||
|
}
|
||||||
|
|
||||||
|
return [b, x, y]
|
||||||
|
}
|
||||||
|
|
||||||
|
function toZn(a: number | bigint, n: number | bigint): bigint {
|
||||||
|
if (typeof a === 'number') a = BigInt(a)
|
||||||
|
if (typeof n === 'number') n = BigInt(n)
|
||||||
|
|
||||||
|
if (n <= 0n) {
|
||||||
|
throw new RangeError('n must be > 0')
|
||||||
|
}
|
||||||
|
|
||||||
|
const aZn = a % n
|
||||||
|
|
||||||
|
return aZn < 0n ? aZn + n : aZn
|
||||||
|
}
|
||||||
|
|
||||||
|
export function bigIntModInv(a: bigint, n: bigint): bigint {
|
||||||
|
const [g, x] = eGcd(toZn(a, n), n)
|
||||||
|
|
||||||
|
if (g !== 1n) {
|
||||||
|
throw new RangeError(`${a.toString()} does not have inverse modulo ${n.toString()}`) // modular inverse does not exist
|
||||||
|
} else {
|
||||||
|
return toZn(x, n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,23 @@ export function concatBuffers(buffers: Uint8Array[]): Uint8Array {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut for creating a DataView from a Uint8Array
|
||||||
|
*/
|
||||||
export function dataViewFromBuffer(buf: Uint8Array): DataView {
|
export function dataViewFromBuffer(buf: Uint8Array): DataView {
|
||||||
return new DataView(buf.buffer, buf.byteOffset, buf.byteLength)
|
return new DataView(buf.buffer, buf.byteOffset, buf.byteLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse a buffer (or a part of it) into a new buffer
|
||||||
|
*/
|
||||||
|
export function bufferToReversed(buf: Uint8Array, start = 0, end = buf.length): Uint8Array {
|
||||||
|
const len = end - start
|
||||||
|
const ret = new Uint8Array(len)
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
ret[i] = buf[end - i - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import bigInt, { BigInteger } from 'big-integer'
|
import {
|
||||||
|
bigIntAbs,
|
||||||
import { bigIntToBuffer, bufferToBigInt, randomBigIntInRange } from '../bigint-utils.js'
|
bigIntGcd,
|
||||||
|
bigIntMin,
|
||||||
|
bigIntToBuffer,
|
||||||
|
bufferToBigInt,
|
||||||
|
randomBigIntInRange,
|
||||||
|
} from '../bigint-utils.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factorize `p*q` to `p` and `q` synchronously using Brent-Pollard rho algorithm
|
* Factorize `p*q` to `p` and `q` synchronously using Brent-Pollard rho algorithm
|
||||||
|
@ -10,12 +15,12 @@ export function factorizePQSync(pq: Uint8Array): [Uint8Array, Uint8Array] {
|
||||||
const pq_ = bufferToBigInt(pq)
|
const pq_ = bufferToBigInt(pq)
|
||||||
|
|
||||||
const n = PollardRhoBrent(pq_)
|
const n = PollardRhoBrent(pq_)
|
||||||
const m = pq_.divide(n)
|
const m = pq_ / n
|
||||||
|
|
||||||
let p
|
let p
|
||||||
let q
|
let q
|
||||||
|
|
||||||
if (n.lt(m)) {
|
if (n < m) {
|
||||||
p = n
|
p = n
|
||||||
q = m
|
q = m
|
||||||
} else {
|
} else {
|
||||||
|
@ -26,50 +31,46 @@ export function factorizePQSync(pq: Uint8Array): [Uint8Array, Uint8Array] {
|
||||||
return [bigIntToBuffer(p), bigIntToBuffer(q)]
|
return [bigIntToBuffer(p), bigIntToBuffer(q)]
|
||||||
}
|
}
|
||||||
|
|
||||||
function PollardRhoBrent(n: BigInteger): BigInteger {
|
function PollardRhoBrent(n: bigint): bigint {
|
||||||
if (n.isEven()) return bigInt[2]
|
if (n % 2n === 0n) return 2n
|
||||||
|
|
||||||
let y = randomBigIntInRange(n.minus(1))
|
let y = randomBigIntInRange(n - 1n)
|
||||||
const c = randomBigIntInRange(n.minus(1))
|
const c = randomBigIntInRange(n - 1n)
|
||||||
const m = randomBigIntInRange(n.minus(1))
|
const m = randomBigIntInRange(n - 1n)
|
||||||
let g = bigInt.one
|
let g = 1n
|
||||||
let r = bigInt.one
|
let r = 1n
|
||||||
let q = bigInt.one
|
let q = 1n
|
||||||
|
|
||||||
let ys: BigInteger
|
let ys: bigint
|
||||||
let x: BigInteger
|
let x: bigint
|
||||||
|
|
||||||
while (g.eq(bigInt.one)) {
|
while (g === 1n) {
|
||||||
x = y
|
x = y
|
||||||
for (let i = 0; r.geq(i); i++) y = y.multiply(y).mod(n).plus(c).mod(n)
|
for (let i = 0; r >= i; i++) y = (((y * y) % n) + c) % n
|
||||||
// y = ((y * y) % n + c) % n
|
|
||||||
|
|
||||||
let k = bigInt.zero
|
let k = 0n
|
||||||
|
|
||||||
while (k.lt(r) && g.eq(1)) {
|
while (k < r && g === 1n) {
|
||||||
ys = y
|
ys = y
|
||||||
|
|
||||||
for (let i = bigInt.zero; i.lt(bigInt.min(m, r.minus(k))); i = i.plus(bigInt.one)) {
|
for (let i = 0n; i < bigIntMin(m, r - k); i++) {
|
||||||
y = y.multiply(y).mod(n).plus(c).mod(n)
|
y = (((y * y) % n) + c) % n
|
||||||
q = q.multiply(x.minus(y).abs()).mod(n)
|
q = (q * bigIntAbs(x - y)) % n
|
||||||
// y = (y * y % n + c) % n
|
|
||||||
// q = q * abs(x - y) % n
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g = bigInt.gcd(q, n)
|
g = bigIntGcd(q, n)
|
||||||
k = k.plus(m)
|
k = k + m
|
||||||
}
|
}
|
||||||
|
|
||||||
r = r.multiply(bigInt[2])
|
r <<= 1n
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g.eq(n)) {
|
if (g === n) {
|
||||||
do {
|
do {
|
||||||
ys = ys!.multiply(ys!).mod(n).plus(c).mod(n)
|
ys = (((ys! * ys!) % n) + c) % n
|
||||||
// ys = ((ys * ys) % n + c) % n
|
|
||||||
|
|
||||||
g = bigInt.gcd(x!.minus(ys), n)
|
g = bigIntGcd(x! - ys!, n)
|
||||||
} while (g.leq(bigInt.one))
|
} while (g <= 1n)
|
||||||
}
|
}
|
||||||
|
|
||||||
return g
|
return g
|
||||||
|
|
|
@ -1,42 +1,42 @@
|
||||||
import bigInt, { BigInteger } from 'big-integer'
|
import { bigIntBitLength, bigIntModPow, randomBigIntBits, twoMultiplicity } from '../bigint-utils.js'
|
||||||
|
|
||||||
import { randomBigIntBits, twoMultiplicity } from '../bigint-utils.js'
|
export function millerRabin(n: bigint, rounds = 20): boolean {
|
||||||
|
|
||||||
export function millerRabin(n: BigInteger, rounds = 20): boolean {
|
|
||||||
// small numbers: 0, 1 are not prime, 2, 3 are prime
|
// small numbers: 0, 1 are not prime, 2, 3 are prime
|
||||||
if (n.lt(bigInt[4])) return n.gt(bigInt[1])
|
if (n < 4n) return n > 1n
|
||||||
if (n.isEven() || n.isNegative()) return false
|
if (n % 2n === 0n || n < 0n) return false
|
||||||
|
|
||||||
const nBits = n.bitLength().toJSNumber()
|
const nBits = bigIntBitLength(n)
|
||||||
const nSub = n.minus(1)
|
const nSub = n - 1n
|
||||||
|
|
||||||
const r = twoMultiplicity(nSub)
|
const r = twoMultiplicity(nSub)
|
||||||
const d = nSub.shiftRight(r)
|
const d = nSub >> r
|
||||||
|
|
||||||
for (let i = 0; i < rounds; i++) {
|
for (let i = 0; i < rounds; i++) {
|
||||||
let base
|
let base
|
||||||
|
|
||||||
do {
|
do {
|
||||||
base = randomBigIntBits(nBits)
|
base = randomBigIntBits(nBits)
|
||||||
} while (base.leq(bigInt.one) || base.geq(nSub))
|
} while (base <= 1n || base >= nSub)
|
||||||
|
|
||||||
let x = base.modPow(d, n)
|
let x = bigIntModPow(base, d, n)
|
||||||
if (x.eq(bigInt.one) || x.eq(nSub)) continue
|
// if (x.eq(bigInt.one) || x.eq(nSub)) continue
|
||||||
|
if (x === 1n || x === nSub) continue
|
||||||
|
|
||||||
let i = bigInt.zero
|
let i = 0n
|
||||||
let y: BigInteger
|
let y: bigint
|
||||||
|
|
||||||
while (i.lt(r)) {
|
while (i < r) {
|
||||||
y = x.modPow(bigInt[2], n)
|
// y = x.modPow(bigInt[2], n)
|
||||||
|
y = bigIntModPow(x, 2n, n)
|
||||||
|
|
||||||
if (x.eq(bigInt.one)) return false
|
if (x === 1n) return false
|
||||||
if (x.eq(nSub)) break
|
if (x === nSub) break
|
||||||
i = i.plus(bigInt.one)
|
i += 1n
|
||||||
|
|
||||||
x = y
|
x = y
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i.eq(r)) return false
|
if (i === r) return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import bigInt from 'big-integer'
|
|
||||||
|
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
import { utf8EncodeToBuffer } from '@mtcute/tl-runtime'
|
import { utf8EncodeToBuffer } from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
import { MtSecurityError, MtUnsupportedError } from '../../types/errors.js'
|
import { MtSecurityError, MtUnsupportedError } from '../../types/errors.js'
|
||||||
import { bigIntToBuffer, bufferToBigInt } from '../bigint-utils.js'
|
import { bigIntModPow, bigIntToBuffer, bufferToBigInt } from '../bigint-utils.js'
|
||||||
import { concatBuffers, randomBytes } from '../buffer-utils.js'
|
import { concatBuffers, randomBytes } from '../buffer-utils.js'
|
||||||
import { ICryptoProvider } from './abstract.js'
|
import { ICryptoProvider } from './abstract.js'
|
||||||
import { xorBuffer } from './utils.js'
|
import { xorBuffer } from './utils.js'
|
||||||
|
@ -47,11 +45,11 @@ export async function computeNewPasswordHash(
|
||||||
|
|
||||||
const _x = await computePasswordHash(crypto, utf8EncodeToBuffer(password), algo.salt1, algo.salt2)
|
const _x = await computePasswordHash(crypto, utf8EncodeToBuffer(password), algo.salt1, algo.salt2)
|
||||||
|
|
||||||
const g = bigInt(algo.g)
|
const g = BigInt(algo.g)
|
||||||
const p = bufferToBigInt(algo.p)
|
const p = bufferToBigInt(algo.p)
|
||||||
const x = bufferToBigInt(_x)
|
const x = bufferToBigInt(_x)
|
||||||
|
|
||||||
return bigIntToBuffer(g.modPow(x, p), 256)
|
return bigIntToBuffer(bigIntModPow(g, x, p), 256)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,13 +84,13 @@ export async function computeSrpParams(
|
||||||
throw new MtSecurityError('SRP_ID is not present in the request')
|
throw new MtSecurityError('SRP_ID is not present in the request')
|
||||||
}
|
}
|
||||||
|
|
||||||
const g = bigInt(algo.g)
|
const g = BigInt(algo.g)
|
||||||
const _g = bigIntToBuffer(g, 256)
|
const _g = bigIntToBuffer(g, 256)
|
||||||
const p = bufferToBigInt(algo.p)
|
const p = bufferToBigInt(algo.p)
|
||||||
const gB = bufferToBigInt(request.srpB)
|
const gB = bufferToBigInt(request.srpB)
|
||||||
|
|
||||||
const a = bufferToBigInt(randomBytes(256))
|
const a = bufferToBigInt(randomBytes(256))
|
||||||
const gA = g.modPow(a, p)
|
const gA = bigIntModPow(g, a, p)
|
||||||
const _gA = bigIntToBuffer(gA, 256)
|
const _gA = bigIntToBuffer(gA, 256)
|
||||||
|
|
||||||
const H = (data: Uint8Array) => crypto.sha256(data)
|
const H = (data: Uint8Array) => crypto.sha256(data)
|
||||||
|
@ -107,12 +105,12 @@ export async function computeSrpParams(
|
||||||
const u = bufferToBigInt(_u)
|
const u = bufferToBigInt(_u)
|
||||||
const x = bufferToBigInt(_x)
|
const x = bufferToBigInt(_x)
|
||||||
|
|
||||||
const v = g.modPow(x, p)
|
const v = bigIntModPow(g, x, p)
|
||||||
const kV = k.multiply(v).mod(p)
|
const kV = (k * v) % p
|
||||||
|
|
||||||
let t = gB.minus(kV).mod(p)
|
let t = gB - kV
|
||||||
if (t.isNegative()) t = t.plus(p)
|
if (t < 0n) t += p
|
||||||
const sA = t.modPow(a.plus(u.multiply(x)), p)
|
const sA = bigIntModPow(t, a + u * x, p)
|
||||||
const _kA = await H(bigIntToBuffer(sA, 256))
|
const _kA = await H(bigIntToBuffer(sA, 256))
|
||||||
|
|
||||||
const _M1 = await H(
|
const _M1 = await H(
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import bigInt from 'big-integer'
|
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { describe, it } from 'mocha'
|
import { describe, it } from 'mocha'
|
||||||
|
|
||||||
|
@ -6,38 +5,62 @@ import { hexDecodeToBuffer } from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
import { bigIntToBuffer, bufferToBigInt } from '../src/utils/index.js'
|
import { bigIntToBuffer, bufferToBigInt } from '../src/utils/index.js'
|
||||||
|
|
||||||
// since bigIntToBuffer is a tiny wrapper over writeBigInt, no need to test it individually
|
|
||||||
describe('bigIntToBuffer', () => {
|
describe('bigIntToBuffer', () => {
|
||||||
it('should handle writing to BE', () => {
|
it('should handle writing to BE', () => {
|
||||||
expect([...bigIntToBuffer(bigInt('10495708'), 0, false)]).eql([0xa0, 0x26, 0xdc])
|
expect([...bigIntToBuffer(BigInt('10495708'), 0, false)]).eql([0xa0, 0x26, 0xdc])
|
||||||
expect([...bigIntToBuffer(bigInt('10495708'), 4, false)]).eql([0x00, 0xa0, 0x26, 0xdc])
|
expect([...bigIntToBuffer(BigInt('10495708'), 4, false)]).eql([0x00, 0xa0, 0x26, 0xdc])
|
||||||
expect([...bigIntToBuffer(bigInt('10495708'), 8, false)]).eql([0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x26, 0xdc])
|
expect([...bigIntToBuffer(BigInt('10495708'), 8, false)]).eql([0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x26, 0xdc])
|
||||||
expect([...bigIntToBuffer(bigInt('3038102549'), 4, false)]).eql([0xb5, 0x15, 0xc4, 0x15])
|
expect([...bigIntToBuffer(BigInt('3038102549'), 4, false)]).eql([0xb5, 0x15, 0xc4, 0x15])
|
||||||
expect([...bigIntToBuffer(bigInt('9341376580368336208'), 8, false)]).eql([
|
expect([...bigIntToBuffer(BigInt('9341376580368336208'), 8, false)]).eql([
|
||||||
...hexDecodeToBuffer('81A33C81D2020550'),
|
...hexDecodeToBuffer('81A33C81D2020550'),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle writing to LE', () => {
|
it('should handle writing to LE', () => {
|
||||||
expect([...bigIntToBuffer(bigInt('10495708'), 0, true)]).eql([0xdc, 0x26, 0xa0])
|
expect([...bigIntToBuffer(BigInt('10495708'), 0, true)]).eql([0xdc, 0x26, 0xa0])
|
||||||
expect([...bigIntToBuffer(bigInt('10495708'), 4, true)]).eql([0xdc, 0x26, 0xa0, 0x00])
|
expect([...bigIntToBuffer(BigInt('10495708'), 4, true)]).eql([0xdc, 0x26, 0xa0, 0x00])
|
||||||
expect([...bigIntToBuffer(bigInt('10495708'), 8, true)]).eql([0xdc, 0x26, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00])
|
expect([...bigIntToBuffer(BigInt('10495708'), 8, true)]).eql([0xdc, 0x26, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||||
expect([...bigIntToBuffer(bigInt('3038102549'), 4, true)]).eql([0x15, 0xc4, 0x15, 0xb5])
|
expect([...bigIntToBuffer(BigInt('3038102549'), 4, true)]).eql([0x15, 0xc4, 0x15, 0xb5])
|
||||||
expect([...bigIntToBuffer(bigInt('9341376580368336208'), 8, true)]).eql([
|
expect([...bigIntToBuffer(BigInt('9341376580368336208'), 8, true)]).eql([
|
||||||
...hexDecodeToBuffer('81A33C81D2020550').reverse(),
|
...hexDecodeToBuffer('81A33C81D2020550').reverse(),
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should handle large integers', () => {
|
||||||
|
const buf = hexDecodeToBuffer(
|
||||||
|
'1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538',
|
||||||
|
)
|
||||||
|
const num = BigInt(
|
||||||
|
'0x1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538',
|
||||||
|
)
|
||||||
|
|
||||||
|
expect([...bigIntToBuffer(num, 0, false)]).eql([...buf])
|
||||||
|
expect([...bigIntToBuffer(num, 0, true)]).eql([...buf.reverse()])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('bufferToBigInt', () => {
|
describe('bufferToBigInt', () => {
|
||||||
it('should handle reading BE', () => {
|
it('should handle reading BE', () => {
|
||||||
expect(bufferToBigInt(new Uint8Array([0xa0, 0x26, 0xdc]), 0, 3, false).toString()).eq('10495708')
|
expect(bufferToBigInt(new Uint8Array([0xa0, 0x26, 0xdc]), false).toString()).eq('10495708')
|
||||||
expect(bufferToBigInt(new Uint8Array([0x00, 0xa0, 0x26, 0xdc]), 0, 4, false).toString()).eq('10495708')
|
expect(bufferToBigInt(new Uint8Array([0x00, 0xa0, 0x26, 0xdc]), false).toString()).eq('10495708')
|
||||||
expect(bufferToBigInt(new Uint8Array([0xb5, 0x15, 0xc4, 0x15]), 0, 4, false).toString()).eq('3038102549')
|
expect(bufferToBigInt(new Uint8Array([0xb5, 0x15, 0xc4, 0x15]), false).toString()).eq('3038102549')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle reading LE', () => {
|
it('should handle reading LE', () => {
|
||||||
expect(bufferToBigInt(new Uint8Array([0xdc, 0x26, 0xa0]), 0, 3, true).toString()).eq('10495708')
|
expect(bufferToBigInt(new Uint8Array([0xdc, 0x26, 0xa0]), true).toString()).eq('10495708')
|
||||||
expect(bufferToBigInt(new Uint8Array([0xdc, 0x26, 0xa0, 0x00]), 0, 4, true).toString()).eq('10495708')
|
expect(bufferToBigInt(new Uint8Array([0xdc, 0x26, 0xa0, 0x00]), true).toString()).eq('10495708')
|
||||||
expect(bufferToBigInt(new Uint8Array([0x15, 0xc4, 0x15, 0xb5]), 0, 4, true).toString()).eq('3038102549')
|
expect(bufferToBigInt(new Uint8Array([0x15, 0xc4, 0x15, 0xb5]), true).toString()).eq('3038102549')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle large integers', () => {
|
||||||
|
const buf = hexDecodeToBuffer(
|
||||||
|
'1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538',
|
||||||
|
)
|
||||||
|
const num = BigInt(
|
||||||
|
'0x1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538',
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(bufferToBigInt(buf, false).toString()).eq(num.toString())
|
||||||
|
expect(bufferToBigInt(buf.reverse(), true).toString()).eq(num.toString())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,11 +3,7 @@ import { describe, it } from 'mocha'
|
||||||
|
|
||||||
import { hexEncode, utf8Decode, utf8EncodeToBuffer } from '@mtcute/tl-runtime'
|
import { hexEncode, utf8Decode, utf8EncodeToBuffer } from '@mtcute/tl-runtime'
|
||||||
|
|
||||||
import {
|
import { buffersEqual, bufferToReversed, cloneBuffer, concatBuffers, randomBytes } from '../src/utils/buffer-utils.js'
|
||||||
buffersEqual,
|
|
||||||
cloneBuffer, concatBuffers,
|
|
||||||
randomBytes,
|
|
||||||
} from '../src/utils/buffer-utils.js'
|
|
||||||
import { xorBuffer, xorBufferInPlace } from '../src/utils/crypto/utils.js'
|
import { xorBuffer, xorBufferInPlace } from '../src/utils/crypto/utils.js'
|
||||||
|
|
||||||
describe('buffersEqual', () => {
|
describe('buffersEqual', () => {
|
||||||
|
@ -113,10 +109,7 @@ describe('cloneBuffer', () => {
|
||||||
|
|
||||||
describe('concatBuffers', () => {
|
describe('concatBuffers', () => {
|
||||||
it('should concat buffers', () => {
|
it('should concat buffers', () => {
|
||||||
const buf = concatBuffers([
|
const buf = concatBuffers([new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])])
|
||||||
new Uint8Array([1, 2, 3]),
|
|
||||||
new Uint8Array([4, 5, 6]),
|
|
||||||
])
|
|
||||||
|
|
||||||
expect([...buf]).eql([1, 2, 3, 4, 5, 6])
|
expect([...buf]).eql([1, 2, 3, 4, 5, 6])
|
||||||
})
|
})
|
||||||
|
@ -130,3 +123,25 @@ describe('concatBuffers', () => {
|
||||||
expect(buf1[0]).not.eql(0xff)
|
expect(buf1[0]).not.eql(0xff)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('bufferToReversed', () => {
|
||||||
|
it('should reverse the buffer', () => {
|
||||||
|
const buf = bufferToReversed(new Uint8Array([1, 2, 3, 4, 5, 6]))
|
||||||
|
|
||||||
|
expect([...buf]).eql([6, 5, 4, 3, 2, 1])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should reverse a part of the buffer', () => {
|
||||||
|
const buf = bufferToReversed(new Uint8Array([1, 2, 3, 4, 5, 6]), 1, 5)
|
||||||
|
|
||||||
|
expect([...buf]).eql([5, 4, 3, 2])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should create a new buffer', () => {
|
||||||
|
const buf1 = new Uint8Array([1, 2, 3])
|
||||||
|
const buf2 = bufferToReversed(buf1)
|
||||||
|
|
||||||
|
buf2[0] = 0xff
|
||||||
|
expect([...buf1]).eql([1, 2, 3])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import bigInt from 'big-integer'
|
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { describe, it } from 'mocha'
|
import { describe, it } from 'mocha'
|
||||||
|
|
||||||
|
@ -7,8 +6,8 @@ import { millerRabin } from '../src/utils/crypto/miller-rabin.js'
|
||||||
describe('miller-rabin test', function () {
|
describe('miller-rabin test', function () {
|
||||||
this.timeout(10000) // since miller-rabin factorization relies on RNG, it may take a while (or may not!)
|
this.timeout(10000) // since miller-rabin factorization relies on RNG, it may take a while (or may not!)
|
||||||
|
|
||||||
const testMillerRabin = (n: bigInt.BigNumber, isPrime: boolean) => {
|
const testMillerRabin = (n: number | string | bigint, isPrime: boolean) => {
|
||||||
expect(millerRabin(bigInt(n as number))).eq(isPrime)
|
expect(millerRabin(BigInt(n))).eq(isPrime)
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should correctly label small primes as probable primes', () => {
|
it('should correctly label small primes as probable primes', () => {
|
||||||
|
@ -134,6 +133,6 @@ describe('miller-rabin test', function () {
|
||||||
// dh_prime used by telegram, as seen in https://core.telegram.org/mtproto/security_guidelines
|
// dh_prime used by telegram, as seen in https://core.telegram.org/mtproto/security_guidelines
|
||||||
const telegramDhPrime =
|
const telegramDhPrime =
|
||||||
'C7 1C AE B9 C6 B1 C9 04 8E 6C 52 2F 70 F1 3F 73 98 0D 40 23 8E 3E 21 C1 49 34 D0 37 56 3D 93 0F 48 19 8A 0A A7 C1 40 58 22 94 93 D2 25 30 F4 DB FA 33 6F 6E 0A C9 25 13 95 43 AE D4 4C CE 7C 37 20 FD 51 F6 94 58 70 5A C6 8C D4 FE 6B 6B 13 AB DC 97 46 51 29 69 32 84 54 F1 8F AF 8C 59 5F 64 24 77 FE 96 BB 2A 94 1D 5B CD 1D 4A C8 CC 49 88 07 08 FA 9B 37 8E 3C 4F 3A 90 60 BE E6 7C F9 A4 A4 A6 95 81 10 51 90 7E 16 27 53 B5 6B 0F 6B 41 0D BA 74 D8 A8 4B 2A 14 B3 14 4E 0E F1 28 47 54 FD 17 ED 95 0D 59 65 B4 B9 DD 46 58 2D B1 17 8D 16 9C 6B C4 65 B0 D6 FF 9C A3 92 8F EF 5B 9A E4 E4 18 FC 15 E8 3E BE A0 F8 7F A9 FF 5E ED 70 05 0D ED 28 49 F4 7B F9 59 D9 56 85 0C E9 29 85 1F 0D 81 15 F6 35 B1 05 EE 2E 4E 15 D0 4B 24 54 BF 6F 4F AD F0 34 B1 04 03 11 9C D8 E3 B9 2F CC 5B'
|
'C7 1C AE B9 C6 B1 C9 04 8E 6C 52 2F 70 F1 3F 73 98 0D 40 23 8E 3E 21 C1 49 34 D0 37 56 3D 93 0F 48 19 8A 0A A7 C1 40 58 22 94 93 D2 25 30 F4 DB FA 33 6F 6E 0A C9 25 13 95 43 AE D4 4C CE 7C 37 20 FD 51 F6 94 58 70 5A C6 8C D4 FE 6B 6B 13 AB DC 97 46 51 29 69 32 84 54 F1 8F AF 8C 59 5F 64 24 77 FE 96 BB 2A 94 1D 5B CD 1D 4A C8 CC 49 88 07 08 FA 9B 37 8E 3C 4F 3A 90 60 BE E6 7C F9 A4 A4 A6 95 81 10 51 90 7E 16 27 53 B5 6B 0F 6B 41 0D BA 74 D8 A8 4B 2A 14 B3 14 4E 0E F1 28 47 54 FD 17 ED 95 0D 59 65 B4 B9 DD 46 58 2D B1 17 8D 16 9C 6B C4 65 B0 D6 FF 9C A3 92 8F EF 5B 9A E4 E4 18 FC 15 E8 3E BE A0 F8 7F A9 FF 5E ED 70 05 0D ED 28 49 F4 7B F9 59 D9 56 85 0C E9 29 85 1F 0D 81 15 F6 35 B1 05 EE 2E 4E 15 D0 4B 24 54 BF 6F 4F AD F0 34 B1 04 03 11 9C D8 E3 B9 2F CC 5B'
|
||||||
testMillerRabin(bigInt(telegramDhPrime.replace(/ /g, ''), 16), true)
|
testMillerRabin(BigInt('0x' + telegramDhPrime.replace(/ /g, '')), true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,50 +1,54 @@
|
||||||
/* eslint-disable no-restricted-globals */
|
/* eslint-disable no-restricted-globals */
|
||||||
// todo fixme
|
|
||||||
import bigInt, { BigInteger } from 'big-integer'
|
|
||||||
|
|
||||||
import { IPacketCodec, WrappedCodec } from '@mtcute/core'
|
import { IPacketCodec, WrappedCodec } from '@mtcute/core'
|
||||||
import { bigIntToBuffer, bufferToBigInt, ICryptoProvider, randomBytes } from '@mtcute/core/utils.js'
|
import {
|
||||||
|
bigIntModInv,
|
||||||
|
bigIntModPow,
|
||||||
|
bigIntToBuffer,
|
||||||
|
bufferToBigInt,
|
||||||
|
ICryptoProvider,
|
||||||
|
randomBytes,
|
||||||
|
} from '@mtcute/core/utils.js'
|
||||||
|
|
||||||
const MAX_TLS_PACKET_LENGTH = 2878
|
const MAX_TLS_PACKET_LENGTH = 2878
|
||||||
const TLS_FIRST_PREFIX = Buffer.from('140303000101', 'hex')
|
const TLS_FIRST_PREFIX = Buffer.from('140303000101', 'hex')
|
||||||
|
|
||||||
// ref: https://github.com/tdlib/td/blob/master/td/mtproto/TlsInit.cpp
|
// ref: https://github.com/tdlib/td/blob/master/td/mtproto/TlsInit.cpp
|
||||||
const KEY_MOD = bigInt('7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed', 16)
|
const KEY_MOD = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn
|
||||||
// 2^255 - 19
|
// 2^255 - 19
|
||||||
const QUAD_RES_MOD = bigInt('7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed', 16)
|
const QUAD_RES_MOD = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn
|
||||||
// (mod - 1) / 2 = 2^254 - 10
|
// (mod - 1) / 2 = 2^254 - 10
|
||||||
const QUAD_RES_POW = bigInt('3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6', 16)
|
const QUAD_RES_POW = 0x3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6n
|
||||||
|
|
||||||
function _getY2(x: BigInteger, mod: BigInteger): BigInteger {
|
function _getY2(x: bigint, mod: bigint): bigint {
|
||||||
// returns y = x^3 + x^2 * 486662 + x
|
// returns y = x^3 + x^2 * 486662 + x
|
||||||
let y = x
|
let y = x
|
||||||
y = y.add(486662).mod(mod)
|
y = (y + 486662n) % mod
|
||||||
y = y.multiply(x).mod(mod)
|
y = (y * x) % mod
|
||||||
y = y.plus(1).mod(mod)
|
y = (y + 1n) % mod
|
||||||
y = y.multiply(x).mod(mod)
|
y = (y * x) % mod
|
||||||
|
|
||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getDoubleX(x: BigInteger, mod: BigInteger): BigInteger {
|
function _getDoubleX(x: bigint, mod: bigint): bigint {
|
||||||
// returns x_2 = (x^2 - 1)^2/(4*y^2)
|
// returns x_2 = (x^2 - 1)^2/(4*y^2)
|
||||||
let denominator = _getY2(x, mod)
|
let denominator = _getY2(x, mod)
|
||||||
denominator = denominator.multiply(4).mod(mod)
|
denominator = (denominator * 4n) % mod
|
||||||
|
|
||||||
let numerator = x.multiply(x).mod(mod)
|
let numerator = (x * x) % mod
|
||||||
numerator = numerator.minus(1).mod(mod)
|
numerator = (numerator - 1n) % mod
|
||||||
numerator = numerator.multiply(numerator).mod(mod)
|
numerator = (numerator * numerator) % mod
|
||||||
|
|
||||||
denominator = denominator.modInv(mod)
|
denominator = bigIntModInv(denominator, mod)
|
||||||
numerator = numerator.multiply(denominator).mod(mod)
|
numerator = (numerator * denominator) % mod
|
||||||
|
|
||||||
return numerator
|
return numerator
|
||||||
}
|
}
|
||||||
|
|
||||||
function _isQuadraticResidue(a: BigInteger): boolean {
|
function _isQuadraticResidue(a: bigint): boolean {
|
||||||
const r = a.modPow(QUAD_RES_POW, QUAD_RES_MOD)
|
const r = bigIntModPow(a, QUAD_RES_POW, QUAD_RES_MOD)
|
||||||
|
|
||||||
return r.eq(1)
|
return r === 1n
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TlsOperationHandler {
|
interface TlsOperationHandler {
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mtcute/core": "workspace:^",
|
"@mtcute/core": "workspace:^"
|
||||||
"big-integer": "1.6.51"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,9 +132,6 @@ importers:
|
||||||
'@types/events':
|
'@types/events':
|
||||||
specifier: 3.0.0
|
specifier: 3.0.0
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
big-integer:
|
|
||||||
specifier: 1.6.51
|
|
||||||
version: 1.6.51
|
|
||||||
events:
|
events:
|
||||||
specifier: 3.2.0
|
specifier: 3.2.0
|
||||||
version: 3.2.0
|
version: 3.2.0
|
||||||
|
@ -238,9 +235,6 @@ importers:
|
||||||
'@mtcute/core':
|
'@mtcute/core':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../core
|
version: link:../core
|
||||||
big-integer:
|
|
||||||
specifier: 1.6.51
|
|
||||||
version: 1.6.51
|
|
||||||
|
|
||||||
packages/node:
|
packages/node:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in a new issue