chore!: moved to @fuman/utils for common stuff
breaking: some utils were removed from @mtcute/core/utils.js, use them from @fuman/utils instead
This commit is contained in:
parent
66786064e3
commit
eebf95c6ec
63 changed files with 241 additions and 1028 deletions
|
@ -1,7 +1,7 @@
|
||||||
import { expect } from 'vitest'
|
import { expect } from 'vitest'
|
||||||
|
import { typed } from '@fuman/utils'
|
||||||
|
|
||||||
import { setPlatform } from '../../packages/core/src/platform.js'
|
import { setPlatform } from '../../packages/core/src/platform.js'
|
||||||
import { buffersEqual } from '../../packages/core/src/utils/buffer-utils.js'
|
|
||||||
|
|
||||||
// @ts-expect-error no .env here
|
// @ts-expect-error no .env here
|
||||||
if (import.meta.env.TEST_ENV === 'browser' || import.meta.env.TEST_ENV === 'deno') {
|
if (import.meta.env.TEST_ENV === 'browser' || import.meta.env.TEST_ENV === 'deno') {
|
||||||
|
@ -14,7 +14,7 @@ if (import.meta.env.TEST_ENV === 'browser' || import.meta.env.TEST_ENV === 'deno
|
||||||
expect.addEqualityTesters([
|
expect.addEqualityTesters([
|
||||||
function (a, b) {
|
function (a, b) {
|
||||||
if (a instanceof Uint8Array && b instanceof Uint8Array) {
|
if (a instanceof Uint8Array && b instanceof Uint8Array) {
|
||||||
return buffersEqual(a, b)
|
return typed.equal(a, b)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "2.26.0",
|
"@antfu/eslint-config": "2.26.0",
|
||||||
|
"@fuman/utils": "workspace:^",
|
||||||
"@teidesu/slow-types-compiler": "1.1.0",
|
"@teidesu/slow-types-compiler": "1.1.0",
|
||||||
"@types/deno": "npm:@teidesu/deno-types@1.46.3",
|
"@types/deno": "npm:@teidesu/deno-types@1.46.3",
|
||||||
"@types/node": "20.10.0",
|
"@types/node": "20.10.0",
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { MtArgumentError } from '@mtcute/core'
|
import { MtArgumentError } from '@mtcute/core'
|
||||||
import { dataViewFromBuffer } from '@mtcute/core/utils.js'
|
import { base64, typed, utf8 } from '@fuman/utils'
|
||||||
import { base64, utf8 } from '@fuman/utils'
|
|
||||||
|
|
||||||
import type { TelethonSession } from '../telethon/types.js'
|
import type { TelethonSession } from '../telethon/types.js'
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ export function parseGramjsSession(session: string): TelethonSession {
|
||||||
session = session.slice(1)
|
session = session.slice(1)
|
||||||
|
|
||||||
const data = base64.decode(session)
|
const data = base64.decode(session)
|
||||||
const dv = dataViewFromBuffer(data)
|
const dv = typed.toDataView(data)
|
||||||
|
|
||||||
const dcId = dv.getUint8(0)
|
const dcId = dv.getUint8(0)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { MtArgumentError } from '@mtcute/core'
|
import { MtArgumentError } from '@mtcute/core'
|
||||||
import { dataViewFromBuffer } from '@mtcute/core/utils.js'
|
import { base64, typed, utf8 } from '@fuman/utils'
|
||||||
import { base64, utf8 } from '@fuman/utils'
|
|
||||||
|
|
||||||
import type { TelethonSession } from '../telethon/types.js'
|
import type { TelethonSession } from '../telethon/types.js'
|
||||||
|
|
||||||
|
@ -12,7 +11,7 @@ export function serializeGramjsSession(session: TelethonSession): string {
|
||||||
const ipEncoded = utf8.encoder.encode(session.ipAddress)
|
const ipEncoded = utf8.encoder.encode(session.ipAddress)
|
||||||
|
|
||||||
const u8 = new Uint8Array(261 + ipEncoded.length)
|
const u8 = new Uint8Array(261 + ipEncoded.length)
|
||||||
const dv = dataViewFromBuffer(u8)
|
const dv = typed.toDataView(u8)
|
||||||
|
|
||||||
dv.setUint8(0, session.dcId)
|
dv.setUint8(0, session.dcId)
|
||||||
dv.setUint16(1, ipEncoded.length)
|
dv.setUint16(1, ipEncoded.length)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// source: https://github.com/pyrogram/pyrogram/blob/master/pyrogram/storage/storage.py
|
// source: https://github.com/pyrogram/pyrogram/blob/master/pyrogram/storage/storage.py
|
||||||
|
|
||||||
import { Long } from '@mtcute/core'
|
import { Long } from '@mtcute/core'
|
||||||
import { dataViewFromBuffer, longFromBuffer } from '@mtcute/core/utils.js'
|
import { longFromBuffer } from '@mtcute/core/utils.js'
|
||||||
import { base64 } from '@fuman/utils'
|
import { base64, typed } from '@fuman/utils'
|
||||||
|
|
||||||
import type { PyrogramSession } from './types.js'
|
import type { PyrogramSession } from './types.js'
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ const SESSION_STRING_SIZE_64 = 356
|
||||||
|
|
||||||
export function parsePyrogramSession(session: string): PyrogramSession {
|
export function parsePyrogramSession(session: string): PyrogramSession {
|
||||||
const data = base64.decode(session, true)
|
const data = base64.decode(session, true)
|
||||||
const dv = dataViewFromBuffer(data)
|
const dv = typed.toDataView(data)
|
||||||
|
|
||||||
if (session.length === SESSION_STRING_SIZE || session.length === SESSION_STRING_SIZE_64) {
|
if (session.length === SESSION_STRING_SIZE || session.length === SESSION_STRING_SIZE_64) {
|
||||||
// old format
|
// old format
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { Long, MtArgumentError } from '@mtcute/core'
|
import { Long, MtArgumentError } from '@mtcute/core'
|
||||||
import { dataViewFromBuffer } from '@mtcute/core/utils.js'
|
import { base64, typed } from '@fuman/utils'
|
||||||
import { base64 } from '@fuman/utils'
|
|
||||||
|
|
||||||
import type { PyrogramSession } from './types.js'
|
import type { PyrogramSession } from './types.js'
|
||||||
|
|
||||||
|
@ -19,7 +18,7 @@ export function serializePyrogramSession(session: PyrogramSession): string {
|
||||||
if (session.apiId === undefined) {
|
if (session.apiId === undefined) {
|
||||||
// old format
|
// old format
|
||||||
u8 = new Uint8Array(SESSION_STRING_SIZE_OLD)
|
u8 = new Uint8Array(SESSION_STRING_SIZE_OLD)
|
||||||
const dv = dataViewFromBuffer(u8)
|
const dv = typed.toDataView(u8)
|
||||||
|
|
||||||
dv.setUint8(0, session.dcId)
|
dv.setUint8(0, session.dcId)
|
||||||
dv.setUint8(1, session.isTest ? 1 : 0)
|
dv.setUint8(1, session.isTest ? 1 : 0)
|
||||||
|
@ -29,7 +28,7 @@ export function serializePyrogramSession(session: PyrogramSession): string {
|
||||||
dv.setUint8(266, session.isBot ? 1 : 0)
|
dv.setUint8(266, session.isBot ? 1 : 0)
|
||||||
} else {
|
} else {
|
||||||
u8 = new Uint8Array(SESSION_STRING_SIZE)
|
u8 = new Uint8Array(SESSION_STRING_SIZE)
|
||||||
const dv = dataViewFromBuffer(u8)
|
const dv = typed.toDataView(u8)
|
||||||
|
|
||||||
dv.setUint8(0, session.dcId)
|
dv.setUint8(0, session.dcId)
|
||||||
dv.setUint32(1, session.apiId)
|
dv.setUint32(1, session.apiId)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { MtArgumentError } from '@mtcute/core'
|
import { MtArgumentError } from '@mtcute/core'
|
||||||
import { dataViewFromBuffer } from '@mtcute/core/utils.js'
|
import { base64, typed } from '@fuman/utils'
|
||||||
import { base64 } from '@fuman/utils'
|
|
||||||
|
|
||||||
import { parseIpFromBytes } from '../utils/ip.js'
|
import { parseIpFromBytes } from '../utils/ip.js'
|
||||||
|
|
||||||
|
@ -15,7 +14,7 @@ export function parseTelethonSession(session: string): TelethonSession {
|
||||||
session = session.slice(1)
|
session = session.slice(1)
|
||||||
|
|
||||||
const data = base64.decode(session, true)
|
const data = base64.decode(session, true)
|
||||||
const dv = dataViewFromBuffer(data)
|
const dv = typed.toDataView(data)
|
||||||
|
|
||||||
const dcId = dv.getUint8(0)
|
const dcId = dv.getUint8(0)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { MtArgumentError } from '@mtcute/core'
|
import { MtArgumentError } from '@mtcute/core'
|
||||||
import { dataViewFromBuffer } from '@mtcute/core/utils.js'
|
import { base64, typed } from '@fuman/utils'
|
||||||
import { base64 } from '@fuman/utils'
|
|
||||||
|
|
||||||
import { serializeIpv4ToBytes, serializeIpv6ToBytes } from '../utils/ip.js'
|
import { serializeIpv4ToBytes, serializeIpv6ToBytes } from '../utils/ip.js'
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ export function serializeTelethonSession(session: TelethonSession): string {
|
||||||
|
|
||||||
const ipSize = session.ipv6 ? 16 : 4
|
const ipSize = session.ipv6 ? 16 : 4
|
||||||
const u8 = new Uint8Array(259 + ipSize)
|
const u8 = new Uint8Array(259 + ipSize)
|
||||||
const dv = dataViewFromBuffer(u8)
|
const dv = typed.toDataView(u8)
|
||||||
|
|
||||||
dv.setUint8(0, session.dcId)
|
dv.setUint8(0, session.dcId)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
import { Deferred, base64 } from '@fuman/utils'
|
||||||
|
|
||||||
import { getPlatform } from '../../../platform.js'
|
|
||||||
import type { MaybePromise } from '../../../types/utils.js'
|
import type { MaybePromise } from '../../../types/utils.js'
|
||||||
import type { ControllablePromise } from '../../../utils/controllable-promise.js'
|
|
||||||
import { createControllablePromise } from '../../../utils/controllable-promise.js'
|
|
||||||
import { sleepWithAbort } from '../../../utils/misc-utils.js'
|
import { sleepWithAbort } from '../../../utils/misc-utils.js'
|
||||||
import { assertTypeIs } from '../../../utils/type-assertions.js'
|
import { assertTypeIs } from '../../../utils/type-assertions.js'
|
||||||
import type { ITelegramClient, ServerUpdateHandler } from '../../client.types.js'
|
import type { ITelegramClient, ServerUpdateHandler } from '../../client.types.js'
|
||||||
|
@ -53,7 +51,7 @@ export async function signInQr(
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
const { onUrlUpdated, abortSignal, onQrScanned } = params
|
const { onUrlUpdated, abortSignal, onQrScanned } = params
|
||||||
|
|
||||||
let waiter: ControllablePromise<void> | undefined
|
let waiter: Deferred<void> | undefined
|
||||||
|
|
||||||
// crutch – we need to wait for the updateLoginToken update.
|
// crutch – we need to wait for the updateLoginToken update.
|
||||||
// we replace the server update handler temporarily because:
|
// we replace the server update handler temporarily because:
|
||||||
|
@ -108,7 +106,6 @@ export async function signInQr(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { id, hash } = await client.getApiCrenetials()
|
const { id, hash } = await client.getApiCrenetials()
|
||||||
const platform = getPlatform()
|
|
||||||
|
|
||||||
loop: while (true) {
|
loop: while (true) {
|
||||||
let res: tl.auth.TypeLoginToken
|
let res: tl.auth.TypeLoginToken
|
||||||
|
@ -134,11 +131,11 @@ export async function signInQr(
|
||||||
switch (res._) {
|
switch (res._) {
|
||||||
case 'auth.loginToken':
|
case 'auth.loginToken':
|
||||||
onUrlUpdated(
|
onUrlUpdated(
|
||||||
`tg://login?token=${platform.base64Encode(res.token, true)}`,
|
`tg://login?token=${base64.encode(res.token, true)}`,
|
||||||
new Date(res.expires * 1000),
|
new Date(res.expires * 1000),
|
||||||
)
|
)
|
||||||
|
|
||||||
waiter = createControllablePromise()
|
waiter = new Deferred()
|
||||||
await Promise.race([waiter, sleepWithAbort(res.expires * 1000 - Date.now(), client.stopSignal)])
|
await Promise.race([waiter, sleepWithAbort(res.expires * 1000 - Date.now(), client.stopSignal)])
|
||||||
break
|
break
|
||||||
case 'auth.loginTokenMigrateTo': {
|
case 'auth.loginTokenMigrateTo': {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { concatBuffers } from '../../../utils/buffer-utils.js'
|
import { u8 } from '@fuman/utils'
|
||||||
|
|
||||||
import type { ITelegramClient } from '../../client.types.js'
|
import type { ITelegramClient } from '../../client.types.js'
|
||||||
import type { FileDownloadLocation, FileDownloadParameters } from '../../types/index.js'
|
import type { FileDownloadLocation, FileDownloadParameters } from '../../types/index.js'
|
||||||
import { FileLocation } from '../../types/index.js'
|
import { FileLocation } from '../../types/index.js'
|
||||||
|
@ -28,5 +29,5 @@ export async function downloadAsBuffer(
|
||||||
chunks.push(chunk)
|
chunks.push(chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
return concatBuffers(chunks)
|
return u8.concat(chunks)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { parseFileId } from '@mtcute/file-id'
|
import { parseFileId } from '@mtcute/file-id'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
import { ConditionVariable } from '@fuman/utils'
|
||||||
|
|
||||||
import type { ConnectionKind } from '../../../network/network-manager.js'
|
import type { ConnectionKind } from '../../../network/network-manager.js'
|
||||||
import { getPlatform } from '../../../platform.js'
|
|
||||||
import { MtArgumentError, MtUnsupportedError } from '../../../types/errors.js'
|
import { MtArgumentError, MtUnsupportedError } from '../../../types/errors.js'
|
||||||
import { ConditionVariable } from '../../../utils/condition-variable.js'
|
|
||||||
import type { ITelegramClient } from '../../client.types.js'
|
import type { ITelegramClient } from '../../client.types.js'
|
||||||
import type { FileDownloadLocation, FileDownloadParameters } from '../../types/index.js'
|
import type { FileDownloadLocation, FileDownloadParameters } from '../../types/index.js'
|
||||||
import { FileLocation } from '../../types/index.js'
|
import { FileLocation } from '../../types/index.js'
|
||||||
|
@ -58,7 +57,7 @@ export async function* downloadAsIterable(
|
||||||
if (!fileSize) fileSize = input.fileSize
|
if (!fileSize) fileSize = input.fileSize
|
||||||
location = locationInner
|
location = locationInner
|
||||||
} else if (typeof input === 'string') {
|
} else if (typeof input === 'string') {
|
||||||
const parsed = parseFileId(getPlatform(), input)
|
const parsed = parseFileId(input)
|
||||||
|
|
||||||
if (parsed.location._ === 'web') {
|
if (parsed.location._ === 'web') {
|
||||||
location = fileIdToInputWebFileLocation(parsed)
|
location = fileIdToInputWebFileLocation(parsed)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { tl } from '@mtcute/tl'
|
import type { tl } from '@mtcute/tl'
|
||||||
|
import { ConditionVariable } from '@fuman/utils'
|
||||||
|
|
||||||
import { ConditionVariable } from '../../../utils/condition-variable.js'
|
|
||||||
import type { ITelegramClient } from '../../client.types.js'
|
import type { ITelegramClient } from '../../client.types.js'
|
||||||
import { MtPeerNotFoundError } from '../../types/errors.js'
|
import { MtPeerNotFoundError } from '../../types/errors.js'
|
||||||
import type { InputPeerLike } from '../../types/peers/index.js'
|
import type { InputPeerLike } from '../../types/peers/index.js'
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import { typed } from '@fuman/utils'
|
||||||
|
|
||||||
import type { IKeyValueRepository } from '../../../storage/repository/key-value.js'
|
import type { IKeyValueRepository } from '../../../storage/repository/key-value.js'
|
||||||
import type { ServiceOptions } from '../../../storage/service/base.js'
|
import type { ServiceOptions } from '../../../storage/service/base.js'
|
||||||
import { BaseService } from '../../../storage/service/base.js'
|
import { BaseService } from '../../../storage/service/base.js'
|
||||||
import { dataViewFromBuffer } from '../../../utils/buffer-utils.js'
|
|
||||||
|
|
||||||
const KV_PTS = 'updates_pts'
|
const KV_PTS = 'updates_pts'
|
||||||
const KV_QTS = 'updates_qts'
|
const KV_QTS = 'updates_qts'
|
||||||
|
@ -23,12 +24,12 @@ export class UpdatesStateService extends BaseService {
|
||||||
const val = await this._kv.get(key)
|
const val = await this._kv.get(key)
|
||||||
if (!val) return null
|
if (!val) return null
|
||||||
|
|
||||||
return dataViewFromBuffer(val).getInt32(0, true)
|
return typed.toDataView(val).getInt32(0, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _setInt(key: string, val: number): Promise<void> {
|
private async _setInt(key: string, val: number): Promise<void> {
|
||||||
const buf = new Uint8Array(4)
|
const buf = new Uint8Array(4)
|
||||||
dataViewFromBuffer(buf).setInt32(0, val, true)
|
typed.toDataView(buf).setInt32(0, val, true)
|
||||||
|
|
||||||
await this._kv.set(key, buf)
|
await this._kv.set(key, buf)
|
||||||
}
|
}
|
||||||
|
@ -68,12 +69,12 @@ export class UpdatesStateService extends BaseService {
|
||||||
const val = await this._kv.get(KV_CHANNEL_PREFIX + channelId)
|
const val = await this._kv.get(KV_CHANNEL_PREFIX + channelId)
|
||||||
if (!val) return null
|
if (!val) return null
|
||||||
|
|
||||||
return dataViewFromBuffer(val).getUint32(0, true)
|
return typed.toDataView(val).getUint32(0, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async setChannelPts(channelId: number, pts: number): Promise<void> {
|
async setChannelPts(channelId: number, pts: number): Promise<void> {
|
||||||
const buf = new Uint8Array(4)
|
const buf = new Uint8Array(4)
|
||||||
dataViewFromBuffer(buf).setUint32(0, pts, true)
|
typed.toDataView(buf).setUint32(0, pts, true)
|
||||||
|
|
||||||
await this._kv.set(KV_CHANNEL_PREFIX + channelId, buf)
|
await this._kv.set(KV_CHANNEL_PREFIX + channelId, buf)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import type { tl } from '@mtcute/tl'
|
import type { tl } from '@mtcute/tl'
|
||||||
|
import { AsyncLock, Deferred, timers } from '@fuman/utils'
|
||||||
|
|
||||||
import { MtArgumentError, MtTimeoutError } from '../../types/errors.js'
|
import { MtArgumentError, MtTimeoutError } from '../../types/errors.js'
|
||||||
import type { MaybePromise } from '../../types/utils.js'
|
import type { MaybePromise } from '../../types/utils.js'
|
||||||
import { AsyncLock } from '../../utils/async-lock.js'
|
|
||||||
import type { ControllablePromise } from '../../utils/controllable-promise.js'
|
|
||||||
import { createControllablePromise } from '../../utils/controllable-promise.js'
|
|
||||||
import { Deque } from '../../utils/deque.js'
|
import { Deque } from '../../utils/deque.js'
|
||||||
import { getMarkedPeerId } from '../../utils/peer-utils.js'
|
import { getMarkedPeerId } from '../../utils/peer-utils.js'
|
||||||
import type { ITelegramClient } from '../client.types.js'
|
import type { ITelegramClient } from '../client.types.js'
|
||||||
|
@ -14,7 +12,6 @@ import { sendMedia } from '../methods/messages/send-media.js'
|
||||||
import { sendMediaGroup } from '../methods/messages/send-media-group.js'
|
import { sendMediaGroup } from '../methods/messages/send-media-group.js'
|
||||||
import { sendText } from '../methods/messages/send-text.js'
|
import { sendText } from '../methods/messages/send-text.js'
|
||||||
import { resolvePeer } from '../methods/users/resolve-peer.js'
|
import { resolvePeer } from '../methods/users/resolve-peer.js'
|
||||||
import { timers } from '../../utils/index.js'
|
|
||||||
|
|
||||||
import type { Message } from './messages/message.js'
|
import type { Message } from './messages/message.js'
|
||||||
import type { InputPeerLike } from './peers/index.js'
|
import type { InputPeerLike } from './peers/index.js'
|
||||||
|
@ -22,7 +19,7 @@ import type { HistoryReadUpdate, ParsedUpdate } from './updates/index.js'
|
||||||
import type { ParametersSkip2 } from './utils.js'
|
import type { ParametersSkip2 } from './utils.js'
|
||||||
|
|
||||||
interface QueuedHandler<T> {
|
interface QueuedHandler<T> {
|
||||||
promise: ControllablePromise<T>
|
promise: Deferred<T>
|
||||||
check?: (update: T) => MaybePromise<boolean>
|
check?: (update: T) => MaybePromise<boolean>
|
||||||
timeout?: timers.Timer
|
timeout?: timers.Timer
|
||||||
}
|
}
|
||||||
|
@ -340,7 +337,7 @@ export class Conversation {
|
||||||
throw new MtArgumentError("Conversation hasn't started yet")
|
throw new MtArgumentError("Conversation hasn't started yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = createControllablePromise<Message>()
|
const promise = new Deferred<Message>()
|
||||||
|
|
||||||
let timer: timers.Timer | undefined
|
let timer: timers.Timer | undefined
|
||||||
|
|
||||||
|
@ -359,7 +356,7 @@ export class Conversation {
|
||||||
|
|
||||||
this._processPendingNewMessages()
|
this._processPendingNewMessages()
|
||||||
|
|
||||||
return promise
|
return promise.promise
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -481,7 +478,7 @@ export class Conversation {
|
||||||
throw new MtArgumentError('Provide message for which to wait for edit for')
|
throw new MtArgumentError('Provide message for which to wait for edit for')
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = createControllablePromise<Message>()
|
const promise = new Deferred<Message>()
|
||||||
|
|
||||||
let timer: timers.Timer | undefined
|
let timer: timers.Timer | undefined
|
||||||
const timeout = params?.timeout
|
const timeout = params?.timeout
|
||||||
|
@ -501,7 +498,7 @@ export class Conversation {
|
||||||
|
|
||||||
this._processRecentEdits()
|
this._processRecentEdits()
|
||||||
|
|
||||||
return promise
|
return promise.promise
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -529,7 +526,7 @@ export class Conversation {
|
||||||
const [dialog] = await getPeerDialogs(this.client, this._inputPeer)
|
const [dialog] = await getPeerDialogs(this.client, this._inputPeer)
|
||||||
if (dialog.lastRead >= msgId) return
|
if (dialog.lastRead >= msgId) return
|
||||||
|
|
||||||
const promise = createControllablePromise<void>()
|
const promise = new Deferred<void>()
|
||||||
|
|
||||||
let timer: timers.Timer | undefined
|
let timer: timers.Timer | undefined
|
||||||
|
|
||||||
|
@ -545,7 +542,7 @@ export class Conversation {
|
||||||
timeout: timer,
|
timeout: timer,
|
||||||
})
|
})
|
||||||
|
|
||||||
return promise
|
return promise.promise
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onNewMessage(msg: Message) {
|
private _onNewMessage(msg: Message) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
|
import { AsyncLock, ConditionVariable, timers } from '@fuman/utils'
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
|
|
||||||
import { MtArgumentError } from '../../types/errors.js'
|
import { MtArgumentError } from '../../types/errors.js'
|
||||||
|
@ -8,8 +9,6 @@ import type {
|
||||||
Logger,
|
Logger,
|
||||||
} from '../../utils/index.js'
|
} from '../../utils/index.js'
|
||||||
import {
|
import {
|
||||||
AsyncLock,
|
|
||||||
ConditionVariable,
|
|
||||||
Deque,
|
Deque,
|
||||||
EarlyTimer,
|
EarlyTimer,
|
||||||
SortedLinkedList,
|
SortedLinkedList,
|
||||||
|
@ -22,7 +21,6 @@ import {
|
||||||
import type { BaseTelegramClient } from '../base.js'
|
import type { BaseTelegramClient } from '../base.js'
|
||||||
import type { CurrentUserInfo } from '../storage/service/current-user.js'
|
import type { CurrentUserInfo } from '../storage/service/current-user.js'
|
||||||
import { PeersIndex } from '../types/peers/peers-index.js'
|
import { PeersIndex } from '../types/peers/peers-index.js'
|
||||||
import * as timers from '../../utils/timers.js'
|
|
||||||
import { _getChannelsBatched } from '../methods/chats/batched-queries.js'
|
import { _getChannelsBatched } from '../methods/chats/batched-queries.js'
|
||||||
|
|
||||||
import type { PendingUpdate, PendingUpdateContainer, RawUpdateHandler, UpdatesManagerParams } from './types.js'
|
import type { PendingUpdate, PendingUpdateContainer, RawUpdateHandler, UpdatesManagerParams } from './types.js'
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { timers } from '../../utils/index.js'
|
import { timers } from '@fuman/utils'
|
||||||
|
|
||||||
import type { Message } from '../types/messages/index.js'
|
import type { Message } from '../types/messages/index.js'
|
||||||
import type { BusinessMessage, ParsedUpdate } from '../types/updates/index.js'
|
import type { BusinessMessage, ParsedUpdate } from '../types/updates/index.js'
|
||||||
import { _parseUpdate } from '../types/updates/parse-update.js'
|
import { _parseUpdate } from '../types/updates/parse-update.js'
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import type { tl } from '@mtcute/tl'
|
import type { tl } from '@mtcute/tl'
|
||||||
|
import type { AsyncLock, ConditionVariable, timers } from '@fuman/utils'
|
||||||
|
|
||||||
import type { AsyncLock, ConditionVariable, Deque, EarlyTimer, Logger, SortedLinkedList, timers } from '../../utils/index.js'
|
import type { Deque, EarlyTimer, Logger, SortedLinkedList } from '../../utils/index.js'
|
||||||
import type { CurrentUserInfo } from '../storage/service/current-user.js'
|
import type { CurrentUserInfo } from '../storage/service/current-user.js'
|
||||||
import type { PeersIndex } from '../types/peers/peers-index.js'
|
import type { PeersIndex } from '../types/peers/peers-index.js'
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import type { tl } from '@mtcute/tl'
|
import type { tl } from '@mtcute/tl'
|
||||||
import { hex, utf8 } from '@fuman/utils'
|
import { hex, u8, utf8 } from '@fuman/utils'
|
||||||
|
|
||||||
import { MtArgumentError } from '../../types/errors.js'
|
import { MtArgumentError } from '../../types/errors.js'
|
||||||
import { concatBuffers } from '../../utils/buffer-utils.js'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given file size, determine the appropriate chunk size (in KB)
|
* Given file size, determine the appropriate chunk size (in KB)
|
||||||
|
@ -72,7 +71,7 @@ export function strippedPhotoToJpg(stripped: Uint8Array): Uint8Array {
|
||||||
JPEG_HEADER_BYTES = JPEG_HEADER()
|
JPEG_HEADER_BYTES = JPEG_HEADER()
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = concatBuffers([JPEG_HEADER_BYTES, stripped.slice(3), JPEG_FOOTER])
|
const result = u8.concat3(JPEG_HEADER_BYTES, stripped.slice(3), JPEG_FOOTER)
|
||||||
result[164] = stripped[1]
|
result[164] = stripped[1]
|
||||||
result[166] = stripped[2]
|
result[166] = stripped[2]
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { AsyncLock } from '../../utils/async-lock.js'
|
import { AsyncLock, u8 } from '@fuman/utils'
|
||||||
import { concatBuffers } from '../../utils/buffer-utils.js'
|
|
||||||
|
|
||||||
export function bufferToStream(buf: Uint8Array): ReadableStream<Uint8Array> {
|
export function bufferToStream(buf: Uint8Array): ReadableStream<Uint8Array> {
|
||||||
return new ReadableStream({
|
return new ReadableStream({
|
||||||
|
@ -21,7 +20,7 @@ export async function streamToBuffer(stream: ReadableStream<Uint8Array>): Promis
|
||||||
chunks.push(value)
|
chunks.push(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return concatBuffers(chunks)
|
return u8.concat(chunks)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createChunkedReader(stream: ReadableStream<Uint8Array>, chunkSize: number): {
|
export function createChunkedReader(stream: ReadableStream<Uint8Array>, chunkSize: number): {
|
||||||
|
@ -77,12 +76,12 @@ export function createChunkedReader(stream: ReadableStream<Uint8Array>, chunkSiz
|
||||||
if (length === chunkSize) {
|
if (length === chunkSize) {
|
||||||
bufferLength -= chunkSize
|
bufferLength -= chunkSize
|
||||||
|
|
||||||
return concatBuffers(chunks)
|
return u8.concat(chunks)
|
||||||
}
|
}
|
||||||
} else if (next === undefined && bufferLength > 0) {
|
} else if (next === undefined && bufferLength > 0) {
|
||||||
bufferLength = 0
|
bufferLength = 0
|
||||||
|
|
||||||
return concatBuffers(buffer)
|
return u8.concat(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = await readInner()
|
const value = await readInner()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { dataViewFromBuffer } from '../../utils/buffer-utils.js'
|
import { typed } from '@fuman/utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode 5-bit encoded voice message waveform into
|
* Decode 5-bit encoded voice message waveform into
|
||||||
|
@ -24,7 +24,7 @@ export function decodeWaveform(wf: Uint8Array): number[] {
|
||||||
// So we read in a general way all the entries except the last one.
|
// So we read in a general way all the entries except the last one.
|
||||||
|
|
||||||
const result: number[] = []
|
const result: number[] = []
|
||||||
const dv = dataViewFromBuffer(wf)
|
const dv = typed.toDataView(wf)
|
||||||
|
|
||||||
for (let i = 0, j = 0; i < lastIdx; i++, j += 5) {
|
for (let i = 0, j = 0; i < lastIdx; i++, j += 5) {
|
||||||
const byteIdx = ~~(j / 8)
|
const byteIdx = ~~(j / 8)
|
||||||
|
@ -50,7 +50,7 @@ export function encodeWaveform(wf: number[]): Uint8Array {
|
||||||
const bitsCount = wf.length * 5
|
const bitsCount = wf.length * 5
|
||||||
const bytesCount = ~~((bitsCount + 7) / 8)
|
const bytesCount = ~~((bitsCount + 7) / 8)
|
||||||
const result = new Uint8Array(bytesCount + 1)
|
const result = new Uint8Array(bytesCount + 1)
|
||||||
const dv = dataViewFromBuffer(result)
|
const dv = typed.toDataView(result)
|
||||||
|
|
||||||
// Write each 0-31 unsigned char as 5 bit to result.
|
// Write each 0-31 unsigned char as 5 bit to result.
|
||||||
// We reserve one extra byte to be able to dereference any of required bytes
|
// We reserve one extra byte to be able to dereference any of required bytes
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import type { ControllablePromise } from '../../utils/controllable-promise.js'
|
import { Deferred } from '@fuman/utils'
|
||||||
import { createControllablePromise } from '../../utils/controllable-promise.js'
|
|
||||||
|
|
||||||
import { deserializeError } from './errors.js'
|
import { deserializeError } from './errors.js'
|
||||||
import type { SendFn, WorkerInboundMessage, WorkerOutboundMessage } from './protocol.js'
|
import type { SendFn, WorkerInboundMessage, WorkerOutboundMessage } from './protocol.js'
|
||||||
|
@ -11,7 +10,7 @@ export class WorkerInvoker {
|
||||||
constructor(private send: SendFn) {}
|
constructor(private send: SendFn) {}
|
||||||
|
|
||||||
private _nextId = 0
|
private _nextId = 0
|
||||||
private _pending = new Map<number, ControllablePromise>()
|
private _pending = new Map<number, Deferred<unknown>>()
|
||||||
|
|
||||||
private _invoke(target: InvokeTarget, method: string, args: unknown[], isVoid: boolean, abortSignal?: AbortSignal) {
|
private _invoke(target: InvokeTarget, method: string, args: unknown[], isVoid: boolean, abortSignal?: AbortSignal) {
|
||||||
const id = this._nextId++
|
const id = this._nextId++
|
||||||
|
@ -34,11 +33,11 @@ export class WorkerInvoker {
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!isVoid) {
|
if (!isVoid) {
|
||||||
const promise = createControllablePromise()
|
const promise = new Deferred<unknown>()
|
||||||
|
|
||||||
this._pending.set(id, promise)
|
this._pending.set(id, promise)
|
||||||
|
|
||||||
return promise
|
return promise.promise
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,11 @@ import type Long from 'long'
|
||||||
import type { tl } from '@mtcute/tl'
|
import type { tl } from '@mtcute/tl'
|
||||||
import type { TlReaderMap } from '@mtcute/tl-runtime'
|
import type { TlReaderMap } from '@mtcute/tl-runtime'
|
||||||
import { TlBinaryReader } from '@mtcute/tl-runtime'
|
import { TlBinaryReader } from '@mtcute/tl-runtime'
|
||||||
|
import { typed, u8 } from '@fuman/utils'
|
||||||
|
|
||||||
import { MtcuteError } from '../types/errors.js'
|
import { MtcuteError } from '../types/errors.js'
|
||||||
import { createAesIgeForMessage } from '../utils/crypto/mtproto.js'
|
import { createAesIgeForMessage } from '../utils/crypto/mtproto.js'
|
||||||
import type { ICryptoProvider, Logger } from '../utils/index.js'
|
import type { ICryptoProvider, Logger } from '../utils/index.js'
|
||||||
import { buffersEqual, concatBuffers, dataViewFromBuffer } from '../utils/index.js'
|
|
||||||
|
|
||||||
export class AuthKey {
|
export class AuthKey {
|
||||||
ready = false
|
ready = false
|
||||||
|
@ -23,7 +23,7 @@ export class AuthKey {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
match(keyId: Uint8Array): boolean {
|
match(keyId: Uint8Array): boolean {
|
||||||
return this.ready && buffersEqual(keyId, this.id)
|
return this.ready && typed.equal(keyId, this.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
setup(authKey?: Uint8Array | null): void {
|
setup(authKey?: Uint8Array | null): void {
|
||||||
|
@ -45,7 +45,7 @@ export class AuthKey {
|
||||||
padding = 12 + (padding ? 16 - padding : 0)
|
padding = 12 + (padding ? 16 - padding : 0)
|
||||||
|
|
||||||
const buf = new Uint8Array(16 + message.length + padding)
|
const buf = new Uint8Array(16 + message.length + padding)
|
||||||
const dv = dataViewFromBuffer(buf)
|
const dv = typed.toDataView(buf)
|
||||||
|
|
||||||
dv.setInt32(0, serverSalt.low, true)
|
dv.setInt32(0, serverSalt.low, true)
|
||||||
dv.setInt32(4, serverSalt.high, true)
|
dv.setInt32(4, serverSalt.high, true)
|
||||||
|
@ -54,11 +54,11 @@ export class AuthKey {
|
||||||
buf.set(message, 16)
|
buf.set(message, 16)
|
||||||
this._crypto.randomFill(buf.subarray(16 + message.length, 16 + message.length + padding))
|
this._crypto.randomFill(buf.subarray(16 + message.length, 16 + message.length + padding))
|
||||||
|
|
||||||
const messageKey = this._crypto.sha256(concatBuffers([this.clientSalt, buf])).subarray(8, 24)
|
const messageKey = this._crypto.sha256(u8.concat2(this.clientSalt, buf)).subarray(8, 24)
|
||||||
const ige = createAesIgeForMessage(this._crypto, this.key, messageKey, true)
|
const ige = createAesIgeForMessage(this._crypto, this.key, messageKey, true)
|
||||||
const encryptedData = ige.encrypt(buf)
|
const encryptedData = ige.encrypt(buf)
|
||||||
|
|
||||||
return concatBuffers([this.id, messageKey, encryptedData])
|
return u8.concat3(this.id, messageKey, encryptedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptMessage(
|
decryptMessage(
|
||||||
|
@ -81,10 +81,10 @@ export class AuthKey {
|
||||||
const ige = createAesIgeForMessage(this._crypto, this.key, messageKey, false)
|
const ige = createAesIgeForMessage(this._crypto, this.key, messageKey, false)
|
||||||
const innerData = ige.decrypt(encryptedData)
|
const innerData = ige.decrypt(encryptedData)
|
||||||
|
|
||||||
const msgKeySource = this._crypto.sha256(concatBuffers([this.serverSalt, innerData]))
|
const msgKeySource = this._crypto.sha256(u8.concat2(this.serverSalt, innerData))
|
||||||
const expectedMessageKey = msgKeySource.subarray(8, 24)
|
const expectedMessageKey = msgKeySource.subarray(8, 24)
|
||||||
|
|
||||||
if (!buffersEqual(messageKey, expectedMessageKey)) {
|
if (!typed.equal(messageKey, expectedMessageKey)) {
|
||||||
this.log.warn('received message with invalid messageKey = %h (expected %h)', messageKey, expectedMessageKey)
|
this.log.warn('received message with invalid messageKey = %h (expected %h)', messageKey, expectedMessageKey)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -2,15 +2,13 @@ import Long from 'long'
|
||||||
import { mtp } from '@mtcute/tl'
|
import { mtp } from '@mtcute/tl'
|
||||||
import type { TlPublicKey } from '@mtcute/tl/binary/rsa-keys.js'
|
import type { TlPublicKey } from '@mtcute/tl/binary/rsa-keys.js'
|
||||||
import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime'
|
import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime'
|
||||||
|
import { bigint, typed, u8 } from '@fuman/utils'
|
||||||
|
|
||||||
import { MtArgumentError, MtSecurityError, MtTypeAssertionError } from '../types/index.js'
|
import { MtArgumentError, MtSecurityError, MtTypeAssertionError } from '../types/index.js'
|
||||||
import { buffersEqual, concatBuffers, dataViewFromBuffer } from '../utils/buffer-utils.js'
|
|
||||||
import { findKeyByFingerprints } from '../utils/crypto/keys.js'
|
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 type { ICryptoProvider, Logger } from '../utils/index.js'
|
import type { ICryptoProvider, Logger } from '../utils/index.js'
|
||||||
import { bigIntModPow, bigIntToBuffer, bufferToBigInt } from '../utils/index.js'
|
|
||||||
import { mtpAssertTypeIs } from '../utils/type-assertions.js'
|
import { mtpAssertTypeIs } from '../utils/type-assertions.js'
|
||||||
|
|
||||||
import type { SessionConnection } from './session-connection.js'
|
import type { SessionConnection } from './session-connection.js'
|
||||||
|
@ -140,7 +138,7 @@ function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Ui
|
||||||
|
|
||||||
const aesKey = crypto.randomBytes(32)
|
const aesKey = crypto.randomBytes(32)
|
||||||
|
|
||||||
const dataWithHash = concatBuffers([data, crypto.sha256(concatBuffers([aesKey, data]))])
|
const dataWithHash = u8.concat2(data, crypto.sha256(u8.concat2(aesKey, data)))
|
||||||
// we only need to reverse the data
|
// we only need to reverse the data
|
||||||
dataWithHash.subarray(0, 192).reverse()
|
dataWithHash.subarray(0, 192).reverse()
|
||||||
|
|
||||||
|
@ -148,36 +146,36 @@ function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Ui
|
||||||
const encrypted = aes.encrypt(dataWithHash)
|
const encrypted = aes.encrypt(dataWithHash)
|
||||||
const encryptedHash = crypto.sha256(encrypted)
|
const encryptedHash = crypto.sha256(encrypted)
|
||||||
|
|
||||||
xorBufferInPlace(aesKey, encryptedHash)
|
u8.xorInPlace(aesKey, encryptedHash)
|
||||||
const decryptedData = concatBuffers([aesKey, encrypted])
|
const decryptedData = u8.concat2(aesKey, encrypted)
|
||||||
|
|
||||||
const decryptedDataBigint = bufferToBigInt(decryptedData)
|
const decryptedDataBigint = bigint.fromBytes(decryptedData)
|
||||||
|
|
||||||
if (decryptedDataBigint >= keyModulus) {
|
if (decryptedDataBigint >= keyModulus) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const encryptedBigint = bigIntModPow(decryptedDataBigint, keyExponent, keyModulus)
|
const encryptedBigint = bigint.modPowBinary(decryptedDataBigint, keyExponent, keyModulus)
|
||||||
|
|
||||||
return bigIntToBuffer(encryptedBigint, 256)
|
return bigint.toBytes(encryptedBigint, 256)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function rsaEncrypt(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Uint8Array {
|
function rsaEncrypt(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Uint8Array {
|
||||||
const toEncrypt = concatBuffers([
|
const toEncrypt = u8.concat3(
|
||||||
crypto.sha1(data),
|
crypto.sha1(data),
|
||||||
data,
|
data,
|
||||||
// sha1 is always 20 bytes, so we're left with 255 - 20 - x padding
|
// sha1 is always 20 bytes, so we're left with 255 - 20 - x padding
|
||||||
crypto.randomBytes(235 - data.length),
|
crypto.randomBytes(235 - data.length),
|
||||||
])
|
)
|
||||||
|
|
||||||
const encryptedBigInt = bigIntModPow(
|
const encryptedBigInt = bigint.modPowBinary(
|
||||||
bufferToBigInt(toEncrypt),
|
bigint.fromBytes(toEncrypt),
|
||||||
BigInt(`0x${key.exponent}`),
|
BigInt(`0x${key.exponent}`),
|
||||||
BigInt(`0x${key.modulus}`),
|
BigInt(`0x${key.modulus}`),
|
||||||
)
|
)
|
||||||
|
|
||||||
return bigIntToBuffer(encryptedBigInt)
|
return bigint.toBytes(encryptedBigInt)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -233,7 +231,7 @@ export async function doAuthorization(
|
||||||
|
|
||||||
mtpAssertTypeIs('auth step 1', resPq, 'mt_resPQ')
|
mtpAssertTypeIs('auth step 1', resPq, 'mt_resPQ')
|
||||||
|
|
||||||
if (!buffersEqual(resPq.nonce, nonce)) {
|
if (!typed.equal(resPq.nonce, nonce)) {
|
||||||
throw new MtSecurityError('Step 1: invalid nonce from server')
|
throw new MtSecurityError('Step 1: invalid nonce from server')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +248,7 @@ export async function doAuthorization(
|
||||||
}
|
}
|
||||||
log.debug('found server key, fp = %s, old = %s', publicKey.fingerprint, publicKey.old)
|
log.debug('found server key, fp = %s, old = %s', publicKey.fingerprint, publicKey.old)
|
||||||
|
|
||||||
if (millerRabin(crypto, bufferToBigInt(resPq.pq))) {
|
if (millerRabin(crypto, bigint.fromBytes(resPq.pq))) {
|
||||||
throw new MtSecurityError('Step 2: pq is prime')
|
throw new MtSecurityError('Step 2: pq is prime')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,10 +293,10 @@ export async function doAuthorization(
|
||||||
|
|
||||||
mtpAssertTypeIs('auth step 2', serverDhParams, 'mt_server_DH_params_ok')
|
mtpAssertTypeIs('auth step 2', serverDhParams, 'mt_server_DH_params_ok')
|
||||||
|
|
||||||
if (!buffersEqual(serverDhParams.nonce, nonce)) {
|
if (!typed.equal(serverDhParams.nonce, nonce)) {
|
||||||
throw new MtSecurityError('Step 2: invalid nonce from server')
|
throw new MtSecurityError('Step 2: invalid nonce from server')
|
||||||
}
|
}
|
||||||
if (!buffersEqual(serverDhParams.serverNonce, resPq.serverNonce)) {
|
if (!typed.equal(serverDhParams.serverNonce, resPq.serverNonce)) {
|
||||||
throw new MtSecurityError('Step 2: invalid server nonce from server')
|
throw new MtSecurityError('Step 2: invalid server nonce from server')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,36 +315,36 @@ export async function doAuthorization(
|
||||||
const serverDhInnerReader = new TlBinaryReader(readerMap, plainTextAnswer, 20)
|
const serverDhInnerReader = new TlBinaryReader(readerMap, plainTextAnswer, 20)
|
||||||
const serverDhInner = serverDhInnerReader.object() as mtp.TlObject
|
const serverDhInner = serverDhInnerReader.object() as mtp.TlObject
|
||||||
|
|
||||||
if (!buffersEqual(innerDataHash, crypto.sha1(plainTextAnswer.subarray(20, serverDhInnerReader.pos)))) {
|
if (!typed.equal(innerDataHash, crypto.sha1(plainTextAnswer.subarray(20, serverDhInnerReader.pos)))) {
|
||||||
throw new MtSecurityError('Step 3: invalid inner data hash')
|
throw new MtSecurityError('Step 3: invalid inner data hash')
|
||||||
}
|
}
|
||||||
|
|
||||||
mtpAssertTypeIs('auth step 3', serverDhInner, 'mt_server_DH_inner_data')
|
mtpAssertTypeIs('auth step 3', serverDhInner, 'mt_server_DH_inner_data')
|
||||||
|
|
||||||
if (!buffersEqual(serverDhInner.nonce, nonce)) {
|
if (!typed.equal(serverDhInner.nonce, nonce)) {
|
||||||
throw new Error('Step 3: invalid nonce from server')
|
throw new Error('Step 3: invalid nonce from server')
|
||||||
}
|
}
|
||||||
if (!buffersEqual(serverDhInner.serverNonce, resPq.serverNonce)) {
|
if (!typed.equal(serverDhInner.serverNonce, resPq.serverNonce)) {
|
||||||
throw new Error('Step 3: invalid server nonce from server')
|
throw new Error('Step 3: invalid server nonce from server')
|
||||||
}
|
}
|
||||||
|
|
||||||
const dhPrime = bufferToBigInt(serverDhInner.dhPrime)
|
const dhPrime = bigint.fromBytes(serverDhInner.dhPrime)
|
||||||
const timeOffset = Math.floor(Date.now() / 1000) - serverDhInner.serverTime
|
const timeOffset = Math.floor(Date.now() / 1000) - serverDhInner.serverTime
|
||||||
session.updateTimeOffset(timeOffset)
|
session.updateTimeOffset(timeOffset)
|
||||||
|
|
||||||
const g = BigInt(serverDhInner.g)
|
const g = BigInt(serverDhInner.g)
|
||||||
const gA = bufferToBigInt(serverDhInner.gA)
|
const gA = bigint.fromBytes(serverDhInner.gA)
|
||||||
|
|
||||||
checkDhPrime(crypto, log, dhPrime, serverDhInner.g)
|
checkDhPrime(crypto, log, dhPrime, serverDhInner.g)
|
||||||
|
|
||||||
let retryId = Long.ZERO
|
let retryId = Long.ZERO
|
||||||
const serverSalt = xorBuffer(newNonce.subarray(0, 8), resPq.serverNonce.subarray(0, 8))
|
const serverSalt = u8.xor(newNonce.subarray(0, 8), resPq.serverNonce.subarray(0, 8))
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const b = bufferToBigInt(crypto.randomBytes(256))
|
const b = bigint.fromBytes(crypto.randomBytes(256))
|
||||||
const gB = bigIntModPow(g, b, dhPrime)
|
const gB = bigint.modPowBinary(g, b, dhPrime)
|
||||||
|
|
||||||
const authKey = bigIntToBuffer(bigIntModPow(gA, b, dhPrime))
|
const authKey = bigint.toBytes(bigint.modPowBinary(gA, b, dhPrime))
|
||||||
const authKeyAuxHash = crypto.sha1(authKey).subarray(0, 8)
|
const authKeyAuxHash = crypto.sha1(authKey).subarray(0, 8)
|
||||||
|
|
||||||
// validate DH params
|
// validate DH params
|
||||||
|
@ -367,7 +365,7 @@ export async function doAuthorization(
|
||||||
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})')
|
||||||
}
|
}
|
||||||
|
|
||||||
const gB_ = bigIntToBuffer(gB, 0, false)
|
const gB_ = bigint.toBytes(gB, 0)
|
||||||
|
|
||||||
// Step 4: send client DH
|
// Step 4: send client DH
|
||||||
const clientDhInner: mtp.RawMt_client_DH_inner_data = {
|
const clientDhInner: mtp.RawMt_client_DH_inner_data = {
|
||||||
|
@ -404,10 +402,10 @@ export async function doAuthorization(
|
||||||
throw new MtTypeAssertionError('auth step 4', 'set_client_DH_params_answer', dhGen._)
|
throw new MtTypeAssertionError('auth step 4', 'set_client_DH_params_answer', dhGen._)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!buffersEqual(dhGen.nonce, nonce)) {
|
if (!typed.equal(dhGen.nonce, nonce)) {
|
||||||
throw new MtSecurityError('Step 4: invalid nonce from server')
|
throw new MtSecurityError('Step 4: invalid nonce from server')
|
||||||
}
|
}
|
||||||
if (!buffersEqual(dhGen.serverNonce, resPq.serverNonce)) {
|
if (!typed.equal(dhGen.serverNonce, resPq.serverNonce)) {
|
||||||
throw new MtSecurityError('Step 4: invalid server nonce from server')
|
throw new MtSecurityError('Step 4: invalid server nonce from server')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,9 +417,9 @@ export async function doAuthorization(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dhGen._ === 'mt_dh_gen_retry') {
|
if (dhGen._ === 'mt_dh_gen_retry') {
|
||||||
const expectedHash = crypto.sha1(concatBuffers([newNonce, new Uint8Array([2]), authKeyAuxHash]))
|
const expectedHash = crypto.sha1(u8.concat3(newNonce, new Uint8Array([2]), authKeyAuxHash))
|
||||||
|
|
||||||
if (!buffersEqual(expectedHash.subarray(4, 20), dhGen.newNonceHash2)) {
|
if (!typed.equal(expectedHash.subarray(4, 20), dhGen.newNonceHash2)) {
|
||||||
throw new MtSecurityError('Step 4: invalid retry nonce hash from server')
|
throw new MtSecurityError('Step 4: invalid retry nonce hash from server')
|
||||||
}
|
}
|
||||||
retryId = Long.fromBytesLE(authKeyAuxHash as unknown as number[])
|
retryId = Long.fromBytesLE(authKeyAuxHash as unknown as number[])
|
||||||
|
@ -430,15 +428,15 @@ export async function doAuthorization(
|
||||||
|
|
||||||
if (dhGen._ !== 'mt_dh_gen_ok') throw new Error('unreachable')
|
if (dhGen._ !== 'mt_dh_gen_ok') throw new Error('unreachable')
|
||||||
|
|
||||||
const expectedHash = crypto.sha1(concatBuffers([newNonce, new Uint8Array([1]), authKeyAuxHash]))
|
const expectedHash = crypto.sha1(u8.concat3(newNonce, [1], authKeyAuxHash))
|
||||||
|
|
||||||
if (!buffersEqual(expectedHash.subarray(4, 20), dhGen.newNonceHash1)) {
|
if (!typed.equal(expectedHash.subarray(4, 20), dhGen.newNonceHash1)) {
|
||||||
throw new MtSecurityError('Step 4: invalid nonce hash from server')
|
throw new MtSecurityError('Step 4: invalid nonce hash from server')
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info('authorization successful')
|
log.info('authorization successful')
|
||||||
|
|
||||||
const dv = dataViewFromBuffer(serverSalt)
|
const dv = typed.toDataView(serverSalt)
|
||||||
|
|
||||||
return [authKey, new Long(dv.getInt32(0, true), dv.getInt32(4, true)), timeOffset]
|
return [authKey, new Long(dv.getInt32(0, true), dv.getInt32(4, true)), timeOffset]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ import Long from 'long'
|
||||||
import type { mtp, tl } from '@mtcute/tl'
|
import type { mtp, tl } from '@mtcute/tl'
|
||||||
import type { TlBinaryWriter, TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
import type { TlBinaryWriter, TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
||||||
import { TlSerializationCounter } from '@mtcute/tl-runtime'
|
import { TlSerializationCounter } from '@mtcute/tl-runtime'
|
||||||
|
import type { Deferred } from '@fuman/utils'
|
||||||
|
|
||||||
import { MtcuteError } from '../types/index.js'
|
import { MtcuteError } from '../types/index.js'
|
||||||
import type {
|
import type {
|
||||||
ControllablePromise,
|
|
||||||
ICryptoProvider,
|
ICryptoProvider,
|
||||||
Logger,
|
Logger,
|
||||||
} from '../utils/index.js'
|
} from '../utils/index.js'
|
||||||
|
@ -27,7 +27,7 @@ import type { ServerSaltManager } from './server-salt.js'
|
||||||
export interface PendingRpc {
|
export interface PendingRpc {
|
||||||
method: string
|
method: string
|
||||||
data: Uint8Array
|
data: Uint8Array
|
||||||
promise: ControllablePromise
|
promise: Deferred<unknown>
|
||||||
stack?: string
|
stack?: string
|
||||||
gzipOverhead?: number
|
gzipOverhead?: number
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ export type PendingMessage =
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
_: 'bind'
|
_: 'bind'
|
||||||
promise: ControllablePromise<boolean | mtp.RawMt_rpc_error>
|
promise: Deferred<boolean | mtp.RawMt_rpc_error>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { Bytes, type ISyncWritable, read } from '@fuman/io'
|
import { Bytes, type ISyncWritable, read } from '@fuman/io'
|
||||||
|
import { bigint, typed, u8 } from '@fuman/utils'
|
||||||
|
|
||||||
import type { Logger } from '../../utils'
|
import type { Logger } from '../../utils'
|
||||||
import { concatBuffers, dataViewFromBuffer } from '../../utils'
|
|
||||||
import { bigIntModInv, bigIntModPow, bigIntToBuffer, bufferToBigInt } from '../../utils/bigint-utils'
|
|
||||||
import type { ICryptoProvider } from '../../utils/crypto/abstract'
|
import type { ICryptoProvider } from '../../utils/crypto/abstract'
|
||||||
import type { IPacketCodec } from '../transports'
|
import type { IPacketCodec } from '../transports'
|
||||||
|
|
||||||
|
@ -35,14 +34,14 @@ function _getDoubleX(x: bigint, mod: bigint): bigint {
|
||||||
numerator = (numerator - 1n) % mod
|
numerator = (numerator - 1n) % mod
|
||||||
numerator = (numerator * numerator) % mod
|
numerator = (numerator * numerator) % mod
|
||||||
|
|
||||||
denominator = bigIntModInv(denominator, mod)
|
denominator = bigint.modInv(denominator, mod)
|
||||||
numerator = (numerator * denominator) % mod
|
numerator = (numerator * denominator) % mod
|
||||||
|
|
||||||
return numerator
|
return numerator
|
||||||
}
|
}
|
||||||
|
|
||||||
function _isQuadraticResidue(a: bigint): boolean {
|
function _isQuadraticResidue(a: bigint): boolean {
|
||||||
const r = bigIntModPow(a, QUAD_RES_POW, QUAD_RES_MOD)
|
const r = bigint.modPowBinary(a, QUAD_RES_POW, QUAD_RES_MOD)
|
||||||
|
|
||||||
return r === 1n
|
return r === 1n
|
||||||
}
|
}
|
||||||
|
@ -126,7 +125,7 @@ class TlsHelloWriter {
|
||||||
) {
|
) {
|
||||||
this._domain = domain
|
this._domain = domain
|
||||||
this.buf = new Uint8Array(size)
|
this.buf = new Uint8Array(size)
|
||||||
this.dv = dataViewFromBuffer(this.buf)
|
this.dv = typed.toDataView(this.buf)
|
||||||
this._grease = initGrease(this.crypto, 7)
|
this._grease = initGrease(this.crypto, 7)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +157,7 @@ class TlsHelloWriter {
|
||||||
const key = this.crypto.randomBytes(32)
|
const key = this.crypto.randomBytes(32)
|
||||||
key[31] &= 127
|
key[31] &= 127
|
||||||
|
|
||||||
let x = bufferToBigInt(key)
|
let x = bigint.fromBytes(key)
|
||||||
const y = _getY2(x, KEY_MOD)
|
const y = _getY2(x, KEY_MOD)
|
||||||
|
|
||||||
if (_isQuadraticResidue(y)) {
|
if (_isQuadraticResidue(y)) {
|
||||||
|
@ -166,7 +165,7 @@ class TlsHelloWriter {
|
||||||
x = _getDoubleX(x, KEY_MOD)
|
x = _getDoubleX(x, KEY_MOD)
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = bigIntToBuffer(x, 32, true)
|
const key = bigint.toBytes(x, 32, true)
|
||||||
this.string(key)
|
this.string(key)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -201,7 +200,7 @@ class TlsHelloWriter {
|
||||||
this.endScope()
|
this.endScope()
|
||||||
|
|
||||||
const hash = await this.crypto.hmacSha256(this.buf, secret)
|
const hash = await this.crypto.hmacSha256(this.buf, secret)
|
||||||
const dv = dataViewFromBuffer(hash)
|
const dv = typed.toDataView(hash)
|
||||||
|
|
||||||
const old = dv.getInt32(28, true)
|
const old = dv.getInt32(28, true)
|
||||||
dv.setInt32(28, old ^ unixTime, true)
|
dv.setInt32(28, old ^ unixTime, true)
|
||||||
|
@ -255,12 +254,12 @@ export class FakeTlsPacketCodec implements IPacketCodec {
|
||||||
let packet
|
let packet
|
||||||
if (this._isFirstTls) {
|
if (this._isFirstTls) {
|
||||||
this._isFirstTls = false
|
this._isFirstTls = false
|
||||||
packet = concatBuffers([this._tag, tmp.readSync(MAX_TLS_PACKET_LENGTH - this._tag.length)])
|
packet = u8.concat2(this._tag, tmp.readSync(MAX_TLS_PACKET_LENGTH - this._tag.length))
|
||||||
} else {
|
} else {
|
||||||
packet = tmp.readSync(MAX_TLS_PACKET_LENGTH)
|
packet = tmp.readSync(MAX_TLS_PACKET_LENGTH)
|
||||||
}
|
}
|
||||||
|
|
||||||
dataViewFromBuffer(header).setUint16(3, packet.length)
|
typed.toDataView(header).setUint16(3, packet.length)
|
||||||
|
|
||||||
into.writeSync(header.length).set(header)
|
into.writeSync(header.length).set(header)
|
||||||
into.writeSync(packet.length).set(packet)
|
into.writeSync(packet.length).set(packet)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import type { tl } from '@mtcute/tl'
|
import type { tl } from '@mtcute/tl'
|
||||||
import type { ITcpConnection, TcpEndpoint } from '@fuman/net'
|
import type { ITcpConnection, TcpEndpoint } from '@fuman/net'
|
||||||
import { Bytes, read, write } from '@fuman/io'
|
import { Bytes, read, write } from '@fuman/io'
|
||||||
import { base64, hex } from '@fuman/utils'
|
import { base64, hex, typed, u8 } from '@fuman/utils'
|
||||||
|
|
||||||
import type { IPacketCodec, ITelegramConnection, MtProxyInfo, TelegramTransport } from '../transports/index.js'
|
import type { IPacketCodec, ITelegramConnection, MtProxyInfo, TelegramTransport } from '../transports/index.js'
|
||||||
import { IntermediatePacketCodec, ObfuscatedPacketCodec, PaddedIntermediatePacketCodec } from '../transports/index.js'
|
import { IntermediatePacketCodec, ObfuscatedPacketCodec, PaddedIntermediatePacketCodec } from '../transports/index.js'
|
||||||
import { type BasicDcOption, type ICryptoProvider, type Logger, buffersEqual, concatBuffers, dataViewFromBuffer } from '../../utils/index.js'
|
import type { BasicDcOption, ICryptoProvider, Logger } from '../../utils/index.js'
|
||||||
import { MtSecurityError, MtUnsupportedError } from '../../types/errors.js'
|
import { MtSecurityError, MtUnsupportedError } from '../../types/errors.js'
|
||||||
|
|
||||||
import { FakeTlsPacketCodec, generateFakeTlsHeader } from './_fake-tls.js'
|
import { FakeTlsPacketCodec, generateFakeTlsHeader } from './_fake-tls.js'
|
||||||
|
@ -152,11 +152,11 @@ export abstract class BaseMtProxyTransport implements TelegramTransport {
|
||||||
const buf = await read.async.exactly(conn, first.length + 2)
|
const buf = await read.async.exactly(conn, first.length + 2)
|
||||||
write.bytes(resp, buf)
|
write.bytes(resp, buf)
|
||||||
|
|
||||||
if (!buffersEqual(buf.slice(0, first.length), first)) {
|
if (!typed.equal(buf.slice(0, first.length), first)) {
|
||||||
throw new MtSecurityError('Server hello is invalid')
|
throw new MtSecurityError('Server hello is invalid')
|
||||||
}
|
}
|
||||||
|
|
||||||
const skipSize = dataViewFromBuffer(buf).getUint16(first.length)
|
const skipSize = typed.toDataView(buf).getUint16(first.length)
|
||||||
|
|
||||||
write.bytes(resp, await read.async.exactly(conn, skipSize))
|
write.bytes(resp, await read.async.exactly(conn, skipSize))
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ export abstract class BaseMtProxyTransport implements TelegramTransport {
|
||||||
const respBuf = resp.result()
|
const respBuf = resp.result()
|
||||||
const respRand = respBuf.slice(11, 11 + 32)
|
const respRand = respBuf.slice(11, 11 + 32)
|
||||||
const hash = await this._crypto.hmacSha256(
|
const hash = await this._crypto.hmacSha256(
|
||||||
concatBuffers([
|
u8.concat([
|
||||||
helloRand,
|
helloRand,
|
||||||
respBuf.slice(0, 11),
|
respBuf.slice(0, 11),
|
||||||
new Uint8Array(32),
|
new Uint8Array(32),
|
||||||
|
@ -173,7 +173,7 @@ export abstract class BaseMtProxyTransport implements TelegramTransport {
|
||||||
this._rawSecret,
|
this._rawSecret,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!buffersEqual(hash, respRand)) {
|
if (!typed.equal(hash, respRand)) {
|
||||||
throw new MtSecurityError('Response hash is invalid')
|
throw new MtSecurityError('Response hash is invalid')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
|
|
||||||
import type { mtp, tl } from '@mtcute/tl'
|
import type { mtp, tl } from '@mtcute/tl'
|
||||||
|
import { Deferred } from '@fuman/utils'
|
||||||
|
|
||||||
import type { Logger } from '../utils/index.js'
|
import type { Logger } from '../utils/index.js'
|
||||||
import { createControllablePromise } from '../utils/index.js'
|
|
||||||
|
|
||||||
import { MtprotoSession } from './mtproto-session.js'
|
import { MtprotoSession } from './mtproto-session.js'
|
||||||
import type { SessionConnectionParams } from './session-connection.js'
|
import type { SessionConnectionParams } from './session-connection.js'
|
||||||
|
@ -132,10 +132,10 @@ export class MultiSessionConnection extends EventEmitter {
|
||||||
|
|
||||||
if (enforcePfsChanged) {
|
if (enforcePfsChanged) {
|
||||||
// we need to fetch new auth keys first
|
// we need to fetch new auth keys first
|
||||||
const promise = createControllablePromise<void>()
|
const promise = new Deferred<void>()
|
||||||
this.emit('request-keys', promise)
|
this.emit('request-keys', promise)
|
||||||
|
|
||||||
promise
|
promise.promise
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this._connections.forEach((conn) => {
|
this._connections.forEach((conn) => {
|
||||||
conn.setUsePfs(this.params.usePfs || this._enforcePfs)
|
conn.setUsePfs(this.params.usePfs || this._enforcePfs)
|
||||||
|
|
|
@ -2,14 +2,14 @@ import type { mtp, tl } from '@mtcute/tl'
|
||||||
import type { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
import type { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
||||||
import type Long from 'long'
|
import type Long from 'long'
|
||||||
import { type ReconnectionStrategy, defaultReconnectionStrategy } from '@fuman/net'
|
import { type ReconnectionStrategy, defaultReconnectionStrategy } from '@fuman/net'
|
||||||
|
import { Deferred } from '@fuman/utils'
|
||||||
|
|
||||||
import { getPlatform } from '../platform.js'
|
import { getPlatform } from '../platform.js'
|
||||||
import type { StorageManager } from '../storage/storage.js'
|
import type { StorageManager } from '../storage/storage.js'
|
||||||
import { MtArgumentError, MtUnsupportedError, MtcuteError } from '../types/index.js'
|
import { MtArgumentError, MtUnsupportedError, MtcuteError } from '../types/index.js'
|
||||||
import type { ComposedMiddleware, Middleware } from '../utils/composer.js'
|
import type { ComposedMiddleware, Middleware } from '../utils/composer.js'
|
||||||
import { composeMiddlewares } from '../utils/composer.js'
|
import { composeMiddlewares } from '../utils/composer.js'
|
||||||
import type { ControllablePromise, DcOptions, ICryptoProvider, Logger } from '../utils/index.js'
|
import type { DcOptions, ICryptoProvider, Logger } from '../utils/index.js'
|
||||||
import { createControllablePromise } from '../utils/index.js'
|
|
||||||
import { assertTypeIs, isTlRpcError } from '../utils/type-assertions.js'
|
import { assertTypeIs, isTlRpcError } from '../utils/type-assertions.js'
|
||||||
|
|
||||||
import type { ConfigManager } from './config-manager.js'
|
import type { ConfigManager } from './config-manager.js'
|
||||||
|
@ -382,7 +382,7 @@ export class DcConnectionManager {
|
||||||
})
|
})
|
||||||
|
|
||||||
// fucking awesome architecture, but whatever
|
// fucking awesome architecture, but whatever
|
||||||
connection.on('request-keys', (promise: ControllablePromise<void>) => {
|
connection.on('request-keys', (promise: Deferred<void>) => {
|
||||||
this.loadKeys(true)
|
this.loadKeys(true)
|
||||||
.then(() => promise.resolve())
|
.then(() => promise.resolve())
|
||||||
.catch((e: Error) => promise.reject(e))
|
.catch((e: Error) => promise.reject(e))
|
||||||
|
@ -577,8 +577,8 @@ export class NetworkManager {
|
||||||
return this._dcConnections.get(dcId)!
|
return this._dcConnections.get(dcId)!
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = createControllablePromise<void>()
|
const promise = new Deferred<void>()
|
||||||
this._dcCreationPromise.set(dcId, promise)
|
this._dcCreationPromise.set(dcId, promise.promise)
|
||||||
|
|
||||||
this._log.debug('creating new DC %d', dcId)
|
this._log.debug('creating new DC %d', dcId)
|
||||||
|
|
||||||
|
@ -632,8 +632,8 @@ export class NetworkManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
this._log.debug('exporting auth to dc %d', manager.dcId)
|
this._log.debug('exporting auth to dc %d', manager.dcId)
|
||||||
const promise = createControllablePromise<void>()
|
const promise = new Deferred<void>()
|
||||||
this._pendingExports[manager.dcId] = promise
|
this._pendingExports[manager.dcId] = promise.promise
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const auth = await this.call({
|
const auth = await this.call({
|
||||||
|
|
|
@ -4,9 +4,9 @@ import EventEmitter from 'events'
|
||||||
import type { ReconnectionStrategy } from '@fuman/net'
|
import type { ReconnectionStrategy } from '@fuman/net'
|
||||||
import { PersistentConnection as FumanPersistentConnection } from '@fuman/net'
|
import { PersistentConnection as FumanPersistentConnection } from '@fuman/net'
|
||||||
import { FramedReader, FramedWriter } from '@fuman/io'
|
import { FramedReader, FramedWriter } from '@fuman/io'
|
||||||
|
import { timers } from '@fuman/utils'
|
||||||
|
|
||||||
import type { BasicDcOption, ICryptoProvider, Logger } from '../utils/index.js'
|
import type { BasicDcOption, ICryptoProvider, Logger } from '../utils/index.js'
|
||||||
import { timers } from '../utils/index.js'
|
|
||||||
|
|
||||||
import type { IPacketCodec, ITelegramConnection, TelegramTransport } from './transports/abstract.js'
|
import type { IPacketCodec, ITelegramConnection, TelegramTransport } from './transports/abstract.js'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
import type { mtp } from '@mtcute/tl'
|
import type { mtp } from '@mtcute/tl'
|
||||||
|
import { timers } from '@fuman/utils'
|
||||||
import { timers } from '../utils/index.js'
|
|
||||||
|
|
||||||
export class ServerSaltManager {
|
export class ServerSaltManager {
|
||||||
private _futureSalts: mtp.RawMt_future_salt[] = []
|
private _futureSalts: mtp.RawMt_future_salt[] = []
|
||||||
|
|
|
@ -3,19 +3,14 @@ import type { mtp } from '@mtcute/tl'
|
||||||
import { tl } from '@mtcute/tl'
|
import { tl } from '@mtcute/tl'
|
||||||
import type { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
import type { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
||||||
import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime'
|
import { TlBinaryReader, TlBinaryWriter, TlSerializationCounter } from '@mtcute/tl-runtime'
|
||||||
|
import { Deferred, u8 } from '@fuman/utils'
|
||||||
|
|
||||||
import { getPlatform } from '../platform.js'
|
import { getPlatform } from '../platform.js'
|
||||||
import { MtArgumentError, MtTimeoutError, MtcuteError } from '../types/index.js'
|
import { MtArgumentError, MtTimeoutError, MtcuteError } from '../types/index.js'
|
||||||
import { createAesIgeForMessageOld } from '../utils/crypto/mtproto.js'
|
import { createAesIgeForMessageOld } from '../utils/crypto/mtproto.js'
|
||||||
import type {
|
import type { ICryptoProvider } from '../utils/index.js'
|
||||||
ControllablePromise,
|
|
||||||
ICryptoProvider,
|
|
||||||
} from '../utils/index.js'
|
|
||||||
import {
|
import {
|
||||||
EarlyTimer,
|
EarlyTimer,
|
||||||
|
|
||||||
concatBuffers,
|
|
||||||
createControllablePromise,
|
|
||||||
longFromBuffer,
|
longFromBuffer,
|
||||||
randomLong,
|
randomLong,
|
||||||
removeFromLongArray,
|
removeFromLongArray,
|
||||||
|
@ -74,7 +69,7 @@ export class SessionConnection extends PersistentConnection {
|
||||||
private _queuedDestroySession: Long[] = []
|
private _queuedDestroySession: Long[] = []
|
||||||
|
|
||||||
// waitForMessage
|
// waitForMessage
|
||||||
private _pendingWaitForUnencrypted: [ControllablePromise<Uint8Array>, timers.Timer][] = []
|
private _pendingWaitForUnencrypted: [Deferred<Uint8Array>, timers.Timer][] = []
|
||||||
|
|
||||||
private _usePfs
|
private _usePfs
|
||||||
private _isPfsBindingPending = false
|
private _isPfsBindingPending = false
|
||||||
|
@ -389,9 +384,9 @@ export class SessionConnection extends PersistentConnection {
|
||||||
|
|
||||||
const ige = createAesIgeForMessageOld(this._crypto, this._session._authKey.key, msgKey, true)
|
const ige = createAesIgeForMessageOld(this._crypto, this._session._authKey.key, msgKey, true)
|
||||||
const encryptedData = ige.encrypt(msgWithPadding)
|
const encryptedData = ige.encrypt(msgWithPadding)
|
||||||
const encryptedMessage = concatBuffers([this._session._authKey.id, msgKey, encryptedData])
|
const encryptedMessage = u8.concat3(this._session._authKey.id, msgKey, encryptedData)
|
||||||
|
|
||||||
const promise = createControllablePromise<mtp.RawMt_rpc_error | boolean>()
|
const promise = new Deferred<mtp.RawMt_rpc_error | boolean>()
|
||||||
|
|
||||||
// encrypt the message using temp key and same msg id
|
// encrypt the message using temp key and same msg id
|
||||||
// this is a bit of a hack, but it works
|
// this is a bit of a hack, but it works
|
||||||
|
@ -432,7 +427,7 @@ export class SessionConnection extends PersistentConnection {
|
||||||
)
|
)
|
||||||
await this.send(requestEncrypted)
|
await this.send(requestEncrypted)
|
||||||
|
|
||||||
const res = await promise
|
const res = await promise.promise
|
||||||
|
|
||||||
this._session.pendingMessages.delete(msgId)
|
this._session.pendingMessages.delete(msgId)
|
||||||
|
|
||||||
|
@ -496,14 +491,14 @@ export class SessionConnection extends PersistentConnection {
|
||||||
if (this._destroyed) {
|
if (this._destroyed) {
|
||||||
return Promise.reject(new MtcuteError('Connection destroyed'))
|
return Promise.reject(new MtcuteError('Connection destroyed'))
|
||||||
}
|
}
|
||||||
const promise = createControllablePromise<Uint8Array>()
|
const promise = new Deferred<Uint8Array>()
|
||||||
const timeoutId = timers.setTimeout(() => {
|
const timeoutId = timers.setTimeout(() => {
|
||||||
promise.reject(new MtTimeoutError(timeout))
|
promise.reject(new MtTimeoutError(timeout))
|
||||||
this._pendingWaitForUnencrypted = this._pendingWaitForUnencrypted.filter(it => it[0] !== promise)
|
this._pendingWaitForUnencrypted = this._pendingWaitForUnencrypted.filter(it => it[0] !== promise)
|
||||||
}, timeout)
|
}, timeout)
|
||||||
this._pendingWaitForUnencrypted.push([promise, timeoutId])
|
this._pendingWaitForUnencrypted.push([promise, timeoutId])
|
||||||
|
|
||||||
return promise
|
return promise.promise
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onMessage(data: Uint8Array): void {
|
protected onMessage(data: Uint8Array): void {
|
||||||
|
@ -1412,7 +1407,7 @@ export class SessionConnection extends PersistentConnection {
|
||||||
|
|
||||||
const pending: PendingRpc = {
|
const pending: PendingRpc = {
|
||||||
method,
|
method,
|
||||||
promise: createControllablePromise(),
|
promise: new Deferred<unknown>(),
|
||||||
data: content,
|
data: content,
|
||||||
// we will need to know size of gzip_packed overhead in _flush()
|
// we will need to know size of gzip_packed overhead in _flush()
|
||||||
gzipOverhead: shouldGzip ? 4 + TlSerializationCounter.countBytesOverhead(content.length) : 0,
|
gzipOverhead: shouldGzip ? 4 + TlSerializationCounter.countBytesOverhead(content.length) : 0,
|
||||||
|
@ -1435,7 +1430,7 @@ export class SessionConnection extends PersistentConnection {
|
||||||
if (abortSignal?.aborted) {
|
if (abortSignal?.aborted) {
|
||||||
pending.promise.reject(abortSignal.reason)
|
pending.promise.reject(abortSignal.reason)
|
||||||
|
|
||||||
return pending.promise
|
return pending.promise.promise
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
|
@ -1448,7 +1443,7 @@ export class SessionConnection extends PersistentConnection {
|
||||||
|
|
||||||
this._enqueueRpc(pending, true)
|
this._enqueueRpc(pending, true)
|
||||||
|
|
||||||
return pending.promise
|
return pending.promise.promise
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyNetworkChanged(online: boolean): void {
|
notifyNetworkChanged(online: boolean): void {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import type { Bytes, ISyncWritable } from '@fuman/io'
|
import type { Bytes, ISyncWritable } from '@fuman/io'
|
||||||
import { read, write } from '@fuman/io'
|
import { read, write } from '@fuman/io'
|
||||||
|
import { typed } from '@fuman/utils'
|
||||||
|
|
||||||
import type { ICryptoProvider } from '../../utils/index.js'
|
import type { ICryptoProvider } from '../../utils/index.js'
|
||||||
import { dataViewFromBuffer, getRandomInt } from '../../utils/index.js'
|
import { getRandomInt } from '../../utils/index.js'
|
||||||
|
|
||||||
import type { IPacketCodec } from './abstract.js'
|
import type { IPacketCodec } from './abstract.js'
|
||||||
import { TransportError } from './abstract.js'
|
import { TransportError } from './abstract.js'
|
||||||
|
@ -68,7 +69,7 @@ export class PaddedIntermediatePacketCodec extends IntermediatePacketCodec imple
|
||||||
const padSize = getRandomInt(16)
|
const padSize = getRandomInt(16)
|
||||||
|
|
||||||
const ret = into.writeSync(frame.length + 4 + padSize)
|
const ret = into.writeSync(frame.length + 4 + padSize)
|
||||||
const dv = dataViewFromBuffer(ret)
|
const dv = typed.toDataView(ret)
|
||||||
dv.setUint32(0, frame.length + padSize, true)
|
dv.setUint32(0, frame.length + padSize, true)
|
||||||
ret.set(frame, 4)
|
ret.set(frame, 4)
|
||||||
this._crypto.randomFill(ret.subarray(4 + frame.length))
|
this._crypto.randomFill(ret.subarray(4 + frame.length))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { ISyncWritable } from '@fuman/io'
|
import type { ISyncWritable } from '@fuman/io'
|
||||||
import { Bytes, write } from '@fuman/io'
|
import { Bytes, write } from '@fuman/io'
|
||||||
|
import { typed, u8 } from '@fuman/utils'
|
||||||
|
|
||||||
import { bufferToReversed, concatBuffers, dataViewFromBuffer } from '../../utils/buffer-utils.js'
|
|
||||||
import type { IAesCtr, ICryptoProvider, Logger } from '../../utils/index.js'
|
import type { IAesCtr, ICryptoProvider, Logger } from '../../utils/index.js'
|
||||||
|
|
||||||
import type { IPacketCodec } from './abstract.js'
|
import type { IPacketCodec } from './abstract.js'
|
||||||
|
@ -39,7 +39,7 @@ export class ObfuscatedPacketCodec implements IPacketCodec {
|
||||||
random = this._crypto.randomBytes(64)
|
random = this._crypto.randomBytes(64)
|
||||||
if (random[0] === 0xEF) continue
|
if (random[0] === 0xEF) continue
|
||||||
|
|
||||||
dv = dataViewFromBuffer(random)
|
dv = typed.toDataView(random)
|
||||||
const firstInt = dv.getUint32(0, true)
|
const firstInt = dv.getUint32(0, true)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -74,7 +74,7 @@ export class ObfuscatedPacketCodec implements IPacketCodec {
|
||||||
dv.setInt16(60, dcId, true)
|
dv.setInt16(60, dcId, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const randomRev = bufferToReversed(random, 8, 56)
|
const randomRev = u8.toReversed(random.subarray(8, 56))
|
||||||
|
|
||||||
let encryptKey = random.subarray(8, 40)
|
let encryptKey = random.subarray(8, 40)
|
||||||
const encryptIv = random.subarray(40, 56)
|
const encryptIv = random.subarray(40, 56)
|
||||||
|
@ -83,8 +83,8 @@ export class ObfuscatedPacketCodec implements IPacketCodec {
|
||||||
const decryptIv = randomRev.subarray(32, 48)
|
const decryptIv = randomRev.subarray(32, 48)
|
||||||
|
|
||||||
if (this._proxy) {
|
if (this._proxy) {
|
||||||
encryptKey = this._crypto.sha256(concatBuffers([encryptKey, this._proxy.secret]))
|
encryptKey = this._crypto.sha256(u8.concat2(encryptKey, this._proxy.secret))
|
||||||
decryptKey = this._crypto.sha256(concatBuffers([decryptKey, this._proxy.secret]))
|
decryptKey = this._crypto.sha256(u8.concat2(decryptKey, this._proxy.secret))
|
||||||
}
|
}
|
||||||
|
|
||||||
this._encryptor = this._crypto.createAesCtr(encryptKey, encryptIv, true)
|
this._encryptor = this._crypto.createAesCtr(encryptKey, encryptIv, true)
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
|
||||||
|
|
||||||
import { AsyncLock } from './async-lock.js'
|
|
||||||
import { sleep } from './misc-utils.js'
|
|
||||||
|
|
||||||
describe('AsyncLock', () => {
|
|
||||||
it('should correctly lock execution', async () => {
|
|
||||||
const lock = new AsyncLock()
|
|
||||||
|
|
||||||
const log: number[] = []
|
|
||||||
await Promise.all(
|
|
||||||
Array.from({ length: 10 }, (_, idx) =>
|
|
||||||
lock.with(async () => {
|
|
||||||
await sleep(10 - idx)
|
|
||||||
log.push(idx)
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(log).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should correctly propagate errors', async () => {
|
|
||||||
const lock = new AsyncLock()
|
|
||||||
|
|
||||||
await expect(async () => {
|
|
||||||
await lock.with(() => {
|
|
||||||
throw new Error('test')
|
|
||||||
})
|
|
||||||
}).rejects.toThrow('test')
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,44 +0,0 @@
|
||||||
import { Deque } from './deque.js'
|
|
||||||
|
|
||||||
type LockInfo = [Promise<void>, () => void]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple class implementing a semaphore like
|
|
||||||
* behaviour.
|
|
||||||
*/
|
|
||||||
export class AsyncLock {
|
|
||||||
private _queue = new Deque<LockInfo>()
|
|
||||||
|
|
||||||
async acquire(): Promise<void> {
|
|
||||||
let info
|
|
||||||
|
|
||||||
while ((info = this._queue.peekFront())) {
|
|
||||||
await info[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
let unlock: () => void
|
|
||||||
const prom = new Promise<void>((resolve) => {
|
|
||||||
unlock = resolve
|
|
||||||
})
|
|
||||||
|
|
||||||
this._queue.pushBack([prom, unlock!])
|
|
||||||
}
|
|
||||||
|
|
||||||
release(): void {
|
|
||||||
if (!this._queue.length) throw new Error('Nothing to release')
|
|
||||||
|
|
||||||
this._queue.popFront()![1]()
|
|
||||||
}
|
|
||||||
|
|
||||||
with(func: () => Promise<void>): Promise<void> {
|
|
||||||
let err: unknown = null
|
|
||||||
|
|
||||||
return this.acquire()
|
|
||||||
.then(() => func())
|
|
||||||
.catch(e => void (err = e))
|
|
||||||
.then(() => {
|
|
||||||
this.release()
|
|
||||||
if (err) throw err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,90 +1,13 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { defaultTestCryptoProvider } from '@mtcute/test'
|
import { defaultTestCryptoProvider } from '@mtcute/test'
|
||||||
import { hex } from '@fuman/utils'
|
import { bigint } from '@fuman/utils'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
bigIntBitLength,
|
|
||||||
bigIntGcd,
|
|
||||||
bigIntModInv,
|
|
||||||
bigIntModPow,
|
|
||||||
bigIntToBuffer,
|
|
||||||
bufferToBigInt,
|
|
||||||
randomBigInt,
|
randomBigInt,
|
||||||
randomBigIntBits,
|
randomBigIntBits,
|
||||||
randomBigIntInRange,
|
randomBigIntInRange,
|
||||||
twoMultiplicity,
|
|
||||||
} from './index.js'
|
} from './index.js'
|
||||||
|
|
||||||
describe('bigIntBitLength', () => {
|
|
||||||
it('should correctly calculate bit length', () => {
|
|
||||||
expect(bigIntBitLength(0n)).eq(0)
|
|
||||||
expect(bigIntBitLength(1n)).eq(1)
|
|
||||||
expect(bigIntBitLength(2n)).eq(2)
|
|
||||||
expect(bigIntBitLength(255n)).eq(8)
|
|
||||||
expect(bigIntBitLength(256n)).eq(9)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('bigIntToBuffer', () => {
|
|
||||||
it('should handle writing to BE', () => {
|
|
||||||
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'), 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('9341376580368336208'), 8, false)]).eql([
|
|
||||||
...hex.decode('81A33C81D2020550'),
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle writing to LE', () => {
|
|
||||||
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'), 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('9341376580368336208'), 8, true)]).eql([
|
|
||||||
...hex.decode('81A33C81D2020550').reverse(),
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle large integers', () => {
|
|
||||||
const buf = hex.decode(
|
|
||||||
'1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538',
|
|
||||||
)
|
|
||||||
const num = BigInt(
|
|
||||||
'0x1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538',
|
|
||||||
)
|
|
||||||
|
|
||||||
expect([...bigIntToBuffer(num, 0, false)]).eql([...buf])
|
|
||||||
expect([...bigIntToBuffer(num, 0, true)]).eql([...buf.reverse()])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('bufferToBigInt', () => {
|
|
||||||
it('should handle reading BE', () => {
|
|
||||||
expect(bufferToBigInt(new Uint8Array([0xA0, 0x26, 0xDC]), false).toString()).eq('10495708')
|
|
||||||
expect(bufferToBigInt(new Uint8Array([0x00, 0xA0, 0x26, 0xDC]), false).toString()).eq('10495708')
|
|
||||||
expect(bufferToBigInt(new Uint8Array([0xB5, 0x15, 0xC4, 0x15]), false).toString()).eq('3038102549')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle reading LE', () => {
|
|
||||||
expect(bufferToBigInt(new Uint8Array([0xDC, 0x26, 0xA0]), true).toString()).eq('10495708')
|
|
||||||
expect(bufferToBigInt(new Uint8Array([0xDC, 0x26, 0xA0, 0x00]), true).toString()).eq('10495708')
|
|
||||||
expect(bufferToBigInt(new Uint8Array([0x15, 0xC4, 0x15, 0xB5]), true).toString()).eq('3038102549')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle large integers', () => {
|
|
||||||
const buf = hex.decode(
|
|
||||||
'1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538',
|
|
||||||
)
|
|
||||||
const num = BigInt(
|
|
||||||
'0x1a981ce8bf86bf4a1bd79c2ef829914172f8d0e54cb7ad807552d56977e1c946872e2c7bd77052be30e7e9a7a35c4feff848a25759f5f2f5b0e96538',
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(bufferToBigInt(buf, false).toString()).eq(num.toString())
|
|
||||||
expect(bufferToBigInt(buf.reverse(), true).toString()).eq(num.toString())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('randomBigInt', async () => {
|
describe('randomBigInt', async () => {
|
||||||
const c = await defaultTestCryptoProvider()
|
const c = await defaultTestCryptoProvider()
|
||||||
|
|
||||||
|
@ -99,8 +22,8 @@ describe('randomBigInt', async () => {
|
||||||
const a = randomBigInt(c, 32)
|
const a = randomBigInt(c, 32)
|
||||||
const b = randomBigInt(c, 64)
|
const b = randomBigInt(c, 64)
|
||||||
|
|
||||||
expect(bigIntBitLength(a)).toBeLessThanOrEqual(32 * 8)
|
expect(bigint.bitLength(a)).toBeLessThanOrEqual(32 * 8)
|
||||||
expect(bigIntBitLength(b)).toBeLessThanOrEqual(64 * 8)
|
expect(bigint.bitLength(b)).toBeLessThanOrEqual(64 * 8)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -118,8 +41,8 @@ describe('randomBigIntBits', async () => {
|
||||||
const a = randomBigIntBits(c, 32)
|
const a = randomBigIntBits(c, 32)
|
||||||
const b = randomBigIntBits(c, 64)
|
const b = randomBigIntBits(c, 64)
|
||||||
|
|
||||||
expect(bigIntBitLength(a)).toBeLessThanOrEqual(32)
|
expect(bigint.bitLength(a)).toBeLessThanOrEqual(32)
|
||||||
expect(bigIntBitLength(b)).toBeLessThanOrEqual(64)
|
expect(bigint.bitLength(b)).toBeLessThanOrEqual(64)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -140,61 +63,3 @@ describe('randomBigIntInRange', async () => {
|
||||||
expect(a).toBeLessThan(200n)
|
expect(a).toBeLessThan(200n)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('twoMultiplicity', () => {
|
|
||||||
it('should return the multiplicity of 2 in the prime factorization of n', () => {
|
|
||||||
expect(twoMultiplicity(0n)).toEqual(0n)
|
|
||||||
expect(twoMultiplicity(1n)).toEqual(0n)
|
|
||||||
expect(twoMultiplicity(2n)).toEqual(1n)
|
|
||||||
expect(twoMultiplicity(4n)).toEqual(2n)
|
|
||||||
expect(twoMultiplicity(65536n)).toEqual(16n)
|
|
||||||
expect(twoMultiplicity(65537n)).toEqual(0n)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('bigIntGcd', () => {
|
|
||||||
it('should return the greatest common divisor of a and b', () => {
|
|
||||||
expect(bigIntGcd(123n, 456n)).toEqual(3n)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should correctly handle zeros', () => {
|
|
||||||
expect(bigIntGcd(0n, 0n)).toEqual(0n)
|
|
||||||
expect(bigIntGcd(0n, 1n)).toEqual(1n)
|
|
||||||
expect(bigIntGcd(1n, 0n)).toEqual(1n)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should correctly handle equal values', () => {
|
|
||||||
expect(bigIntGcd(1n, 1n)).toEqual(1n)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('bigIntModPow', () => {
|
|
||||||
it('should correctly calculate modular exponentiation', () => {
|
|
||||||
expect(bigIntModPow(2n, 3n, 5n)).toEqual(3n)
|
|
||||||
expect(bigIntModPow(2n, 3n, 6n)).toEqual(2n)
|
|
||||||
expect(bigIntModPow(2n, 3n, 7n)).toEqual(1n)
|
|
||||||
expect(bigIntModPow(2n, 3n, 8n)).toEqual(0n)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should correctly handle very large numbers', () => {
|
|
||||||
// calculating this with BigInt would either take forever or error with "Maximum BigInt size exceeded
|
|
||||||
expect(bigIntModPow(2n, 100000000000n, 100n)).toEqual(76n)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('bigIntModInv', () => {
|
|
||||||
it('should correctly calculate modular inverse', () => {
|
|
||||||
expect(bigIntModInv(2n, 5n)).toEqual(3n)
|
|
||||||
expect(bigIntModInv(2n, 7n)).toEqual(4n)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should error if there's no modular inverse", () => {
|
|
||||||
expect(() => bigIntModInv(2n, 6n)).toThrow(RangeError)
|
|
||||||
expect(() => bigIntModInv(2n, 8n)).toThrow(RangeError)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should correctly handle very large numbers', () => {
|
|
||||||
// calculating this with BigInt would either take forever or error with "Maximum BigInt size exceeded
|
|
||||||
expect(bigIntModInv(123123123123n, 1829n)).toEqual(318n)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
|
@ -1,93 +1,13 @@
|
||||||
import { bufferToReversed } from './buffer-utils.js'
|
import { bigint } from '@fuman/utils'
|
||||||
|
|
||||||
import type { ICryptoProvider } from './crypto/abstract.js'
|
import type { ICryptoProvider } from './crypto/abstract.js'
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the minimum number of bits required to represent a number
|
|
||||||
*/
|
|
||||||
export function bigIntBitLength(n: bigint): number {
|
|
||||||
// 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
|
|
||||||
*
|
|
||||||
* @param value Value to convert
|
|
||||||
* @param length Length of the resulting buffer (by default it's the minimum required)
|
|
||||||
* @param le Whether to use little-endian encoding
|
|
||||||
*/
|
|
||||||
export function bigIntToBuffer(value: bigint, length = 0, le = false): Uint8Array {
|
|
||||||
const bits = bigIntBitLength(value)
|
|
||||||
const bytes = Math.ceil(bits / 8)
|
|
||||||
|
|
||||||
if (length !== 0 && bytes > length) {
|
|
||||||
throw new Error('Value out of bounds')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (length === 0) length = bytes
|
|
||||||
|
|
||||||
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 (unaligned > 0) {
|
|
||||||
for (let i = length - unaligned; i < length; i++) {
|
|
||||||
u8[i] = Number(value & 0xFFn)
|
|
||||||
value >>= 8n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!le) u8.reverse()
|
|
||||||
|
|
||||||
return u8
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a buffer to a big integer
|
|
||||||
*
|
|
||||||
* @param buffer Buffer to convert
|
|
||||||
* @param le Whether to use little-endian encoding
|
|
||||||
*/
|
|
||||||
export function bufferToBigInt(buffer: Uint8Array, le = false): bigint {
|
|
||||||
if (le) buffer = bufferToReversed(buffer)
|
|
||||||
|
|
||||||
const unaligned = buffer.length % 8
|
|
||||||
const dv = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength - unaligned)
|
|
||||||
|
|
||||||
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 cryptographically safe random big integer of the given size (in bytes)
|
* Generate a cryptographically safe random big integer of the given size (in bytes)
|
||||||
* @param size Size in bytes
|
* @param size Size in bytes
|
||||||
*/
|
*/
|
||||||
export function randomBigInt(crypto: ICryptoProvider, size: number): bigint {
|
export function randomBigInt(crypto: ICryptoProvider, size: number): bigint {
|
||||||
return bufferToBigInt(crypto.randomBytes(size))
|
return bigint.fromBytes(crypto.randomBytes(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -97,7 +17,7 @@ export function randomBigInt(crypto: ICryptoProvider, size: number): bigint {
|
||||||
export function randomBigIntBits(crypto: ICryptoProvider, bits: number): bigint {
|
export function randomBigIntBits(crypto: ICryptoProvider, bits: number): bigint {
|
||||||
let num = randomBigInt(crypto, Math.ceil(bits / 8))
|
let num = randomBigInt(crypto, Math.ceil(bits / 8))
|
||||||
|
|
||||||
const bitLength = bigIntBitLength(num)
|
const bitLength = bigint.bitLength(num)
|
||||||
|
|
||||||
if (bitLength > bits) {
|
if (bitLength > bits) {
|
||||||
const toTrim = bitLength - bits
|
const toTrim = bitLength - bits
|
||||||
|
@ -117,115 +37,10 @@ export function randomBigIntInRange(crypto: ICryptoProvider, max: bigint, min =
|
||||||
const interval = max - min
|
const interval = max - min
|
||||||
if (interval < 0n) throw new Error('expected min < max')
|
if (interval < 0n) throw new Error('expected min < max')
|
||||||
|
|
||||||
const byteSize = Math.ceil(bigIntBitLength(interval) / 8)
|
const byteSize = Math.ceil(bigint.bitLength(interval) / 8)
|
||||||
|
|
||||||
let result = randomBigInt(crypto, byteSize)
|
let result = randomBigInt(crypto, byteSize)
|
||||||
while (result > interval) result -= interval
|
while (result > interval) result -= interval
|
||||||
|
|
||||||
return min + result
|
return min + result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the multiplicity of 2 in the prime factorization of n
|
|
||||||
* @param n
|
|
||||||
*/
|
|
||||||
export function twoMultiplicity(n: bigint): bigint {
|
|
||||||
if (n === 0n) return 0n
|
|
||||||
|
|
||||||
let m = 0n
|
|
||||||
let pow = 1n
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if ((n & pow) !== 0n) return m
|
|
||||||
m += 1n
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
||||||
|
|
||||||
import { bufferToReversed, buffersEqual, cloneBuffer, concatBuffers } from './buffer-utils.js'
|
|
||||||
|
|
||||||
describe('buffersEqual', () => {
|
|
||||||
it('should return true for equal buffers', () => {
|
|
||||||
expect(buffersEqual(new Uint8Array([]), new Uint8Array([]))).toEqual(true)
|
|
||||||
expect(buffersEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3]))).toEqual(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return false for non-equal buffers', () => {
|
|
||||||
expect(buffersEqual(new Uint8Array([1]), new Uint8Array([]))).toEqual(false)
|
|
||||||
expect(buffersEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 4]))).toEqual(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('cloneBuffer', () => {
|
|
||||||
it('should clone buffer', () => {
|
|
||||||
const orig = new Uint8Array([1, 2, 3])
|
|
||||||
const copy = cloneBuffer(orig)
|
|
||||||
|
|
||||||
expect([...copy]).eql([1, 2, 3])
|
|
||||||
orig[0] = 0xFF
|
|
||||||
expect(copy[0]).not.eql(0xFF)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should clone buffer partially', () => {
|
|
||||||
const orig = new Uint8Array([1, 2, 3, 4, 5])
|
|
||||||
const copy = cloneBuffer(orig, 1, 4)
|
|
||||||
|
|
||||||
expect([...copy]).eql([2, 3, 4])
|
|
||||||
orig[0] = 0xFF
|
|
||||||
expect(copy[0]).not.eql(0xFF)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('concatBuffers', () => {
|
|
||||||
it('should concat buffers', () => {
|
|
||||||
const buf = concatBuffers([new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])])
|
|
||||||
|
|
||||||
expect([...buf]).eql([1, 2, 3, 4, 5, 6])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create a new buffer', () => {
|
|
||||||
const buf1 = new Uint8Array([1, 2, 3])
|
|
||||||
const buf2 = new Uint8Array([4, 5, 6])
|
|
||||||
const buf = concatBuffers([buf1, buf2])
|
|
||||||
|
|
||||||
buf[0] = 0xFF
|
|
||||||
expect(buf1[0]).not.eql(0xFF)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should work without native Buffer', () => {
|
|
||||||
vi.stubGlobal('Buffer', undefined)
|
|
||||||
const buf1 = new Uint8Array([1, 2, 3])
|
|
||||||
const buf2 = new Uint8Array([4, 5, 6])
|
|
||||||
const buf = concatBuffers([buf1, buf2])
|
|
||||||
|
|
||||||
buf1[0] = 0xFF
|
|
||||||
|
|
||||||
expect([...buf]).eql([1, 2, 3, 4, 5, 6])
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => void vi.unstubAllGlobals())
|
|
||||||
})
|
|
||||||
|
|
||||||
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,79 +0,0 @@
|
||||||
/**
|
|
||||||
* Check if two buffers are equal
|
|
||||||
*
|
|
||||||
* @param a First buffer
|
|
||||||
* @param b Second buffer
|
|
||||||
*/
|
|
||||||
export function buffersEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
||||||
if (a.length !== b.length) return false
|
|
||||||
|
|
||||||
for (let i = 0; i < a.length; i++) {
|
|
||||||
if (a[i] !== b[i]) return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy a buffer
|
|
||||||
*
|
|
||||||
* @param buf Buffer to copy
|
|
||||||
* @param start Start offset
|
|
||||||
* @param end End offset
|
|
||||||
*/
|
|
||||||
export function cloneBuffer(buf: Uint8Array, start = 0, end: number = buf.length): Uint8Array {
|
|
||||||
const ret = new Uint8Array(end - start)
|
|
||||||
ret.set(buf.subarray(start, end))
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Concatenate multiple buffers into one
|
|
||||||
*/
|
|
||||||
export function concatBuffers(buffers: Uint8Array[]): Uint8Array {
|
|
||||||
if (buffers.length === 1) return buffers[0]
|
|
||||||
|
|
||||||
/* eslint-disable no-restricted-globals */
|
|
||||||
if (typeof Buffer !== 'undefined') {
|
|
||||||
return Buffer.concat(buffers)
|
|
||||||
}
|
|
||||||
/* eslint-enable no-restricted-globals */
|
|
||||||
|
|
||||||
let length = 0
|
|
||||||
|
|
||||||
for (const buf of buffers) {
|
|
||||||
length += buf.length
|
|
||||||
}
|
|
||||||
|
|
||||||
const ret = new Uint8Array(length)
|
|
||||||
let offset = 0
|
|
||||||
|
|
||||||
for (const buf of buffers) {
|
|
||||||
ret.set(buf, offset)
|
|
||||||
offset += buf.length
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for creating a DataView from a Uint8Array
|
|
||||||
*/
|
|
||||||
export function dataViewFromBuffer(buf: Uint8Array): DataView {
|
|
||||||
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: number = 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,38 +0,0 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
|
||||||
|
|
||||||
import { ConditionVariable } from './condition-variable.js'
|
|
||||||
|
|
||||||
describe('ConditionVariable', () => {
|
|
||||||
it('should correctly unlock execution', async () => {
|
|
||||||
const cv = new ConditionVariable()
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
setTimeout(() => cv.notify(), 10)
|
|
||||||
|
|
||||||
await cv.wait()
|
|
||||||
|
|
||||||
expect(true).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should correctly time out', async () => {
|
|
||||||
const cv = new ConditionVariable()
|
|
||||||
|
|
||||||
await cv.wait(10)
|
|
||||||
|
|
||||||
expect(true).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should only unlock once', async () => {
|
|
||||||
const cv = new ConditionVariable()
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
setTimeout(() => {
|
|
||||||
cv.notify()
|
|
||||||
cv.notify()
|
|
||||||
}, 10)
|
|
||||||
|
|
||||||
await cv.wait()
|
|
||||||
|
|
||||||
expect(true).toBeTruthy()
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,30 +0,0 @@
|
||||||
import * as timers from './timers.js'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class implementing a condition variable like behaviour.
|
|
||||||
*/
|
|
||||||
export class ConditionVariable {
|
|
||||||
private _notify?: () => void
|
|
||||||
private _timeout?: timers.Timer
|
|
||||||
|
|
||||||
wait(timeout?: number): Promise<void> {
|
|
||||||
const prom = new Promise<void>((resolve) => {
|
|
||||||
this._notify = resolve
|
|
||||||
})
|
|
||||||
|
|
||||||
if (timeout) {
|
|
||||||
this._timeout = timers.setTimeout(() => {
|
|
||||||
this._notify?.()
|
|
||||||
this._timeout = undefined
|
|
||||||
}, timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
return prom
|
|
||||||
}
|
|
||||||
|
|
||||||
notify(): void {
|
|
||||||
this._notify?.()
|
|
||||||
if (this._timeout) timers.clearTimeout(this._timeout)
|
|
||||||
this._notify = undefined
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
|
||||||
|
|
||||||
import { createControllablePromise } from './controllable-promise.js'
|
|
||||||
|
|
||||||
describe('createControllablePromise', () => {
|
|
||||||
it('should resolve', async () => {
|
|
||||||
const p = createControllablePromise()
|
|
||||||
p.resolve(1)
|
|
||||||
await expect(p).resolves.toBe(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should reject', async () => {
|
|
||||||
const p = createControllablePromise()
|
|
||||||
p.reject(1)
|
|
||||||
await expect(p).rejects.toBe(1)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,25 +0,0 @@
|
||||||
/**
|
|
||||||
* A promise that can be resolved or rejected from outside.
|
|
||||||
*/
|
|
||||||
export type ControllablePromise<T = unknown> = Promise<T> & {
|
|
||||||
resolve: (val: T) => void
|
|
||||||
reject: (err?: unknown) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a promise that can be resolved or rejected from outside.
|
|
||||||
*/
|
|
||||||
export function createControllablePromise<T = unknown>(): ControllablePromise<T> {
|
|
||||||
let _resolve: ControllablePromise<T>['resolve']
|
|
||||||
let _reject: ControllablePromise<T>['reject']
|
|
||||||
const promise = new Promise<T>((resolve, reject) => {
|
|
||||||
_resolve = resolve
|
|
||||||
_reject = reject
|
|
||||||
})
|
|
||||||
// ts doesn't like this, but it's fine
|
|
||||||
|
|
||||||
;(promise as ControllablePromise<T>).resolve = _resolve!
|
|
||||||
;(promise as ControllablePromise<T>).reject = _reject!
|
|
||||||
|
|
||||||
return promise as ControllablePromise<T>
|
|
||||||
}
|
|
|
@ -1,15 +1,14 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { defaultCryptoProvider } from '@mtcute/test'
|
import { defaultCryptoProvider } from '@mtcute/test'
|
||||||
|
import { bigint } from '@fuman/utils'
|
||||||
import { bigIntToBuffer, bufferToBigInt } from '../bigint-utils.js'
|
|
||||||
|
|
||||||
import { factorizePQSync } from './factorization.js'
|
import { factorizePQSync } from './factorization.js'
|
||||||
|
|
||||||
describe('prime factorization', () => {
|
describe('prime factorization', () => {
|
||||||
const testFactorization = (pq: bigint, p: bigint, q: bigint) => {
|
const testFactorization = (pq: bigint, p: bigint, q: bigint) => {
|
||||||
const [p_, q_] = factorizePQSync(defaultCryptoProvider, bigIntToBuffer(pq))
|
const [p_, q_] = factorizePQSync(defaultCryptoProvider, bigint.toBytes(pq))
|
||||||
expect(bufferToBigInt(p_)).toBe(p)
|
expect(bigint.fromBytes(p_)).toBe(p)
|
||||||
expect(bufferToBigInt(q_)).toBe(q)
|
expect(bigint.fromBytes(q_)).toBe(q)
|
||||||
}
|
}
|
||||||
|
|
||||||
it('should factorize', () => {
|
it('should factorize', () => {
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
import {
|
import { bigint } from '@fuman/utils'
|
||||||
bigIntAbs,
|
|
||||||
bigIntGcd,
|
import { randomBigIntInRange } from '../bigint-utils.js'
|
||||||
bigIntMin,
|
|
||||||
bigIntToBuffer,
|
|
||||||
bufferToBigInt,
|
|
||||||
randomBigIntInRange,
|
|
||||||
} from '../bigint-utils.js'
|
|
||||||
|
|
||||||
import type { ICryptoProvider } from './abstract.js'
|
import type { ICryptoProvider } from './abstract.js'
|
||||||
|
|
||||||
|
@ -14,7 +9,7 @@ import type { ICryptoProvider } from './abstract.js'
|
||||||
* @param pq
|
* @param pq
|
||||||
*/
|
*/
|
||||||
export function factorizePQSync(crypto: ICryptoProvider, pq: Uint8Array): [Uint8Array, Uint8Array] {
|
export function factorizePQSync(crypto: ICryptoProvider, pq: Uint8Array): [Uint8Array, Uint8Array] {
|
||||||
const pq_ = bufferToBigInt(pq)
|
const pq_ = bigint.fromBytes(pq)
|
||||||
|
|
||||||
const n = PollardRhoBrent(crypto, pq_)
|
const n = PollardRhoBrent(crypto, pq_)
|
||||||
const m = pq_ / n
|
const m = pq_ / n
|
||||||
|
@ -30,7 +25,7 @@ export function factorizePQSync(crypto: ICryptoProvider, pq: Uint8Array): [Uint8
|
||||||
q = n
|
q = n
|
||||||
}
|
}
|
||||||
|
|
||||||
return [bigIntToBuffer(p), bigIntToBuffer(q)]
|
return [bigint.toBytes(p), bigint.toBytes(q)]
|
||||||
}
|
}
|
||||||
|
|
||||||
function PollardRhoBrent(crypto: ICryptoProvider, n: bigint): bigint {
|
function PollardRhoBrent(crypto: ICryptoProvider, n: bigint): bigint {
|
||||||
|
@ -55,12 +50,12 @@ function PollardRhoBrent(crypto: ICryptoProvider, n: bigint): bigint {
|
||||||
while (k < r && g === 1n) {
|
while (k < r && g === 1n) {
|
||||||
ys = y
|
ys = y
|
||||||
|
|
||||||
for (let i = 0n; i < bigIntMin(m, r - k); i++) {
|
for (let i = 0n; i < bigint.min2(m, r - k); i++) {
|
||||||
y = (((y * y) % n) + c) % n
|
y = (((y * y) % n) + c) % n
|
||||||
q = (q * bigIntAbs(x - y)) % n
|
q = (q * bigint.abs(x - y)) % n
|
||||||
}
|
}
|
||||||
|
|
||||||
g = bigIntGcd(q, n)
|
g = bigint.euclideanGcd(q, n)
|
||||||
k = k + m
|
k = k + m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +66,7 @@ function PollardRhoBrent(crypto: ICryptoProvider, n: bigint): bigint {
|
||||||
do {
|
do {
|
||||||
ys = (((ys! * ys!) % n) + c) % n
|
ys = (((ys! * ys!) % n) + c) % n
|
||||||
|
|
||||||
g = bigIntGcd(x! - ys!, n)
|
g = bigint.euclideanGcd(x! - ys!, n)
|
||||||
} while (g <= 1n)
|
} while (g <= 1n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,3 @@ export * from './keys.js'
|
||||||
export * from './miller-rabin.js'
|
export * from './miller-rabin.js'
|
||||||
export * from './mtproto.js'
|
export * from './mtproto.js'
|
||||||
export * from './password.js'
|
export * from './password.js'
|
||||||
export * from './utils.js'
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import { bigIntBitLength, bigIntModPow, randomBigIntBits, twoMultiplicity } from '../bigint-utils.js'
|
import { bigint } from '@fuman/utils'
|
||||||
|
|
||||||
|
import { randomBigIntBits } from '../bigint-utils.js'
|
||||||
|
|
||||||
import type { ICryptoProvider } from './abstract.js'
|
import type { ICryptoProvider } from './abstract.js'
|
||||||
|
|
||||||
|
@ -7,10 +9,10 @@ export function millerRabin(crypto: ICryptoProvider, n: bigint, rounds = 20): bo
|
||||||
if (n < 4n) return n > 1n
|
if (n < 4n) return n > 1n
|
||||||
if (n % 2n === 0n || n < 0n) return false
|
if (n % 2n === 0n || n < 0n) return false
|
||||||
|
|
||||||
const nBits = bigIntBitLength(n)
|
const nBits = bigint.bitLength(n)
|
||||||
const nSub = n - 1n
|
const nSub = n - 1n
|
||||||
|
|
||||||
const r = twoMultiplicity(nSub)
|
const r = bigint.twoMultiplicity(nSub)
|
||||||
const d = nSub >> r
|
const d = nSub >> r
|
||||||
|
|
||||||
for (let i = 0; i < rounds; i++) {
|
for (let i = 0; i < rounds; i++) {
|
||||||
|
@ -20,16 +22,14 @@ export function millerRabin(crypto: ICryptoProvider, n: bigint, rounds = 20): bo
|
||||||
base = randomBigIntBits(crypto, nBits)
|
base = randomBigIntBits(crypto, nBits)
|
||||||
} while (base <= 1n || base >= nSub)
|
} while (base <= 1n || base >= nSub)
|
||||||
|
|
||||||
let x = bigIntModPow(base, d, n)
|
let x = bigint.modPowBinary(base, d, n)
|
||||||
// if (x.eq(bigInt.one) || x.eq(nSub)) continue
|
|
||||||
if (x === 1n || x === nSub) continue
|
if (x === 1n || x === nSub) continue
|
||||||
|
|
||||||
let i = 0n
|
let i = 0n
|
||||||
let y: bigint
|
let y: bigint
|
||||||
|
|
||||||
while (i < r) {
|
while (i < r) {
|
||||||
// y = x.modPow(bigInt[2], n)
|
y = bigint.modPowBinary(x, 2n, n)
|
||||||
y = bigIntModPow(x, 2n, n)
|
|
||||||
|
|
||||||
if (x === 1n) return false
|
if (x === 1n) return false
|
||||||
if (x === nSub) break
|
if (x === nSub) break
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { defaultTestCryptoProvider } from '@mtcute/test'
|
import { defaultTestCryptoProvider } from '@mtcute/test'
|
||||||
import { hex } from '@fuman/utils'
|
import { hex, u8 } from '@fuman/utils'
|
||||||
|
|
||||||
import { concatBuffers } from '../index.js'
|
|
||||||
|
|
||||||
import { createAesIgeForMessage, createAesIgeForMessageOld, generateKeyAndIvFromNonce } from './mtproto.js'
|
import { createAesIgeForMessage, createAesIgeForMessageOld, generateKeyAndIvFromNonce } from './mtproto.js'
|
||||||
|
|
||||||
const authKeyChunk = hex.decode('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0')
|
const authKeyChunk = hex.decode('98cb29c6ffa89e79da695a54f572e6cb101e81c688b63a4bf73c3622dec230e0')
|
||||||
const authKey = concatBuffers(Array.from({ length: 8 }, () => authKeyChunk))
|
const authKey = u8.concat(Array.from({ length: 8 }, () => authKeyChunk))
|
||||||
const messageKey = hex.decode('25d701f2a29205526757825a99eb2d32')
|
const messageKey = hex.decode('25d701f2a29205526757825a99eb2d32')
|
||||||
|
|
||||||
describe('mtproto 2.0', async () => {
|
describe('mtproto 2.0', async () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { concatBuffers } from '../buffer-utils.js'
|
import { u8 } from '@fuman/utils'
|
||||||
|
|
||||||
import type { ICryptoProvider, IEncryptionScheme } from './abstract.js'
|
import type { ICryptoProvider, IEncryptionScheme } from './abstract.js'
|
||||||
|
|
||||||
|
@ -16,12 +16,12 @@ export function generateKeyAndIvFromNonce(
|
||||||
serverNonce: Uint8Array,
|
serverNonce: Uint8Array,
|
||||||
newNonce: Uint8Array,
|
newNonce: Uint8Array,
|
||||||
): [Uint8Array, Uint8Array] {
|
): [Uint8Array, Uint8Array] {
|
||||||
const hash1 = crypto.sha1(concatBuffers([newNonce, serverNonce]))
|
const hash1 = crypto.sha1(u8.concat2(newNonce, serverNonce))
|
||||||
const hash2 = crypto.sha1(concatBuffers([serverNonce, newNonce]))
|
const hash2 = crypto.sha1(u8.concat2(serverNonce, newNonce))
|
||||||
const hash3 = crypto.sha1(concatBuffers([newNonce, newNonce]))
|
const hash3 = crypto.sha1(u8.concat2(newNonce, newNonce))
|
||||||
|
|
||||||
const key = concatBuffers([hash1, hash2.subarray(0, 12)])
|
const key = u8.concat2(hash1, hash2.subarray(0, 12))
|
||||||
const iv = concatBuffers([hash2.subarray(12, 20), hash3, newNonce.subarray(0, 4)])
|
const iv = u8.concat3(hash2.subarray(12, 20), hash3, newNonce.subarray(0, 4))
|
||||||
|
|
||||||
return [key, iv]
|
return [key, iv]
|
||||||
}
|
}
|
||||||
|
@ -42,11 +42,11 @@ export function createAesIgeForMessage(
|
||||||
client: boolean,
|
client: boolean,
|
||||||
): IEncryptionScheme {
|
): IEncryptionScheme {
|
||||||
const x = client ? 0 : 8
|
const x = client ? 0 : 8
|
||||||
const sha256a = crypto.sha256(concatBuffers([messageKey, authKey.subarray(x, 36 + x)]))
|
const sha256a = crypto.sha256(u8.concat2(messageKey, authKey.subarray(x, 36 + x)))
|
||||||
const sha256b = crypto.sha256(concatBuffers([authKey.subarray(40 + x, 76 + x), messageKey]))
|
const sha256b = crypto.sha256(u8.concat2(authKey.subarray(40 + x, 76 + x), messageKey))
|
||||||
|
|
||||||
const key = concatBuffers([sha256a.subarray(0, 8), sha256b.subarray(8, 24), sha256a.subarray(24, 32)])
|
const key = u8.concat3(sha256a.subarray(0, 8), sha256b.subarray(8, 24), sha256a.subarray(24, 32))
|
||||||
const iv = concatBuffers([sha256b.subarray(0, 8), sha256a.subarray(8, 24), sha256b.subarray(24, 32)])
|
const iv = u8.concat3(sha256b.subarray(0, 8), sha256a.subarray(8, 24), sha256b.subarray(24, 32))
|
||||||
|
|
||||||
return crypto.createAesIge(key, iv)
|
return crypto.createAesIge(key, iv)
|
||||||
}
|
}
|
||||||
|
@ -67,15 +67,15 @@ export function createAesIgeForMessageOld(
|
||||||
client: boolean,
|
client: boolean,
|
||||||
): IEncryptionScheme {
|
): IEncryptionScheme {
|
||||||
const x = client ? 0 : 8
|
const x = client ? 0 : 8
|
||||||
const sha1a = crypto.sha1(concatBuffers([messageKey, authKey.subarray(x, 32 + x)]))
|
const sha1a = crypto.sha1(u8.concat2(messageKey, authKey.subarray(x, 32 + x)))
|
||||||
const sha1b = crypto.sha1(
|
const sha1b = crypto.sha1(
|
||||||
concatBuffers([authKey.subarray(32 + x, 48 + x), messageKey, authKey.subarray(48 + x, 64 + x)]),
|
u8.concat3(authKey.subarray(32 + x, 48 + x), messageKey, authKey.subarray(48 + x, 64 + x)),
|
||||||
)
|
)
|
||||||
const sha1c = crypto.sha1(concatBuffers([authKey.subarray(64 + x, 96 + x), messageKey]))
|
const sha1c = crypto.sha1(u8.concat2(authKey.subarray(64 + x, 96 + x), messageKey))
|
||||||
const sha1d = crypto.sha1(concatBuffers([messageKey, authKey.subarray(96 + x, 128 + x)]))
|
const sha1d = crypto.sha1(u8.concat2(messageKey, authKey.subarray(96 + x, 128 + x)))
|
||||||
|
|
||||||
const key = concatBuffers([sha1a.subarray(0, 8), sha1b.subarray(8, 20), sha1c.subarray(4, 16)])
|
const key = u8.concat3(sha1a.subarray(0, 8), sha1b.subarray(8, 20), sha1c.subarray(4, 16))
|
||||||
const iv = concatBuffers([
|
const iv = u8.concat([
|
||||||
sha1a.subarray(8, 20),
|
sha1a.subarray(8, 20),
|
||||||
sha1b.subarray(0, 8),
|
sha1b.subarray(0, 8),
|
||||||
sha1c.subarray(16, 20),
|
sha1c.subarray(16, 20),
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import type { tl } from '@mtcute/tl'
|
import type { tl } from '@mtcute/tl'
|
||||||
import { utf8 } from '@fuman/utils'
|
import { bigint, u8, utf8 } from '@fuman/utils'
|
||||||
|
|
||||||
import { MtSecurityError, MtUnsupportedError } from '../../types/errors.js'
|
import { MtSecurityError, MtUnsupportedError } from '../../types/errors.js'
|
||||||
import { bigIntModPow, bigIntToBuffer, bufferToBigInt } from '../bigint-utils.js'
|
|
||||||
import { concatBuffers } from '../buffer-utils.js'
|
|
||||||
import { assertTypeIs } from '../type-assertions.js'
|
import { assertTypeIs } from '../type-assertions.js'
|
||||||
|
|
||||||
import type { ICryptoProvider } from './abstract.js'
|
import type { ICryptoProvider } from './abstract.js'
|
||||||
import { xorBuffer } from './utils.js'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute password hash as defined by MTProto.
|
* Compute password hash as defined by MTProto.
|
||||||
|
@ -25,7 +22,7 @@ export async function computePasswordHash(
|
||||||
salt1: Uint8Array,
|
salt1: Uint8Array,
|
||||||
salt2: Uint8Array,
|
salt2: Uint8Array,
|
||||||
): Promise<Uint8Array> {
|
): Promise<Uint8Array> {
|
||||||
const SH = (data: Uint8Array, salt: Uint8Array) => crypto.sha256(concatBuffers([salt, data, salt]))
|
const SH = (data: Uint8Array, salt: Uint8Array) => crypto.sha256(u8.concat3(salt, data, salt))
|
||||||
const PH1 = (pwd: Uint8Array, salt1: Uint8Array, salt2: Uint8Array) => SH(SH(pwd, salt1), salt2)
|
const PH1 = (pwd: Uint8Array, salt1: Uint8Array, salt2: Uint8Array) => SH(SH(pwd, salt1), salt2)
|
||||||
|
|
||||||
return SH(await crypto.pbkdf2(PH1(password, salt1, salt2), salt1, 100000), salt2)
|
return SH(await crypto.pbkdf2(PH1(password, salt1, salt2), salt1, 100000), salt2)
|
||||||
|
@ -53,10 +50,10 @@ export async function computeNewPasswordHash(
|
||||||
const _x = await computePasswordHash(crypto, utf8.encoder.encode(password), algo.salt1, algo.salt2)
|
const _x = await computePasswordHash(crypto, utf8.encoder.encode(password), algo.salt1, algo.salt2)
|
||||||
|
|
||||||
const g = BigInt(algo.g)
|
const g = BigInt(algo.g)
|
||||||
const p = bufferToBigInt(algo.p)
|
const p = bigint.fromBytes(algo.p)
|
||||||
const x = bufferToBigInt(_x)
|
const x = bigint.fromBytes(_x)
|
||||||
|
|
||||||
return bigIntToBuffer(bigIntModPow(g, x, p), 256)
|
return bigint.toBytes(bigint.modPowBinary(g, x, p), 256)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,32 +89,39 @@ export async function computeSrpParams(
|
||||||
}
|
}
|
||||||
|
|
||||||
const g = BigInt(algo.g)
|
const g = BigInt(algo.g)
|
||||||
const _g = bigIntToBuffer(g, 256)
|
const _g = bigint.toBytes(g, 256)
|
||||||
const p = bufferToBigInt(algo.p)
|
const p = bigint.fromBytes(algo.p)
|
||||||
const gB = bufferToBigInt(request.srpB)
|
const gB = bigint.fromBytes(request.srpB)
|
||||||
|
|
||||||
const a = bufferToBigInt(crypto.randomBytes(256))
|
const a = bigint.fromBytes(crypto.randomBytes(256))
|
||||||
const gA = bigIntModPow(g, a, p)
|
const gA = bigint.modPowBinary(g, a, p)
|
||||||
const _gA = bigIntToBuffer(gA, 256)
|
const _gA = bigint.toBytes(gA, 256)
|
||||||
|
|
||||||
const H = (data: Uint8Array) => crypto.sha256(data)
|
const H = (data: Uint8Array) => crypto.sha256(data)
|
||||||
|
|
||||||
const _k = crypto.sha256(concatBuffers([algo.p, _g]))
|
const _k = crypto.sha256(u8.concat2(algo.p, _g))
|
||||||
const _u = crypto.sha256(concatBuffers([_gA, request.srpB]))
|
const _u = crypto.sha256(u8.concat2(_gA, request.srpB))
|
||||||
const _x = await computePasswordHash(crypto, utf8.encoder.encode(password), algo.salt1, algo.salt2)
|
const _x = await computePasswordHash(crypto, utf8.encoder.encode(password), algo.salt1, algo.salt2)
|
||||||
const k = bufferToBigInt(_k)
|
const k = bigint.fromBytes(_k)
|
||||||
const u = bufferToBigInt(_u)
|
const u = bigint.fromBytes(_u)
|
||||||
const x = bufferToBigInt(_x)
|
const x = bigint.fromBytes(_x)
|
||||||
|
|
||||||
const v = bigIntModPow(g, x, p)
|
const v = bigint.modPowBinary(g, x, p)
|
||||||
const kV = (k * v) % p
|
const kV = (k * v) % p
|
||||||
|
|
||||||
let t = gB - kV
|
let t = gB - kV
|
||||||
if (t < 0n) t += p
|
if (t < 0n) t += p
|
||||||
const sA = bigIntModPow(t, a + u * x, p)
|
const sA = bigint.modPowBinary(t, a + u * x, p)
|
||||||
const _kA = H(bigIntToBuffer(sA, 256))
|
const _kA = H(bigint.toBytes(sA, 256))
|
||||||
|
|
||||||
const _M1 = H(concatBuffers([xorBuffer(H(algo.p), H(_g)), H(algo.salt1), H(algo.salt2), _gA, request.srpB, _kA]))
|
const _M1 = H(u8.concat([
|
||||||
|
u8.xor(H(algo.p), H(_g)),
|
||||||
|
H(algo.salt1),
|
||||||
|
H(algo.salt2),
|
||||||
|
_gA,
|
||||||
|
request.srpB,
|
||||||
|
_kA,
|
||||||
|
]))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_: 'inputCheckPasswordSRP',
|
_: 'inputCheckPasswordSRP',
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
|
||||||
import { hex, utf8 } from '@fuman/utils'
|
|
||||||
|
|
||||||
import { xorBuffer, xorBufferInPlace } from './utils.js'
|
|
||||||
|
|
||||||
describe('xorBuffer', () => {
|
|
||||||
it('should xor buffers without modifying original', () => {
|
|
||||||
const data = utf8.encoder.encode('hello')
|
|
||||||
const key = utf8.encoder.encode('xor')
|
|
||||||
|
|
||||||
const xored = xorBuffer(data, key)
|
|
||||||
expect(utf8.decoder.decode(data)).eq('hello')
|
|
||||||
expect(utf8.decoder.decode(key)).eq('xor')
|
|
||||||
expect(hex.encode(xored)).eq('100a1e6c6f')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should be deterministic', () => {
|
|
||||||
const data = utf8.encoder.encode('hello')
|
|
||||||
const key = utf8.encoder.encode('xor')
|
|
||||||
|
|
||||||
const xored1 = xorBuffer(data, key)
|
|
||||||
expect(hex.encode(xored1)).eq('100a1e6c6f')
|
|
||||||
|
|
||||||
const xored2 = xorBuffer(data, key)
|
|
||||||
expect(hex.encode(xored2)).eq('100a1e6c6f')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('second call should decode content', () => {
|
|
||||||
const data = utf8.encoder.encode('hello')
|
|
||||||
const key = utf8.encoder.encode('xor')
|
|
||||||
|
|
||||||
const xored1 = xorBuffer(data, key)
|
|
||||||
expect(hex.encode(xored1)).eq('100a1e6c6f')
|
|
||||||
|
|
||||||
const xored2 = xorBuffer(xored1, key)
|
|
||||||
expect(utf8.decoder.decode(xored2)).eq('hello')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('xorBufferInPlace', () => {
|
|
||||||
it('should xor buffers by modifying original', () => {
|
|
||||||
const data = utf8.encoder.encode('hello')
|
|
||||||
const key = utf8.encoder.encode('xor')
|
|
||||||
|
|
||||||
xorBufferInPlace(data, key)
|
|
||||||
expect(hex.encode(data)).eq('100a1e6c6f')
|
|
||||||
expect(utf8.decoder.decode(key)).eq('xor')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('second call should decode content', () => {
|
|
||||||
const data = utf8.encoder.encode('hello')
|
|
||||||
const key = utf8.encoder.encode('xor')
|
|
||||||
|
|
||||||
xorBufferInPlace(data, key)
|
|
||||||
expect(hex.encode(data)).eq('100a1e6c6f')
|
|
||||||
|
|
||||||
xorBufferInPlace(data, key)
|
|
||||||
expect(utf8.decoder.decode(data)).eq('hello')
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,27 +0,0 @@
|
||||||
/**
|
|
||||||
* Perform XOR operation on two buffers and return the new buffer
|
|
||||||
*
|
|
||||||
* @param data Buffer to XOR
|
|
||||||
* @param key Key to XOR with
|
|
||||||
*/
|
|
||||||
export function xorBuffer(data: Uint8Array, key: Uint8Array): Uint8Array {
|
|
||||||
const ret = new Uint8Array(data.length)
|
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
ret[i] = data[i] ^ key[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform XOR operation on two buffers in-place
|
|
||||||
*
|
|
||||||
* @param data Buffer to XOR
|
|
||||||
* @param key Key to XOR with
|
|
||||||
*/
|
|
||||||
export function xorBufferInPlace(data: Uint8Array, key: Uint8Array): void {
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
|
||||||
data[i] ^= key[i]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as timers from './timers.js'
|
import { timers } from '@fuman/utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper over JS timers that allows re-scheduling them
|
* Wrapper over JS timers that allows re-scheduling them
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { ConditionVariable } from '@fuman/utils'
|
||||||
|
|
||||||
import { ConditionVariable } from './condition-variable.js'
|
|
||||||
import { throttle } from './function-utils.js'
|
import { throttle } from './function-utils.js'
|
||||||
|
|
||||||
describe('throttle', () => {
|
describe('throttle', () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import * as timers from './timers.js'
|
import { timers } from '@fuman/utils'
|
||||||
|
|
||||||
export type ThrottledFunction = (() => void) & {
|
export type ThrottledFunction = (() => void) & {
|
||||||
reset: () => void
|
reset: () => void
|
||||||
|
|
|
@ -7,12 +7,8 @@ export * from '../highlevel/storage/service/updates.js'
|
||||||
export * from '../storage/service/base.js'
|
export * from '../storage/service/base.js'
|
||||||
export * from '../storage/service/default-dcs.js'
|
export * from '../storage/service/default-dcs.js'
|
||||||
// end todo
|
// end todo
|
||||||
export * from './async-lock.js'
|
|
||||||
export * from './bigint-utils.js'
|
export * from './bigint-utils.js'
|
||||||
export * from './buffer-utils.js'
|
|
||||||
export * from './composer.js'
|
export * from './composer.js'
|
||||||
export * from './condition-variable.js'
|
|
||||||
export * from './controllable-promise.js'
|
|
||||||
export * from './crypto/index.js'
|
export * from './crypto/index.js'
|
||||||
export * from './dcs.js'
|
export * from './dcs.js'
|
||||||
export * from './deque.js'
|
export * from './deque.js'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
|
import { typed } from '@fuman/utils'
|
||||||
|
|
||||||
import { dataViewFromBuffer } from './buffer-utils.js'
|
|
||||||
import { getRandomInt } from './misc-utils.js'
|
import { getRandomInt } from './misc-utils.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +23,7 @@ export function randomLong(unsigned = false): Long {
|
||||||
* @param le Whether the number is little-endian
|
* @param le Whether the number is little-endian
|
||||||
*/
|
*/
|
||||||
export function longFromBuffer(buf: Uint8Array, unsigned = false, le = true): Long {
|
export function longFromBuffer(buf: Uint8Array, unsigned = false, le = true): Long {
|
||||||
const dv = dataViewFromBuffer(buf)
|
const dv = typed.toDataView(buf)
|
||||||
|
|
||||||
if (le) {
|
if (le) {
|
||||||
return new Long(dv.getInt32(0, true), dv.getInt32(4, true), unsigned)
|
return new Long(dv.getInt32(0, true), dv.getInt32(4, true), unsigned)
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
import * as timers from './timers.js'
|
import { timers } from '@fuman/utils'
|
||||||
|
|
||||||
/**
|
|
||||||
* Sleep for the given number of ms
|
|
||||||
*
|
|
||||||
* @param ms Number of ms to sleep
|
|
||||||
*/
|
|
||||||
export const sleep = (ms: number): Promise<void> => new Promise(resolve => timers.setTimeout(resolve, ms))
|
|
||||||
|
|
||||||
export function sleepWithAbort(ms: number, signal: AbortSignal): Promise<void> {
|
export function sleepWithAbort(ms: number, signal: AbortSignal): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
import { timers } from '@fuman/utils'
|
||||||
|
|
||||||
import { asyncResettable } from './function-utils.js'
|
import { asyncResettable } from './function-utils.js'
|
||||||
import * as timers from './timers.js'
|
|
||||||
|
|
||||||
export interface ReloadableParams<Data> {
|
export interface ReloadableParams<Data> {
|
||||||
reload: (old?: Data) => Promise<Data>
|
reload: (old?: Data) => Promise<Data>
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mtcute/core": "workspace:^",
|
"@mtcute/core": "workspace:^",
|
||||||
|
"@fuman/utils": "workspace:^",
|
||||||
"events": "3.2.0"
|
"events": "3.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { MtArgumentError, MtcuteError } from '@mtcute/core'
|
import { MtArgumentError, MtcuteError } from '@mtcute/core'
|
||||||
import { sleep } from '@mtcute/core/utils.js'
|
import { sleep } from '@fuman/utils'
|
||||||
|
|
||||||
import type { Dispatcher } from '../dispatcher.js'
|
import type { Dispatcher } from '../dispatcher.js'
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,7 @@ import { gzipSync, inflateSync } from 'node:zlib'
|
||||||
import type { MockInstance } from 'vitest'
|
import type { MockInstance } from 'vitest'
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import type { ICryptoProvider } from '@mtcute/core/utils.js'
|
import type { ICryptoProvider } from '@mtcute/core/utils.js'
|
||||||
import { dataViewFromBuffer } from '@mtcute/core/utils.js'
|
import { hex, typed, utf8 } from '@fuman/utils'
|
||||||
import { hex, utf8 } from '@fuman/utils'
|
|
||||||
|
|
||||||
import { defaultCryptoProvider } from './platform.js'
|
import { defaultCryptoProvider } from './platform.js'
|
||||||
|
|
||||||
|
@ -53,7 +52,7 @@ export function withFakeRandom(provider: ICryptoProvider, source: string = DEFAU
|
||||||
|
|
||||||
export function useFakeMathRandom(source: string = DEFAULT_ENTROPY): void {
|
export function useFakeMathRandom(source: string = DEFAULT_ENTROPY): void {
|
||||||
const sourceBytes = hex.decode(source)
|
const sourceBytes = hex.decode(source)
|
||||||
const dv = dataViewFromBuffer(sourceBytes)
|
const dv = typed.toDataView(sourceBytes)
|
||||||
|
|
||||||
let spy: MockInstance<() => number>
|
let spy: MockInstance<() => number>
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,9 @@ importers:
|
||||||
'@antfu/eslint-config':
|
'@antfu/eslint-config':
|
||||||
specifier: 2.26.0
|
specifier: 2.26.0
|
||||||
version: 2.26.0(@typescript-eslint/utils@8.1.0(eslint@9.9.0)(typescript@5.5.4))(@vue/compiler-sfc@3.4.37)(eslint@9.9.0)(typescript@5.5.4)(vitest@2.0.5(@types/node@20.10.0)(@vitest/browser@2.0.5)(@vitest/ui@2.0.5))
|
version: 2.26.0(@typescript-eslint/utils@8.1.0(eslint@9.9.0)(typescript@5.5.4))(@vue/compiler-sfc@3.4.37)(eslint@9.9.0)(typescript@5.5.4)(vitest@2.0.5(@types/node@20.10.0)(@vitest/browser@2.0.5)(@vitest/ui@2.0.5))
|
||||||
|
'@fuman/utils':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:private/fuman/packages/utils
|
||||||
'@teidesu/slow-types-compiler':
|
'@teidesu/slow-types-compiler':
|
||||||
specifier: 1.1.0
|
specifier: 1.1.0
|
||||||
version: 1.1.0(typescript@5.5.4)
|
version: 1.1.0(typescript@5.5.4)
|
||||||
|
@ -260,6 +263,9 @@ importers:
|
||||||
|
|
||||||
packages/dispatcher:
|
packages/dispatcher:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@fuman/utils':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../../private/fuman/packages/utils
|
||||||
'@mtcute/core':
|
'@mtcute/core':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../core
|
version: link:../core
|
||||||
|
|
Loading…
Reference in a new issue