From 18178b438deb430152067f10d51a5925a7e90034 Mon Sep 17 00:00:00 2001 From: Alina Sireneva Date: Tue, 7 Nov 2023 22:49:35 +0300 Subject: [PATCH] feat(wasm): added sha1/256 to wasm, removed most of async in crypto --- packages/core/src/network/auth-key.ts | 18 +- packages/core/src/network/authorization.ts | 26 +-- packages/core/src/network/mtproto-session.ts | 4 +- .../src/network/multi-session-connection.ts | 4 +- packages/core/src/network/network-manager.ts | 20 +- .../core/src/network/session-connection.ts | 36 ++-- .../core/src/network/transports/obfuscated.ts | 4 +- packages/core/src/utils/crypto/abstract.ts | 4 +- packages/core/src/utils/crypto/keys.ts | 8 +- packages/core/src/utils/crypto/mtproto.ts | 30 +-- packages/core/src/utils/crypto/node.ts | 1 - packages/core/src/utils/crypto/password.ts | 26 +-- packages/core/src/utils/crypto/wasm.ts | 69 +++++++ packages/core/src/utils/crypto/web.ts | 60 +----- packages/core/tests/auth-key.spec.ts | 4 +- packages/core/tests/crypto-providers.spec.ts | 16 +- packages/core/tests/keys.spec.ts | 4 +- packages/core/tests/mtproto-crypto.spec.ts | 20 +- packages/tl/scripts/gen-rsa-keys.ts | 2 +- packages/wasm/README.md | 9 +- packages/wasm/lib/Dockerfile | 5 +- packages/wasm/lib/Makefile | 49 ++--- packages/wasm/lib/crypto/aes256.c | 11 ++ packages/wasm/lib/crypto/aes256.h | 15 +- packages/wasm/lib/crypto/ctr256.c | 14 +- packages/wasm/lib/crypto/ctr256.h | 6 - packages/wasm/lib/crypto/ige256.c | 18 +- packages/wasm/lib/crypto/ige256.h | 17 -- packages/wasm/lib/hash/sha1.c | 179 ++++++++++++++++++ packages/wasm/lib/hash/sha256.c | 167 ++++++++++++++++ packages/wasm/lib/libdeflate/allocator.c | 21 ++ .../wasm/lib/{ => libdeflate}/lib_common.h | 23 +-- .../wasm/lib/{ => libdeflate}/libdeflate.h | 14 -- packages/wasm/lib/mtcute.wasm | Bin 46100 -> 48576 bytes packages/wasm/lib/utils.c | 137 -------------- packages/wasm/lib/utils/allocator.c | 74 ++++++++ packages/wasm/lib/{ => utils}/common_defs.h | 2 - packages/wasm/lib/wasm.h | 18 ++ packages/wasm/src/index.ts | 87 ++++++--- packages/wasm/src/types.ts | 14 +- packages/wasm/tests/hash.spec.ts | 49 +++++ 41 files changed, 813 insertions(+), 472 deletions(-) create mode 100644 packages/core/src/utils/crypto/wasm.ts delete mode 100644 packages/wasm/lib/crypto/ctr256.h delete mode 100644 packages/wasm/lib/crypto/ige256.h create mode 100644 packages/wasm/lib/hash/sha1.c create mode 100644 packages/wasm/lib/hash/sha256.c create mode 100644 packages/wasm/lib/libdeflate/allocator.c rename packages/wasm/lib/{ => libdeflate}/lib_common.h (77%) rename packages/wasm/lib/{ => libdeflate}/libdeflate.h (94%) delete mode 100644 packages/wasm/lib/utils.c create mode 100644 packages/wasm/lib/utils/allocator.c rename packages/wasm/lib/{ => utils}/common_defs.h (99%) create mode 100644 packages/wasm/lib/wasm.h create mode 100644 packages/wasm/tests/hash.spec.ts diff --git a/packages/core/src/network/auth-key.ts b/packages/core/src/network/auth-key.ts index af6ec415..4a0162dd 100644 --- a/packages/core/src/network/auth-key.ts +++ b/packages/core/src/network/auth-key.ts @@ -32,19 +32,19 @@ export class AuthKey { return this.ready && buffersEqual(keyId, this.id) } - async setup(authKey?: Uint8Array | null): Promise { + setup(authKey?: Uint8Array | null): void { if (!authKey) return this.reset() this.ready = true this.key = authKey this.clientSalt = authKey.subarray(88, 120) this.serverSalt = authKey.subarray(96, 128) - this.id = (await this._crypto.sha1(authKey)).subarray(-8) + this.id = this._crypto.sha1(authKey).subarray(-8) this.log.verbose('auth key set up, id = %h', this.id) } - async encryptMessage(message: Uint8Array, serverSalt: Long, sessionId: Long): Promise { + encryptMessage(message: Uint8Array, serverSalt: Long, sessionId: Long): Uint8Array { if (!this.ready) throw new MtcuteError('Keys are not set up!') let padding = (16 /* header size */ + message.length + 12) /* min padding */ % 16 @@ -60,18 +60,18 @@ export class AuthKey { buf.set(message, 16) buf.set(randomBytes(padding), 16 + message.length) - const messageKey = (await this._crypto.sha256(concatBuffers([this.clientSalt, buf]))).subarray(8, 24) - const ige = await createAesIgeForMessage(this._crypto, this.key, messageKey, true) + const messageKey = this._crypto.sha256(concatBuffers([this.clientSalt, buf])).subarray(8, 24) + const ige = createAesIgeForMessage(this._crypto, this.key, messageKey, true) const encryptedData = ige.encrypt(buf) return concatBuffers([this.id, messageKey, encryptedData]) } - async decryptMessage( + decryptMessage( data: Uint8Array, sessionId: Long, callback: (msgId: tl.Long, seqNo: number, data: TlBinaryReader) => void, - ): Promise { + ): void { const messageKey = data.subarray(8, 24) let encryptedData = data.subarray(24) @@ -84,10 +84,10 @@ export class AuthKey { encryptedData = encryptedData.subarray(0, encryptedData.byteLength - mod16) } - const ige = await createAesIgeForMessage(this._crypto, this.key, messageKey, false) + const ige = createAesIgeForMessage(this._crypto, this.key, messageKey, false) const innerData = ige.decrypt(encryptedData) - const msgKeySource = await this._crypto.sha256(concatBuffers([this.serverSalt, innerData])) + const msgKeySource = this._crypto.sha256(concatBuffers([this.serverSalt, innerData])) const expectedMessageKey = msgKeySource.subarray(8, 24) if (!buffersEqual(messageKey, expectedMessageKey)) { diff --git a/packages/core/src/network/authorization.ts b/packages/core/src/network/authorization.ts index 6ef3d27e..e33d5530 100644 --- a/packages/core/src/network/authorization.ts +++ b/packages/core/src/network/authorization.ts @@ -120,7 +120,7 @@ function checkDhPrime(log: Logger, dhPrime: bigint, g: number) { log.debug('g = %d is safe to use with dh_prime', g) } -async function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Promise { +function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Uint8Array { // since Summer 2021, they use "version of RSA with a variant of OAEP+ padding explained below" const keyModulus = BigInt(`0x${key.modulus}`) @@ -137,13 +137,13 @@ async function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKe const aesKey = randomBytes(32) - const dataWithHash = concatBuffers([data, await crypto.sha256(concatBuffers([aesKey, data]))]) + const dataWithHash = concatBuffers([data, crypto.sha256(concatBuffers([aesKey, data]))]) // we only need to reverse the data dataWithHash.subarray(0, 192).reverse() const aes = crypto.createAesIge(aesKey, aesIv) const encrypted = aes.encrypt(dataWithHash) - const encryptedHash = await crypto.sha256(encrypted) + const encryptedHash = crypto.sha256(encrypted) xorBufferInPlace(aesKey, encryptedHash) const decryptedData = concatBuffers([aesKey, encrypted]) @@ -160,9 +160,9 @@ async function rsaPad(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKe } } -async function rsaEncrypt(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Promise { +function rsaEncrypt(data: Uint8Array, crypto: ICryptoProvider, key: TlPublicKey): Uint8Array { const toEncrypt = concatBuffers([ - await crypto.sha1(data), + crypto.sha1(data), data, // sha1 is always 20 bytes, so we're left with 255 - 20 - x padding randomBytes(235 - data.length), @@ -271,8 +271,8 @@ export async function doAuthorization( const pqInnerData = TlBinaryWriter.serializeObject(writerMap, _pqInnerData) const encryptedData = publicKey.old ? - await rsaEncrypt(pqInnerData, crypto, publicKey) : - await rsaPad(pqInnerData, crypto, publicKey) + rsaEncrypt(pqInnerData, crypto, publicKey) : + rsaPad(pqInnerData, crypto, publicKey) log.debug('requesting DH params') @@ -303,7 +303,7 @@ export async function doAuthorization( } // Step 3: complete DH exchange - const [key, iv] = await generateKeyAndIvFromNonce(crypto, resPq.serverNonce, newNonce) + const [key, iv] = generateKeyAndIvFromNonce(crypto, resPq.serverNonce, newNonce) const ige = crypto.createAesIge(key, iv) const plainTextAnswer = ige.decrypt(serverDhParams.encryptedAnswer) @@ -311,7 +311,7 @@ export async function doAuthorization( const serverDhInnerReader = new TlBinaryReader(readerMap, plainTextAnswer, 20) const serverDhInner = serverDhInnerReader.object() as mtp.TlObject - if (!buffersEqual(innerDataHash, await crypto.sha1(plainTextAnswer.subarray(20, serverDhInnerReader.pos)))) { + if (!buffersEqual(innerDataHash, crypto.sha1(plainTextAnswer.subarray(20, serverDhInnerReader.pos)))) { throw new MtSecurityError('Step 3: invalid inner data hash') } @@ -340,7 +340,7 @@ export async function doAuthorization( const gB = bigIntModPow(g, b, dhPrime) const authKey = bigIntToBuffer(bigIntModPow(gA, b, dhPrime)) - const authKeyAuxHash = (await crypto.sha1(authKey)).subarray(0, 8) + const authKeyAuxHash = crypto.sha1(authKey).subarray(0, 8) // validate DH params if (g <= 1 || g >= dhPrime - 1n) { @@ -377,7 +377,7 @@ export async function doAuthorization( const clientDhInnerWriter = TlBinaryWriter.alloc(writerMap, innerLength) clientDhInnerWriter.pos = 20 clientDhInnerWriter.object(clientDhInner) - const clientDhInnerHash = await crypto.sha1(clientDhInnerWriter.uint8View.subarray(20, clientDhInnerWriter.pos)) + const clientDhInnerHash = crypto.sha1(clientDhInnerWriter.uint8View.subarray(20, clientDhInnerWriter.pos)) clientDhInnerWriter.pos = 0 clientDhInnerWriter.raw(clientDhInnerHash) @@ -412,7 +412,7 @@ export async function doAuthorization( } if (dhGen._ === 'mt_dh_gen_retry') { - const expectedHash = await crypto.sha1(concatBuffers([newNonce, new Uint8Array([2]), authKeyAuxHash])) + const expectedHash = crypto.sha1(concatBuffers([newNonce, new Uint8Array([2]), authKeyAuxHash])) if (!buffersEqual(expectedHash.subarray(4, 20), dhGen.newNonceHash2)) { throw Error('Step 4: invalid retry nonce hash from server') @@ -423,7 +423,7 @@ export async function doAuthorization( if (dhGen._ !== 'mt_dh_gen_ok') throw new Error() // unreachable - const expectedHash = await crypto.sha1(concatBuffers([newNonce, new Uint8Array([1]), authKeyAuxHash])) + const expectedHash = crypto.sha1(concatBuffers([newNonce, new Uint8Array([1]), authKeyAuxHash])) if (!buffersEqual(expectedHash.subarray(4, 20), dhGen.newNonceHash1)) { throw Error('Step 4: invalid nonce hash from server') diff --git a/packages/core/src/network/mtproto-session.ts b/packages/core/src/network/mtproto-session.ts index f4a0052f..317f4612 100644 --- a/packages/core/src/network/mtproto-session.ts +++ b/packages/core/src/network/mtproto-session.ts @@ -243,14 +243,14 @@ export class MtprotoSession { } /** Encrypt a single MTProto message using session's keys */ - async encryptMessage(message: Uint8Array): Promise { + encryptMessage(message: Uint8Array): Uint8Array { const key = this._authKeyTemp.ready ? this._authKeyTemp : this._authKey return key.encryptMessage(message, this.serverSalt, this._sessionId) } /** Decrypt a single MTProto message using session's keys */ - async decryptMessage(data: Uint8Array, callback: Parameters[2]): Promise { + decryptMessage(data: Uint8Array, callback: Parameters[2]): void { if (!this._authKey.ready) throw new MtcuteError('Keys are not set up!') const authKeyId = data.subarray(0, 8) diff --git a/packages/core/src/network/multi-session-connection.ts b/packages/core/src/network/multi-session-connection.ts index 4128a2a3..10772015 100644 --- a/packages/core/src/network/multi-session-connection.ts +++ b/packages/core/src/network/multi-session-connection.ts @@ -237,10 +237,10 @@ export class MultiSessionConnection extends EventEmitter { this.connect() } - async setAuthKey(authKey: Uint8Array | null, temp = false, idx = 0): Promise { + setAuthKey(authKey: Uint8Array | null, temp = false, idx = 0): void { const session = this._sessions[idx] const key = temp ? session._authKeyTemp : session._authKey - await key.setup(authKey) + key.setup(authKey) } resetAuthKeys(): void { diff --git a/packages/core/src/network/network-manager.ts b/packages/core/src/network/network-manager.ts index 14dce89e..40c2351d 100644 --- a/packages/core/src/network/network-manager.ts +++ b/packages/core/src/network/network-manager.ts @@ -330,12 +330,10 @@ export class DcConnectionManager { async loadKeys(): Promise { const permanent = await this.manager._storage.getAuthKeyFor(this.dcId) - await Promise.all([ - this.main.setAuthKey(permanent), - this.upload.setAuthKey(permanent), - this.download.setAuthKey(permanent), - this.downloadSmall.setAuthKey(permanent), - ]) + this.main.setAuthKey(permanent) + this.upload.setAuthKey(permanent) + this.download.setAuthKey(permanent) + this.downloadSmall.setAuthKey(permanent) if (!permanent) { return false @@ -345,14 +343,12 @@ export class DcConnectionManager { await Promise.all( this.main._sessions.map(async (_, i) => { const temp = await this.manager._storage.getAuthKeyFor(this.dcId, i) - await this.main.setAuthKey(temp, true, i) + this.main.setAuthKey(temp, true, i) if (i === 0) { - await Promise.all([ - this.upload.setAuthKey(temp, true), - this.download.setAuthKey(temp, true), - this.downloadSmall.setAuthKey(temp, true), - ]) + this.upload.setAuthKey(temp, true) + this.download.setAuthKey(temp, true) + this.downloadSmall.setAuthKey(temp, true) } }), ) diff --git a/packages/core/src/network/session-connection.ts b/packages/core/src/network/session-connection.ts index db8a4e36..053e352e 100644 --- a/packages/core/src/network/session-connection.ts +++ b/packages/core/src/network/session-connection.ts @@ -267,8 +267,8 @@ export class SessionConnection extends PersistentConnection { this.emit('auth-begin') doAuthorization(this, this._crypto) - .then(async ([authKey, serverSalt, timeOffset]) => { - await this._session._authKey.setup(authKey) + .then(([authKey, serverSalt, timeOffset]) => { + this._session._authKey.setup(authKey) this._session.serverSalt = serverSalt this._session._timeOffset = timeOffset @@ -322,7 +322,7 @@ export class SessionConnection extends PersistentConnection { } const tempKey = this._session._authKeyTempSecondary - await tempKey.setup(tempAuthKey) + tempKey.setup(tempAuthKey) const msgId = this._session.getMessageId() @@ -358,10 +358,10 @@ export class SessionConnection extends PersistentConnection { writer.raw(randomBytes(8)) const msgWithPadding = writer.result() - const hash = await this._crypto.sha1(msgWithoutPadding) + const hash = this._crypto.sha1(msgWithoutPadding) const msgKey = hash.subarray(4, 20) - const ige = await 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 encryptedMessage = concatBuffers([this._session._authKey.id, msgKey, encryptedData]) @@ -399,7 +399,7 @@ export class SessionConnection extends PersistentConnection { reqWriter.object(request) // we can now send it as is - const requestEncrypted = await tempKey.encryptMessage( + const requestEncrypted = tempKey.encryptMessage( reqWriter.result(), tempServerSalt, this._session._sessionId, @@ -476,7 +476,7 @@ export class SessionConnection extends PersistentConnection { return promise } - protected async onMessage(data: Uint8Array): Promise { + protected onMessage(data: Uint8Array): void { if (this._pendingWaitForUnencrypted.length) { const int32 = new Int32Array(data.buffer, data.byteOffset, 2) @@ -501,7 +501,7 @@ export class SessionConnection extends PersistentConnection { } try { - await this._session.decryptMessage(data, this._handleRawMessage) + this._session.decryptMessage(data, this._handleRawMessage) } catch (err) { this.log.error('failed to decrypt message: %s\ndata: %h', err, data) } @@ -1793,17 +1793,15 @@ export class SessionConnection extends PersistentConnection { rootMsgId, ) - this._session - .encryptMessage(result) - .then((enc) => this.send(enc)) - .catch((err: Error) => { - this.log.error('error while sending pending messages (root msg_id = %l): %s', rootMsgId, err.stack) + const enc = this._session.encryptMessage(result) + this.send(enc).catch((err: Error) => { + this.log.error('error while sending pending messages (root msg_id = %l): %s', rootMsgId, err.stack) - // put acks in the front so they are the first to be sent - if (ackMsgIds) { - this._session.queuedAcks.splice(0, 0, ...ackMsgIds) - } - this._onMessageFailed(rootMsgId!, 'unknown error') - }) + // put acks in the front so they are the first to be sent + if (ackMsgIds) { + this._session.queuedAcks.splice(0, 0, ...ackMsgIds) + } + this._onMessageFailed(rootMsgId!, 'unknown error') + }) } } diff --git a/packages/core/src/network/transports/obfuscated.ts b/packages/core/src/network/transports/obfuscated.ts index 5af233b0..a4fbf2ef 100644 --- a/packages/core/src/network/transports/obfuscated.ts +++ b/packages/core/src/network/transports/obfuscated.ts @@ -73,8 +73,8 @@ export class ObfuscatedPacketCodec extends WrappedCodec implements IPacketCodec const decryptIv = randomRev.subarray(32, 48) if (this._proxy) { - encryptKey = await this._crypto.sha256(concatBuffers([encryptKey, this._proxy.secret])) - decryptKey = await this._crypto.sha256(concatBuffers([decryptKey, this._proxy.secret])) + encryptKey = this._crypto.sha256(concatBuffers([encryptKey, this._proxy.secret])) + decryptKey = this._crypto.sha256(concatBuffers([decryptKey, this._proxy.secret])) } this._encryptor = this._crypto.createAesCtr(encryptKey, encryptIv, true) diff --git a/packages/core/src/utils/crypto/abstract.ts b/packages/core/src/utils/crypto/abstract.ts index 041c074a..f4048a42 100644 --- a/packages/core/src/utils/crypto/abstract.ts +++ b/packages/core/src/utils/crypto/abstract.ts @@ -14,9 +14,9 @@ export interface IAesCtr { export interface ICryptoProvider { initialize?(): MaybeAsync - sha1(data: Uint8Array): MaybeAsync + sha1(data: Uint8Array): Uint8Array - sha256(data: Uint8Array): MaybeAsync + sha256(data: Uint8Array): Uint8Array pbkdf2( password: Uint8Array, diff --git a/packages/core/src/utils/crypto/keys.ts b/packages/core/src/utils/crypto/keys.ts index 337304a7..b36d30c3 100644 --- a/packages/core/src/utils/crypto/keys.ts +++ b/packages/core/src/utils/crypto/keys.ts @@ -14,7 +14,7 @@ import { ICryptoProvider } from './abstract.js' * @param key PEM-encoded RSA public key * @param old Whether this is an "old" key */ -export async function parsePublicKey(crypto: ICryptoProvider, key: string, old = false): Promise { +export function parsePublicKey(crypto: ICryptoProvider, key: string, old = false): TlPublicKey { const asn1 = parseAsn1(parsePemContents(key)) const modulus = asn1.children?.[0].value const exponent = asn1.children?.[1].value @@ -26,7 +26,7 @@ export async function parsePublicKey(crypto: ICryptoProvider, key: string, old = writer.bytes(exponent) const data = writer.result() - const sha = await crypto.sha1(data) + const sha = crypto.sha1(data) const fp = hexEncode(sha.slice(-8).reverse()) return { @@ -44,8 +44,8 @@ export async function parsePublicKey(crypto: ICryptoProvider, key: string, old = * @param key PEM-encoded RSA public key * @param old Whether this is an "old" key */ -export async function addPublicKey(crypto: ICryptoProvider, key: string, old = false): Promise { - const parsed = await parsePublicKey(crypto, key, old) +export function addPublicKey(crypto: ICryptoProvider, key: string, old = false): void { + const parsed = parsePublicKey(crypto, key, old) keysIndex[parsed.fingerprint] = parsed } diff --git a/packages/core/src/utils/crypto/mtproto.ts b/packages/core/src/utils/crypto/mtproto.ts index 6a063dec..27f80e36 100644 --- a/packages/core/src/utils/crypto/mtproto.ts +++ b/packages/core/src/utils/crypto/mtproto.ts @@ -10,14 +10,14 @@ import { ICryptoProvider, IEncryptionScheme } from './abstract.js' * @param newNonce New nonce * @returns Tuple: `[key, iv]` */ -export async function generateKeyAndIvFromNonce( +export function generateKeyAndIvFromNonce( crypto: ICryptoProvider, serverNonce: Uint8Array, newNonce: Uint8Array, -): Promise<[Uint8Array, Uint8Array]> { - const hash1 = await crypto.sha1(concatBuffers([newNonce, serverNonce])) - const hash2 = await crypto.sha1(concatBuffers([serverNonce, newNonce])) - const hash3 = await crypto.sha1(concatBuffers([newNonce, newNonce])) +): [Uint8Array, Uint8Array] { + const hash1 = crypto.sha1(concatBuffers([newNonce, serverNonce])) + const hash2 = crypto.sha1(concatBuffers([serverNonce, newNonce])) + const hash3 = crypto.sha1(concatBuffers([newNonce, newNonce])) const key = concatBuffers([hash1, hash2.subarray(0, 12)]) const iv = concatBuffers([hash2.subarray(12, 20), hash3, newNonce.subarray(0, 4)]) @@ -34,15 +34,15 @@ export async function generateKeyAndIvFromNonce( * @param messageKey Message key * @param client Whether this is a client to server message */ -export async function createAesIgeForMessage( +export function createAesIgeForMessage( crypto: ICryptoProvider, authKey: Uint8Array, messageKey: Uint8Array, client: boolean, -): Promise { +): IEncryptionScheme { const x = client ? 0 : 8 - const sha256a = await crypto.sha256(concatBuffers([messageKey, authKey.subarray(x, 36 + x)])) - const sha256b = await crypto.sha256(concatBuffers([authKey.subarray(40 + x, 76 + x), messageKey])) + const sha256a = crypto.sha256(concatBuffers([messageKey, authKey.subarray(x, 36 + x)])) + const sha256b = crypto.sha256(concatBuffers([authKey.subarray(40 + x, 76 + x), messageKey])) const key = concatBuffers([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)]) @@ -59,19 +59,19 @@ export async function createAesIgeForMessage( * @param messageKey Message key * @param client Whether this is a client to server message */ -export async function createAesIgeForMessageOld( +export function createAesIgeForMessageOld( crypto: ICryptoProvider, authKey: Uint8Array, messageKey: Uint8Array, client: boolean, -): Promise { +): IEncryptionScheme { const x = client ? 0 : 8 - const sha1a = await crypto.sha1(concatBuffers([messageKey, authKey.subarray(x, 32 + x)])) - const sha1b = await crypto.sha1( + const sha1a = crypto.sha1(concatBuffers([messageKey, authKey.subarray(x, 32 + x)])) + const sha1b = crypto.sha1( concatBuffers([authKey.subarray(32 + x, 48 + x), messageKey, authKey.subarray(48 + x, 64 + x)]), ) - const sha1c = await crypto.sha1(concatBuffers([authKey.subarray(64 + x, 96 + x), messageKey])) - const sha1d = await crypto.sha1(concatBuffers([messageKey, authKey.subarray(96 + x, 128 + x)])) + const sha1c = crypto.sha1(concatBuffers([authKey.subarray(64 + x, 96 + x), messageKey])) + const sha1d = crypto.sha1(concatBuffers([messageKey, authKey.subarray(96 + x, 128 + x)])) const key = concatBuffers([sha1a.subarray(0, 8), sha1b.subarray(8, 20), sha1c.subarray(4, 16)]) const iv = concatBuffers([ diff --git a/packages/core/src/utils/crypto/node.ts b/packages/core/src/utils/crypto/node.ts index e5d11fd1..30cc156a 100644 --- a/packages/core/src/utils/crypto/node.ts +++ b/packages/core/src/utils/crypto/node.ts @@ -63,7 +63,6 @@ export abstract class BaseNodeCryptoProvider extends BaseCryptoProvider { } gunzip(data: Uint8Array): Uint8Array { - // todo: test if wasm impl is better fit here return gunzipSync(data) } } diff --git a/packages/core/src/utils/crypto/password.ts b/packages/core/src/utils/crypto/password.ts index 4dd82488..800660ec 100644 --- a/packages/core/src/utils/crypto/password.ts +++ b/packages/core/src/utils/crypto/password.ts @@ -24,9 +24,9 @@ export async function computePasswordHash( salt2: Uint8Array, ): Promise { const SH = (data: Uint8Array, salt: Uint8Array) => crypto.sha256(concatBuffers([salt, data, salt])) - const PH1 = async (pwd: Uint8Array, salt1: Uint8Array, salt2: Uint8Array) => SH(await SH(pwd, salt1), salt2) + const PH1 = (pwd: Uint8Array, salt1: Uint8Array, salt2: Uint8Array) => SH(SH(pwd, salt1), salt2) - return SH(await crypto.pbkdf2(await PH1(password, salt1, salt2), salt1, 100000), salt2) + return SH(await crypto.pbkdf2(PH1(password, salt1, salt2), salt1, 100000), salt2) } /** @@ -95,12 +95,9 @@ export async function computeSrpParams( const H = (data: Uint8Array) => crypto.sha256(data) - const [_k, _u, _x] = await Promise.all([ - // maybe, just maybe this will be a bit faster with some crypto providers - /* k = */ crypto.sha256(concatBuffers([algo.p, _g])), - /* u = */ crypto.sha256(concatBuffers([_gA, request.srpB])), - /* x = */ computePasswordHash(crypto, utf8EncodeToBuffer(password), algo.salt1, algo.salt2), - ]) + const _k = crypto.sha256(concatBuffers([algo.p, _g])) + const _u = crypto.sha256(concatBuffers([_gA, request.srpB])) + const _x = await computePasswordHash(crypto, utf8EncodeToBuffer(password), algo.salt1, algo.salt2) const k = bufferToBigInt(_k) const u = bufferToBigInt(_u) const x = bufferToBigInt(_x) @@ -111,18 +108,9 @@ export async function computeSrpParams( let t = gB - kV if (t < 0n) t += p const sA = bigIntModPow(t, a + u * x, p) - const _kA = await H(bigIntToBuffer(sA, 256)) + const _kA = H(bigIntToBuffer(sA, 256)) - const _M1 = await H( - concatBuffers([ - xorBuffer(await H(algo.p), await H(_g)), - await H(algo.salt1), - await H(algo.salt2), - _gA, - request.srpB, - _kA, - ]), - ) + const _M1 = H(concatBuffers([xorBuffer(H(algo.p), H(_g)), H(algo.salt1), H(algo.salt2), _gA, request.srpB, _kA])) return { _: 'inputCheckPasswordSRP', diff --git a/packages/core/src/utils/crypto/wasm.ts b/packages/core/src/utils/crypto/wasm.ts new file mode 100644 index 00000000..00da9601 --- /dev/null +++ b/packages/core/src/utils/crypto/wasm.ts @@ -0,0 +1,69 @@ +import { + createCtr256, + ctr256, + deflateMaxSize, + freeCtr256, + gunzip, + ige256Decrypt, + ige256Encrypt, + initAsync, + InitInput, + sha1, + sha256, +} from '@mtcute/wasm' + +import { BaseCryptoProvider, IAesCtr, ICryptoProvider, IEncryptionScheme } from './abstract.js' + +export interface WasmCryptoProviderOptions { + /** + * WASM blob to use for crypto operations. + * + * Must conform to `@mtcute/wasm` interface. + */ + wasmInput?: InitInput +} + +export class WasmCryptoProvider extends BaseCryptoProvider implements Partial { + readonly wasmInput?: InitInput + + constructor(params?: WasmCryptoProviderOptions) { + super() + this.wasmInput = params?.wasmInput + } + + initialize(): Promise { + return initAsync(this.wasmInput) + } + + sha1(data: Uint8Array): Uint8Array { + return sha1(data) + } + + sha256(data: Uint8Array): Uint8Array { + return sha256(data) + } + + createAesCtr(key: Uint8Array, iv: Uint8Array): IAesCtr { + const ctx = createCtr256(key, iv) + + return { + process: (data) => ctr256(ctx, data), + close: () => freeCtr256(ctx), + } + } + + createAesIge(key: Uint8Array, iv: Uint8Array): IEncryptionScheme { + return { + encrypt: (data) => ige256Encrypt(data, key, iv), + decrypt: (data) => ige256Decrypt(data, key, iv), + } + } + + gzip(data: Uint8Array, maxSize: number): Uint8Array | null { + return deflateMaxSize(data, maxSize) + } + + gunzip(data: Uint8Array): Uint8Array { + return gunzip(data) + } +} diff --git a/packages/core/src/utils/crypto/web.ts b/packages/core/src/utils/crypto/web.ts index 89f1050f..8938cec6 100644 --- a/packages/core/src/utils/crypto/web.ts +++ b/packages/core/src/utils/crypto/web.ts @@ -1,17 +1,5 @@ -import { - createCtr256, - ctr256, - deflateMaxSize, - freeCtr256, - gunzip, - ige256Decrypt, - ige256Encrypt, - initAsync, - InitInput, -} from '@mtcute/wasm' - -import { MaybeAsync } from '../../index.js' -import { BaseCryptoProvider, IAesCtr, ICryptoProvider, IEncryptionScheme } from './abstract.js' +import { ICryptoProvider } from './abstract.js' +import { WasmCryptoProvider, WasmCryptoProviderOptions } from './wasm.js' const ALGO_TO_SUBTLE: Record = { sha256: 'SHA-256', @@ -19,13 +7,11 @@ const ALGO_TO_SUBTLE: Record = { sha512: 'SHA-512', } -export class WebCryptoProvider extends BaseCryptoProvider implements ICryptoProvider { +export class WebCryptoProvider extends WasmCryptoProvider implements ICryptoProvider { readonly subtle: SubtleCrypto - readonly wasmInput?: InitInput - constructor(params?: { wasmInput?: InitInput; subtle?: SubtleCrypto }) { - super() - this.wasmInput = params?.wasmInput + constructor(params?: WasmCryptoProviderOptions & { subtle?: SubtleCrypto }) { + super(params) const subtle = params?.subtle ?? globalThis.crypto?.subtle if (!subtle) { @@ -34,18 +20,6 @@ export class WebCryptoProvider extends BaseCryptoProvider implements ICryptoProv this.subtle = subtle } - initialize(): Promise { - return initAsync(this.wasmInput) - } - - sha1(data: Uint8Array): MaybeAsync { - return this.subtle.digest('SHA-1', data).then((result) => new Uint8Array(result)) - } - - sha256(data: Uint8Array): MaybeAsync { - return this.subtle.digest('SHA-256', data).then((result) => new Uint8Array(result)) - } - async pbkdf2( password: Uint8Array, salt: Uint8Array, @@ -82,28 +56,4 @@ export class WebCryptoProvider extends BaseCryptoProvider implements ICryptoProv return new Uint8Array(res) } - - createAesCtr(key: Uint8Array, iv: Uint8Array): IAesCtr { - const ctx = createCtr256(key, iv) - - return { - process: (data) => ctr256(ctx, data), - close: () => freeCtr256(ctx), - } - } - - createAesIge(key: Uint8Array, iv: Uint8Array): IEncryptionScheme { - return { - encrypt: (data) => ige256Encrypt(data, key, iv), - decrypt: (data) => ige256Decrypt(data, key, iv), - } - } - - gzip(data: Uint8Array, maxSize: number): Uint8Array | null { - return deflateMaxSize(data, maxSize) - } - - gunzip(data: Uint8Array): Uint8Array { - return gunzip(data) - } } diff --git a/packages/core/tests/auth-key.spec.ts b/packages/core/tests/auth-key.spec.ts index eccc8753..3835552e 100644 --- a/packages/core/tests/auth-key.spec.ts +++ b/packages/core/tests/auth-key.spec.ts @@ -21,9 +21,9 @@ describe('AuthKey', () => { const logger = new LogManager() const readerMap: TlReaderMap = {} - it('should correctly calculate derivatives', async () => { + it('should correctly calculate derivatives', () => { const key = new AuthKey(crypto, logger, readerMap) - await key.setup(authKey) + key.setup(authKey) expect(key.key).to.eql(authKey) expect(key.clientSalt).to.eql( diff --git a/packages/core/tests/crypto-providers.spec.ts b/packages/core/tests/crypto-providers.spec.ts index ea89492d..12014793 100644 --- a/packages/core/tests/crypto-providers.spec.ts +++ b/packages/core/tests/crypto-providers.spec.ts @@ -11,20 +11,20 @@ import { ICryptoProvider } from '../src/utils/index.js' export function testCryptoProvider(c: ICryptoProvider): void { before(() => c.initialize?.()) - it('should calculate sha1', async () => { - expect(hexEncode(await c.sha1(utf8EncodeToBuffer('')))).to.eq('da39a3ee5e6b4b0d3255bfef95601890afd80709') - expect(hexEncode(await c.sha1(utf8EncodeToBuffer('hello')))).to.eq('aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d') - expect(hexEncode(await c.sha1(hexDecodeToBuffer('aebb1f')))).to.eq('62849d15c5dea495916c5eea8dba5f9551288850') + it('should calculate sha1', () => { + expect(hexEncode(c.sha1(utf8EncodeToBuffer('')))).to.eq('da39a3ee5e6b4b0d3255bfef95601890afd80709') + expect(hexEncode(c.sha1(utf8EncodeToBuffer('hello')))).to.eq('aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d') + expect(hexEncode(c.sha1(hexDecodeToBuffer('aebb1f')))).to.eq('62849d15c5dea495916c5eea8dba5f9551288850') }) - it('should calculate sha256', async () => { - expect(hexEncode(await c.sha256(utf8EncodeToBuffer('')))).to.eq( + it('should calculate sha256', () => { + expect(hexEncode(c.sha256(utf8EncodeToBuffer('')))).to.eq( 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', ) - expect(hexEncode(await c.sha256(utf8EncodeToBuffer('hello')))).to.eq( + expect(hexEncode(c.sha256(utf8EncodeToBuffer('hello')))).to.eq( '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', ) - expect(hexEncode(await c.sha256(hexDecodeToBuffer('aebb1f')))).to.eq( + expect(hexEncode(c.sha256(hexDecodeToBuffer('aebb1f')))).to.eq( '2d29658aba48f2b286fe8bbddb931b7ad297e5adb5b9a6fc3aab67ef7fbf4e80', ) }) diff --git a/packages/core/tests/keys.spec.ts b/packages/core/tests/keys.spec.ts index ae912513..46086342 100644 --- a/packages/core/tests/keys.spec.ts +++ b/packages/core/tests/keys.spec.ts @@ -7,9 +7,9 @@ import { parsePublicKey } from '../src/utils/index.js' const crypto = new NodeCryptoProvider() describe('parsePublicKey', () => { - it('should parse telegram public keys', async () => { + it('should parse telegram public keys', () => { expect( - await parsePublicKey( + parsePublicKey( crypto, `-----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEAruw2yP/BCcsJliRoW5eBVBVle9dtjJw+OYED160Wybum9SXtBBLX diff --git a/packages/core/tests/mtproto-crypto.spec.ts b/packages/core/tests/mtproto-crypto.spec.ts index 1ded2b61..ac1c1422 100644 --- a/packages/core/tests/mtproto-crypto.spec.ts +++ b/packages/core/tests/mtproto-crypto.spec.ts @@ -20,11 +20,11 @@ const authKey = Buffer.alloc( const messageKey = Buffer.from('25d701f2a29205526757825a99eb2d32') describe('mtproto 2.0', () => { - it('should correctly derive message key and iv for client', async () => { + it('should correctly derive message key and iv for client', () => { const crypto = new NodeCryptoProvider() const spy = chai.spy.on(crypto, 'createAesIge') - await createAesIgeForMessage(crypto, authKey, messageKey, true) + createAesIgeForMessage(crypto, authKey, messageKey, true) expect(spy).to.have.been.called.with.exactly( Buffer.from('7acac59ab48cd370e478daf6c64545ab9f32d5c9197f25febe052110f61875ca', 'hex'), @@ -32,11 +32,11 @@ describe('mtproto 2.0', () => { ) }) - it('should correctly derive message key and iv for server', async () => { + it('should correctly derive message key and iv for server', () => { const crypto = new NodeCryptoProvider() const spy = chai.spy.on(crypto, 'createAesIge') - await createAesIgeForMessage(crypto, authKey, messageKey, false) + createAesIgeForMessage(crypto, authKey, messageKey, false) expect(spy).to.have.been.called.with.exactly( Buffer.from('c7cf179e7ebab144ba87de05415db4157d2fc66df4790b2fd405a6c8cbe4c0b3', 'hex'), @@ -46,11 +46,11 @@ describe('mtproto 2.0', () => { }) describe('mtproto 1.0', () => { - it('should correctly derive message key and iv for client', async () => { + it('should correctly derive message key and iv for client', () => { const crypto = new NodeCryptoProvider() const spy = chai.spy.on(crypto, 'createAesIge') - await createAesIgeForMessageOld(crypto, authKey, messageKey, true) + createAesIgeForMessageOld(crypto, authKey, messageKey, true) expect(spy).to.have.been.called.with.exactly( Buffer.from('aad61cb5b7be5e8435174d74665f8a978e85806d0970ad4958642ca49e3c8834', 'hex'), @@ -58,11 +58,11 @@ describe('mtproto 1.0', () => { ) }) - it('should correctly derive message key and iv for server', async () => { + it('should correctly derive message key and iv for server', () => { const crypto = new NodeCryptoProvider() const spy = chai.spy.on(crypto, 'createAesIge') - await createAesIgeForMessageOld(crypto, authKey, messageKey, false) + createAesIgeForMessageOld(crypto, authKey, messageKey, false) expect(spy).to.have.been.called.with.exactly( Buffer.from('d57682a17105e43b92bc5025ea80e88ef708240fc19450dfe072a8760f9534da', 'hex'), @@ -72,10 +72,10 @@ describe('mtproto 1.0', () => { }) describe('mtproto key/iv from nonce', () => { - it('should correctly derive message key and iv for given nonces', async () => { + it('should correctly derive message key and iv for given nonces', () => { const crypto = new NodeCryptoProvider() - const res = await generateKeyAndIvFromNonce( + const res = generateKeyAndIvFromNonce( crypto, Buffer.from('8af24c551836e5ed7002f5857e6e71b2', 'hex'), Buffer.from('3bf48b2d3152f383d82d1f2b32ac7fb5', 'hex'), diff --git a/packages/tl/scripts/gen-rsa-keys.ts b/packages/tl/scripts/gen-rsa-keys.ts index e5b6cd85..3073c9f7 100644 --- a/packages/tl/scripts/gen-rsa-keys.ts +++ b/packages/tl/scripts/gen-rsa-keys.ts @@ -50,7 +50,7 @@ async function main() { const obj: Record = {} for await (const key of parseInputFile()) { - const parsed = await parsePublicKey(crypto, key.pem, key.kind === 'old') + const parsed = parsePublicKey(crypto, key.pem, key.kind === 'old') obj[parsed.fingerprint] = parsed } diff --git a/packages/wasm/README.md b/packages/wasm/README.md index ea528ffb..da77ad69 100644 --- a/packages/wasm/README.md +++ b/packages/wasm/README.md @@ -5,15 +5,20 @@ Highly optimized for size & speed WASM implementation of common algorithms used in Telegram. ## Features -- **Super lightweight**: Only 45 KB raw, 22 KB gzipped +- **Super lightweight**: Only 47 KB raw, 24 KB gzipped - **Blazingly fast**: Up to 10x faster than pure JS implementations -- Implements AES IGE and Deflate (zlib compression + gunzip), which are not available in some environments (e.g. web) +- **Ready to use**: Implements almost all algos used in MTProto: + - AES IGE + - Deflate (zlib compression + gunzip) + - SHA-1, SHA-256 ## Acknowledgements - Deflate is implemented through a modified version of [libdeflate](https://github.com/ebiggers/libdeflate), MIT license. - Modified by [kamillaova](https://github.com/kamillaova) to support WASM and improve bundle size - AES IGE code is mostly based on [tgcrypto](https://github.com/pyrogram/tgcrypto), LGPL-3.0 license. - To comply with LGPL-3.0, the source code of the modified tgcrypto is available [here](./lib/crypto/) under LGPL-3.0 license. +- SHA1 is based on [teeny-sha1](https://github.com/CTrabant/teeny-sha1) +- SHA256 is based on [lekkit/sha256](https://github.com/LekKit/sha256) ## Benchmarks See https://github.com/mtcute/benchmarks \ No newline at end of file diff --git a/packages/wasm/lib/Dockerfile b/packages/wasm/lib/Dockerfile index 6ccbe96e..a819d885 100644 --- a/packages/wasm/lib/Dockerfile +++ b/packages/wasm/lib/Dockerfile @@ -6,9 +6,10 @@ RUN apk add --no-cache lld make clang16 binaryen COPY crypto /src/crypto COPY libdeflate /src/libdeflate -COPY *.h *.c Makefile /src/ +COPY utils /src/utils +COPY wasm.h Makefile /src/ -RUN ZLIB_COMPRESSION_API=1 GZIP_DECOMPRESSION_API=1 IGE_API=1 CTR_API=1 make +RUN make FROM scratch AS binaries COPY --from=build /src/mtcute.wasm / diff --git a/packages/wasm/lib/Makefile b/packages/wasm/lib/Makefile index f8bed887..dccc16ed 100644 --- a/packages/wasm/lib/Makefile +++ b/packages/wasm/lib/Makefile @@ -1,39 +1,17 @@ .PHONY: all clean -DEFAULT_API ?= 0 - -DEFLATE_COMPRESSION_API ?= $(DEFAULT_API) -DEFLATE_DECOMPRESSION_API ?= $(DEFAULT_API) -GZIP_COMPRESSION_API ?= $(DEFAULT_API) -GZIP_DECOMPRESSION_API ?= $(DEFAULT_API) -ZLIB_COMPRESSION_API ?= $(DEFAULT_API) -ZLIB_DECOMPRESSION_API ?= $(DEFAULT_API) -CRC32_API ?= $(DEFAULT_API) -ADLER32_API ?= $(DEFAULT_API) -IGE_API ?= $(DEFAULT_API) -CTR_API ?= $(DEFAULT_API) - -CRC32 ?= 0 - -LOGGING ?= 0 - -_DEFLATE_COMPRESSION := 1 -_DEFLATE_DECOMPRESSION := 1 -_ADLER32 := $(findstring 1, $(ZLIB_COMPRESSION_API)$(ZLIB_DECOMPRESSION_API)) -_AES := $(findstring 1, $(IGE_API)$(CTR_API)) - -SOURCES = utils.c \ - $(if $(filter 1, $(_DEFLATE_COMPRESSION)), libdeflate/deflate_compress.c) \ - $(if $(filter 1, $(_DEFLATE_DECOMPRESSION)), libdeflate/deflate_decompress.c) \ - $(if $(filter 1, $(GZIP_COMPRESSION_API)), libdeflate/gzip_compress.c) \ - $(if $(filter 1, $(GZIP_DECOMPRESSION_API)), libdeflate/gzip_decompress.c) \ - $(if $(filter 1, $(ZLIB_COMPRESSION_API)), libdeflate/zlib_compress.c) \ - $(if $(filter 1, $(ZLIB_DECOMPRESSION_API)), libdeflate/zlib_decompress.c) \ - $(if $(filter 1, $(CRC32)), libdeflate/crc32.c) \ - $(if $(filter 1, $(_ADLER32)), libdeflate/adler32.c) \ - $(if $(filter 1, $(_AES)), crypto/aes256.c) \ - $(if $(filter 1, $(IGE_API)), crypto/ige256.c) \ - $(if $(filter 1, $(CTR_API)), crypto/ctr256.c) +SOURCES = utils/allocator.c \ + libdeflate/allocator.c \ + libdeflate/deflate_compress.c \ + libdeflate/deflate_decompress.c \ + libdeflate/gzip_decompress.c \ + libdeflate/zlib_compress.c \ + libdeflate/adler32.c \ + crypto/aes256.c \ + crypto/ige256.c \ + crypto/ctr256.c \ + hash/sha256.c \ + hash/sha1.c WASM_CC ?= clang CC := $(WASM_CC) @@ -41,7 +19,6 @@ CC := $(WASM_CC) CFLAGS_WASM := \ -target wasm32-unknown-unknown \ -nostdlib -ffreestanding -DFREESTANDING \ - $(if $(filter 1, $(LOGGING)), -DLOGGING) \ -mbulk-memory \ -Wl,--no-entry,--export-dynamic,--lto-O3 @@ -69,7 +46,7 @@ endif OUT := mtcute.wasm $(OUT): $(SOURCES) - $(CC) $(CFLAGS) -I . -o $@ $^ + $(CC) $(CFLAGS) -I . -I utils -o $@ $^ clean: rm -f $(OUT) diff --git a/packages/wasm/lib/crypto/aes256.c b/packages/wasm/lib/crypto/aes256.c index 22a60d12..c58bfefd 100644 --- a/packages/wasm/lib/crypto/aes256.c +++ b/packages/wasm/lib/crypto/aes256.c @@ -6,6 +6,17 @@ #define GET(p) SWAP(*((uint32_t *)(p))) #define PUT(ct, st) (*((uint32_t *)(ct)) = SWAP((st))) +uint8_t aes_shared_key_buffer[32]; +uint8_t aes_shared_iv_buffer[32]; + +WASM_EXPORT uint8_t* __get_shared_key_buffer() { + return aes_shared_key_buffer; +} + +WASM_EXPORT uint8_t* __get_shared_iv_buffer() { + return aes_shared_iv_buffer; +} + static const uint32_t Te0[256] = { 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, diff --git a/packages/wasm/lib/crypto/aes256.h b/packages/wasm/lib/crypto/aes256.h index c84ea460..d2945727 100644 --- a/packages/wasm/lib/crypto/aes256.h +++ b/packages/wasm/lib/crypto/aes256.h @@ -1,15 +1,13 @@ -#include "lib_common.h" +#include "wasm.h" #ifndef AES256_H #define AES256_H #define AES_BLOCK_SIZE 16 #define EXPANDED_KEY_SIZE 60 -#define AES_EXPORT __attribute__((visibility("default"))) -#ifdef __cplusplus -extern "C" { -#endif +extern uint8_t aes_shared_key_buffer[32]; +extern uint8_t aes_shared_iv_buffer[32]; void aes256_set_encryption_key(uint8_t* key, uint32_t* expandedKey); void aes256_set_decryption_key(uint8_t* key, uint32_t* expandedKey); @@ -17,9 +15,4 @@ void aes256_set_decryption_key(uint8_t* key, uint32_t* expandedKey); void aes256_encrypt(uint8_t* in, uint8_t* out, uint32_t* expandedKey); void aes256_decrypt(uint8_t* in, uint8_t* out, uint32_t* expandedKey); - -#ifdef __cplusplus -} -#endif - -#endif // AES256_H +#endif // AES256_H \ No newline at end of file diff --git a/packages/wasm/lib/crypto/ctr256.c b/packages/wasm/lib/crypto/ctr256.c index 71239e59..010201a2 100644 --- a/packages/wasm/lib/crypto/ctr256.c +++ b/packages/wasm/lib/crypto/ctr256.c @@ -2,27 +2,25 @@ struct ctr256_ctx { uint32_t expandedKey[EXPANDED_KEY_SIZE]; - uint8_t* iv; + uint8_t iv[AES_BLOCK_SIZE]; uint8_t state; }; -AES_EXPORT struct ctr256_ctx* ctr256_alloc(uint8_t* key, uint8_t* iv) { +WASM_EXPORT struct ctr256_ctx* ctr256_alloc() { struct ctr256_ctx *state = (struct ctr256_ctx *) __malloc(sizeof(struct ctr256_ctx)); - aes256_set_encryption_key(key, state->expandedKey); - __free(key); + aes256_set_encryption_key(aes_shared_key_buffer, state->expandedKey); - state->iv = iv; + memcpy(state->iv, aes_shared_iv_buffer, AES_BLOCK_SIZE); state->state = 0; return state; } -AES_EXPORT void ctr256_free(struct ctr256_ctx* ctx) { - __free(ctx->iv); +WASM_EXPORT void ctr256_free(struct ctr256_ctx* ctx) { __free(ctx); } -AES_EXPORT void ctr256(struct ctr256_ctx* ctx, uint8_t* in, uint32_t length, uint8_t *out) { +WASM_EXPORT void ctr256(struct ctr256_ctx* ctx, uint8_t* in, uint32_t length, uint8_t *out) { uint8_t chunk[AES_BLOCK_SIZE]; uint32_t* expandedKey = ctx->expandedKey; uint8_t* iv = ctx->iv; diff --git a/packages/wasm/lib/crypto/ctr256.h b/packages/wasm/lib/crypto/ctr256.h deleted file mode 100644 index 9c984a09..00000000 --- a/packages/wasm/lib/crypto/ctr256.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef CTR256_H -#define CTR256_H - -uint8_t *ctr256(const uint8_t in[], uint32_t length, const uint8_t key[32], uint8_t iv[16], uint8_t *state); - -#endif \ No newline at end of file diff --git a/packages/wasm/lib/crypto/ige256.c b/packages/wasm/lib/crypto/ige256.c index 6bc054ed..97bc8350 100644 --- a/packages/wasm/lib/crypto/ige256.c +++ b/packages/wasm/lib/crypto/ige256.c @@ -1,6 +1,6 @@ #include "aes256.h" -AES_EXPORT void ige256_encrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8_t* iv, uint8_t* out) { +WASM_EXPORT void ige256_encrypt(uint8_t* in, uint32_t length, uint8_t* out) { uint32_t expandedKey[EXPANDED_KEY_SIZE]; uint32_t i, j; @@ -8,10 +8,10 @@ AES_EXPORT void ige256_encrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8 uint8_t* iv2; uint8_t* block; - iv1 = &iv[0]; - iv2 = &iv[16]; + iv1 = &aes_shared_iv_buffer[0]; + iv2 = &aes_shared_iv_buffer[16]; - aes256_set_encryption_key(key, expandedKey); + aes256_set_encryption_key(aes_shared_key_buffer, expandedKey); for (i = 0; i < length; i += AES_BLOCK_SIZE) { block = &out[i]; @@ -29,7 +29,7 @@ AES_EXPORT void ige256_encrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8 } } -AES_EXPORT void ige256_decrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8_t* iv, uint8_t* out) { +WASM_EXPORT void ige256_decrypt(uint8_t* in, uint32_t length, uint8_t* out) { uint32_t expandedKey[EXPANDED_KEY_SIZE]; uint32_t i, j; @@ -37,10 +37,10 @@ AES_EXPORT void ige256_decrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8 uint8_t* iv2; uint8_t* block; - iv1 = &iv[16]; - iv2 = &iv[0]; + iv1 = &aes_shared_iv_buffer[16]; + iv2 = &aes_shared_iv_buffer[0]; - aes256_set_decryption_key(key, expandedKey); + aes256_set_decryption_key(aes_shared_key_buffer, expandedKey); for (i = 0; i < length; i += AES_BLOCK_SIZE) { block = &out[i]; @@ -56,4 +56,4 @@ AES_EXPORT void ige256_decrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8 iv1 = block; iv2 = &in[i]; } -} +} \ No newline at end of file diff --git a/packages/wasm/lib/crypto/ige256.h b/packages/wasm/lib/crypto/ige256.h deleted file mode 100644 index b83b1e38..00000000 --- a/packages/wasm/lib/crypto/ige256.h +++ /dev/null @@ -1,17 +0,0 @@ -#include - -#ifndef IGE256_H -#define IGE256_H - -#ifdef __cplusplus -extern "C" { -#endif - -void ige256_encrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8_t* iv, uint8_t* out); -void ige256_decrypt(uint8_t* in, uint32_t length, uint8_t* key, uint8_t* iv, uint8_t* out); - -#ifdef __cplusplus -} -#endif - -#endif // IGE256_H diff --git a/packages/wasm/lib/hash/sha1.c b/packages/wasm/lib/hash/sha1.c new file mode 100644 index 00000000..1139ff16 --- /dev/null +++ b/packages/wasm/lib/hash/sha1.c @@ -0,0 +1,179 @@ +/******************************************************************************* + * Teeny SHA-1 + * + * The below sha1digest() calculates a SHA-1 hash value for a + * specified data buffer and generates a hex representation of the + * result. This implementation is a re-forming of the SHA-1 code at + * https://github.com/jinqiangshou/EncryptionLibrary. + * + * Copyright (c) 2017 CTrabant + * + * License: MIT, see included LICENSE file for details. + * + * To use the sha1digest() function either copy it into an existing + * project source code file or include this file in a project and put + * the declaration (example below) in the sources files where needed. + ******************************************************************************/ + +#include "wasm.h" + +/* Declaration: +extern int sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes); +*/ + +/******************************************************************************* + * sha1digest: https://github.com/CTrabant/teeny-sha1 + * + * Calculate the SHA-1 value for supplied data buffer and generate a + * text representation in hexadecimal. + * + * Based on https://github.com/jinqiangshou/EncryptionLibrary, credit + * goes to @jinqiangshou, all new bugs are mine. + * + * @input: + * data -- data to be hashed + * databytes -- bytes in data buffer to be hashed + * + * @output: + * digest -- the result, MUST be at least 20 bytes + * hexdigest -- the result in hex, MUST be at least 41 bytes + * + * At least one of the output buffers must be supplied. The other, if not + * desired, may be set to NULL. + * + * @return: 0 on success and non-zero on error. + ******************************************************************************/ +WASM_EXPORT void sha1(const uint8_t *data, size_t databytes) { +#define SHA1ROTATELEFT(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + + uint32_t W[80]; + uint32_t H[] = {0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0}; + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint32_t e; + uint32_t f = 0; + uint32_t k = 0; + + uint32_t idx; + uint32_t lidx; + uint32_t widx; + uint32_t didx = 0; + + int32_t wcount; + uint32_t temp; + uint64_t databits = ((uint64_t)databytes) * 8; + uint32_t loopcount = (databytes + 8) / 64 + 1; + uint32_t tailbytes = 64 * loopcount - databytes; + uint8_t datatail[128] = {0}; + + /* Pre-processing of data tail (includes padding to fill out 512-bit chunk): + Add bit '1' to end of message (big-endian) + Add 64-bit message length in bits at very end (big-endian) */ + datatail[0] = 0x80; + datatail[tailbytes - 8] = (uint8_t) (databits >> 56 & 0xFF); + datatail[tailbytes - 7] = (uint8_t) (databits >> 48 & 0xFF); + datatail[tailbytes - 6] = (uint8_t) (databits >> 40 & 0xFF); + datatail[tailbytes - 5] = (uint8_t) (databits >> 32 & 0xFF); + datatail[tailbytes - 4] = (uint8_t) (databits >> 24 & 0xFF); + datatail[tailbytes - 3] = (uint8_t) (databits >> 16 & 0xFF); + datatail[tailbytes - 2] = (uint8_t) (databits >> 8 & 0xFF); + datatail[tailbytes - 1] = (uint8_t) (databits >> 0 & 0xFF); + + /* Process each 512-bit chunk */ + for (lidx = 0; lidx < loopcount; lidx++) + { + /* Compute all elements in W */ + memset (W, 0, 80 * sizeof (uint32_t)); + + /* Break 512-bit chunk into sixteen 32-bit, big endian words */ + for (widx = 0; widx <= 15; widx++) + { + wcount = 24; + + /* Copy byte-per byte from specified buffer */ + while (didx < databytes && wcount >= 0) + { + W[widx] += (((uint32_t)data[didx]) << wcount); + didx++; + wcount -= 8; + } + /* Fill out W with padding as needed */ + while (wcount >= 0) + { + W[widx] += (((uint32_t)datatail[didx - databytes]) << wcount); + didx++; + wcount -= 8; + } + } + + /* Extend the sixteen 32-bit words into eighty 32-bit words, with potential optimization from: + "Improving the Performance of the Secure Hash Algorithm (SHA-1)" by Max Locktyukhin */ + for (widx = 16; widx <= 31; widx++) + { + W[widx] = SHA1ROTATELEFT ((W[widx - 3] ^ W[widx - 8] ^ W[widx - 14] ^ W[widx - 16]), 1); + } + for (widx = 32; widx <= 79; widx++) + { + W[widx] = SHA1ROTATELEFT ((W[widx - 6] ^ W[widx - 16] ^ W[widx - 28] ^ W[widx - 32]), 2); + } + + /* Main loop */ + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + + for (idx = 0; idx <= 79; idx++) + { + if (idx <= 19) + { + f = (b & c) | ((~b) & d); + k = 0x5A827999; + } + else if (idx >= 20 && idx <= 39) + { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + else if (idx >= 40 && idx <= 59) + { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } + else if (idx >= 60 && idx <= 79) + { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + temp = SHA1ROTATELEFT (a, 5) + f + e + k + W[idx]; + e = d; + d = c; + c = SHA1ROTATELEFT (b, 30); + b = a; + a = temp; + } + + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + } + + /* Store binary digest in supplied buffer */ + for (idx = 0; idx < 5; idx++) { + shared_out[idx * 4 + 0] = (uint8_t) (H[idx] >> 24); + shared_out[idx * 4 + 1] = (uint8_t) (H[idx] >> 16); + shared_out[idx * 4 + 2] = (uint8_t) (H[idx] >> 8); + shared_out[idx * 4 + 3] = (uint8_t) (H[idx]); + } + + #undef SHA1ROTATELEFT +} /* End of sha1digest() */ \ No newline at end of file diff --git a/packages/wasm/lib/hash/sha256.c b/packages/wasm/lib/hash/sha256.c new file mode 100644 index 00000000..148bf98c --- /dev/null +++ b/packages/wasm/lib/hash/sha256.c @@ -0,0 +1,167 @@ +/* + MIT License + + Copyright (c) 2020 LekKit https://github.com/LekKit + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "wasm.h" + +struct lekkit_sha256_buff { + uint64_t data_size; + uint32_t h[8]; + uint8_t last_chunk[64]; + uint8_t chunk_size; +}; + +void lekkit_sha256_init(struct lekkit_sha256_buff* buff) { + buff->h[0] = 0x6a09e667; + buff->h[1] = 0xbb67ae85; + buff->h[2] = 0x3c6ef372; + buff->h[3] = 0xa54ff53a; + buff->h[4] = 0x510e527f; + buff->h[5] = 0x9b05688c; + buff->h[6] = 0x1f83d9ab; + buff->h[7] = 0x5be0cd19; + buff->data_size = 0; + buff->chunk_size = 0; +} + +static const uint32_t lekkit_k[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +#define rotate_r(val, bits) (val >> bits | val << (32 - bits)) + +static void lekkit_sha256_calc_chunk(struct lekkit_sha256_buff* buff, const uint8_t* chunk) { + uint32_t w[64]; + uint32_t tv[8]; + uint32_t i; + + for (i=0; i<16; ++i){ + w[i] = (uint32_t) chunk[0] << 24 | (uint32_t) chunk[1] << 16 | (uint32_t) chunk[2] << 8 | (uint32_t) chunk[3]; + chunk += 4; + } + + for (i=16; i<64; ++i){ + uint32_t s0 = rotate_r(w[i-15], 7) ^ rotate_r(w[i-15], 18) ^ (w[i-15] >> 3); + uint32_t s1 = rotate_r(w[i-2], 17) ^ rotate_r(w[i-2], 19) ^ (w[i-2] >> 10); + w[i] = w[i-16] + s0 + w[i-7] + s1; + } + + for (i = 0; i < 8; ++i) + tv[i] = buff->h[i]; + + for (i=0; i<64; ++i){ + uint32_t S1 = rotate_r(tv[4], 6) ^ rotate_r(tv[4], 11) ^ rotate_r(tv[4], 25); + uint32_t ch = (tv[4] & tv[5]) ^ (~tv[4] & tv[6]); + uint32_t temp1 = tv[7] + S1 + ch + lekkit_k[i] + w[i]; + uint32_t S0 = rotate_r(tv[0], 2) ^ rotate_r(tv[0], 13) ^ rotate_r(tv[0], 22); + uint32_t maj = (tv[0] & tv[1]) ^ (tv[0] & tv[2]) ^ (tv[1] & tv[2]); + uint32_t temp2 = S0 + maj; + + tv[7] = tv[6]; + tv[6] = tv[5]; + tv[5] = tv[4]; + tv[4] = tv[3] + temp1; + tv[3] = tv[2]; + tv[2] = tv[1]; + tv[1] = tv[0]; + tv[0] = temp1 + temp2; + } + + for (i = 0; i < 8; ++i) + buff->h[i] += tv[i]; +} + +void lekkit_sha256_update(struct lekkit_sha256_buff* buff, const void* data, uint32_t size) { + const uint8_t* ptr = (const uint8_t*)data; + buff->data_size += size; + /* If there is data left in buff, concatenate it to process as new chunk */ + if (size + buff->chunk_size >= 64) { + uint8_t tmp_chunk[64]; + __builtin_memcpy(tmp_chunk, buff->last_chunk, buff->chunk_size); + __builtin_memcpy(tmp_chunk + buff->chunk_size, ptr, 64 - buff->chunk_size); + ptr += (64 - buff->chunk_size); + size -= (64 - buff->chunk_size); + buff->chunk_size = 0; + lekkit_sha256_calc_chunk(buff, tmp_chunk); + } + /* Run over data chunks */ + while (size >= 64) { + lekkit_sha256_calc_chunk(buff, ptr); + ptr += 64; + size -= 64; + } + + /* Save remaining data in buff, will be reused on next call or finalize */ + __builtin_memcpy(buff->last_chunk + buff->chunk_size, ptr, size); + buff->chunk_size += size; +} + +void lekkit_sha256_finalize(struct lekkit_sha256_buff* buff) { + buff->last_chunk[buff->chunk_size] = 0x80; + buff->chunk_size++; + __builtin_memset(buff->last_chunk + buff->chunk_size, 0, 64 - buff->chunk_size); + + /* If there isn't enough space to fit int64, pad chunk with zeroes and prepare next chunk */ + if (buff->chunk_size > 56) { + lekkit_sha256_calc_chunk(buff, buff->last_chunk); + __builtin_memset(buff->last_chunk, 0, 64); + } + + /* Add total size as big-endian int64 x8 */ + uint64_t size = buff->data_size * 8; + int i; + for (i = 8; i > 0; --i) { + buff->last_chunk[55+i] = size & 255; + size >>= 8; + } + + lekkit_sha256_calc_chunk(buff, buff->last_chunk); +} + +void lekkit_sha256_read(const struct lekkit_sha256_buff* buff, uint8_t* hash) { + uint32_t i; + for (i = 0; i < 8; i++) { + hash[i*4] = (buff->h[i] >> 24) & 255; + hash[i*4 + 1] = (buff->h[i] >> 16) & 255; + hash[i*4 + 2] = (buff->h[i] >> 8) & 255; + hash[i*4 + 3] = buff->h[i] & 255; + } +} + +struct lekkit_sha256_buff lekkit_shared_ctx; + +WASM_EXPORT void sha256(const void* data, uint32_t size) { + lekkit_sha256_init(&lekkit_shared_ctx); + lekkit_sha256_update(&lekkit_shared_ctx, data, size); + lekkit_sha256_finalize(&lekkit_shared_ctx); + lekkit_sha256_read(&lekkit_shared_ctx, shared_out); +} + +#undef rotate_r \ No newline at end of file diff --git a/packages/wasm/lib/libdeflate/allocator.c b/packages/wasm/lib/libdeflate/allocator.c new file mode 100644 index 00000000..0226373b --- /dev/null +++ b/packages/wasm/lib/libdeflate/allocator.c @@ -0,0 +1,21 @@ +#include "wasm.h" + +void * +libdeflate_aligned_malloc(size_t alignment, size_t size) +{ + void *ptr = __malloc(sizeof(void *) + alignment - 1 + size); + + if (ptr) { + void *orig_ptr = ptr; + + ptr = (void *)ALIGN((uintptr_t)ptr + sizeof(void *), alignment); + ((void **)ptr)[-1] = orig_ptr; + } + return ptr; +} + +void +libdeflate_aligned_free(void *ptr) +{ + __free((((void **)ptr)[-1])); +} diff --git a/packages/wasm/lib/lib_common.h b/packages/wasm/lib/libdeflate/lib_common.h similarity index 77% rename from packages/wasm/lib/lib_common.h rename to packages/wasm/lib/libdeflate/lib_common.h index 8bf32b1c..db7ed83e 100644 --- a/packages/wasm/lib/lib_common.h +++ b/packages/wasm/lib/libdeflate/lib_common.h @@ -5,14 +5,6 @@ #ifndef LIB_LIB_COMMON_H #define LIB_LIB_COMMON_H -#ifdef LIBDEFLATE_H - /* - * When building the library, LIBDEFLATEAPI needs to be defined properly before - * including libdeflate.h. - */ -# error "lib_common.h must always be included before libdeflate.h" -#endif - #if defined(LIBDEFLATE_DLL) && (defined(_WIN32) || defined(__CYGWIN__)) # define LIBDEFLATE_EXPORT_SYM __declspec(dllexport) #elif defined(__GNUC__) @@ -38,9 +30,8 @@ #define LIBDEFLATEAPI LIBDEFLATE_EXPORT_SYM LIBDEFLATE_ALIGN_STACK #include "common_defs.h" - -extern void* __malloc(size_t size); -extern void __free(void* ptr); +#include "libdeflate.h" +#include "wasm.h" void *libdeflate_aligned_malloc(size_t alignment, size_t size); void libdeflate_aligned_free(void *ptr); @@ -49,14 +40,4 @@ void libdeflate_aligned_free(void *ptr); #define CONCAT_IMPL(a, b) a##b #define CONCAT(a, b) CONCAT_IMPL(a, b) #define ADD_SUFFIX(name) CONCAT(name, SUFFIX) - -#ifdef LOGGING -void __debug(char* str); - -#define DEBUG(str) __debug(str); - -#else -#define DEBUG(str) -#endif - #endif /* LIB_LIB_COMMON_H */ diff --git a/packages/wasm/lib/libdeflate.h b/packages/wasm/lib/libdeflate/libdeflate.h similarity index 94% rename from packages/wasm/lib/libdeflate.h rename to packages/wasm/lib/libdeflate/libdeflate.h index 1ac01833..e5ac4ef6 100644 --- a/packages/wasm/lib/libdeflate.h +++ b/packages/wasm/lib/libdeflate/libdeflate.h @@ -16,20 +16,6 @@ extern "C" { #define LIBDEFLATE_VERSION_MINOR 19 #define LIBDEFLATE_VERSION_STRING "1.19" -/* - * Users of libdeflate.dll on Windows can define LIBDEFLATE_DLL to cause - * __declspec(dllimport) to be used. This should be done when it's easy to do. - * Otherwise it's fine to skip it, since it is a very minor performance - * optimization that is irrelevant for most use cases of libdeflate. - */ -#ifndef LIBDEFLATEAPI -# if defined(LIBDEFLATE_DLL) && (defined(_WIN32) || defined(__CYGWIN__)) -# define LIBDEFLATEAPI __declspec(dllimport) -# else -# define LIBDEFLATEAPI -# endif -#endif - /* ========================================================================== */ /* Compression */ /* ========================================================================== */ diff --git a/packages/wasm/lib/mtcute.wasm b/packages/wasm/lib/mtcute.wasm index dd10c2c0efa7d9bc898a87d47245f1d336a6ee6c..987ff514f96acf9e6af0b400ab71c108d73f8cc5 100755 GIT binary patch delta 7489 zcmb_h3v`v$l|JWx-231A-<$kNZbC=`oc|g~A`d|!1PG8rL0&34#aXr30%CaFBtUXw z0u;SLgA1$G;L$a7)GjDEP_1jxxpf8~LwQ)Vj-8=1Rhh~f=^6(Kc4^d3)hPq>?Q?H1 z36xG|nXH`uy!JW!eEaNu_TG8yjB)6XMpt|UyZt+Cnolr)-9sZ4ztD3}jktW+1tG+Hy=|(!xV_;K$zoxYxvm>v^3Yj^k6<2G(wxADtEBlo9#Y(z$H%D! z*5iDPI%P?eaeFq(Rdyv0t1kOGjJ{$&jG12#RB>0`9r!knsFQ&SJgRbnlX#Ar9c<*e z>L?9>6DfeVlu)l1`OpuyJ@db1&^u5&?fGy_faoYx#6+ANKFr~K>x0A6Z&)A zN1P#ZtU8bttG{t?jtv!g!oo&YL)Bj3UXXCj^0tSP#8Iuqp$RQw&hPZ)tal#{t5K)*! z5gp5LIU$-5&g)bg9@dD>r)x4`Ikl^(n0xBQqES3g{kEtC(w9z5c|$yK?hh&9B3(Pg z;CXQg+8el)0uYad6JA^`Dz3M$0(#}@h2n=pBeAY2EN_&$dFYEcs9W+^yh`mZxv#V; z!d+1`D|C-2oMqg!iWm3po5@jeMxZD|`5_PXE z-BY%J^I>Y*@Hu=~ddu+jK?wAdk*E2X^uLXIj=QDq5rf+}yjPjtUOC?4rRvC-k-Rc} zX3T{sFH4`9I*RkrDz|nD%K5cZ`RH_K?Is=(y;sNaL^?d<2PQ8~|43Gc_=NPS1uq+X zf_m!45;P9n_*8(MQwYgCcRcPKt$cL zxU~hXN3$)bwWURy7HPrANHG`E7p&4KgK=_v!>lv(FH||$oMXex8{BY7u^S|Bljkm6 zxDc#umWRp{m1Li!)9y`4`kaKty(vCEOo{|8h|y%m7{N9sd>Azthq#g6^UX>5tNzpE z`Khaz?BNaZNI4S`Z>r!{9i7m(cpNYJp#nV z$_|*ChJAv#lpap$U<-2Ab zz=!}0S46-f1OWtXFr00X@NJPpp9tN7Nao1G#VLU^5NPmC5gkcmQP>ig&HyoieK5h4 zeYY^xP3tzL`Yi)`%))9D=c9n&U7{t~l2lDC;hc-I^&s0QWJ~-SA{Vo@Y(2;ptX!0> ztz~N`;(@Ge$$mu>@gOx*kTr}Rp!D-ZkUsg;;(h_OxF5q@oZX>Ln9p9BIif(sw6q*N z68ZWLI9QKNsBYMVfSo}H_Zz4Z8?+X{v`J)|iLp&tA>-A%INt^PR;6(YSE154sms@N z`BPo))a8@9+@{M`nz)RL+o_mG#Y8Fu6=hTa5)`Vg!Bv%H zgT=1yE5PpNL1Xug!D6>~(AeEMXzUsXi{0w40J{x?#%}#!vD-W-?1IqQhxnDitGb-h zcEZoTR2{NfTYNdK?ci5}uj2ovwq5Guy9?ACZQkV-x9W11Z3ns4e@b)nFV$Ri{yRlj zzMtLctEq(8p1~vb<3S^~@2f!U!2biW{Z~S)@KVGojh7YQT=3^Wdt>mRy)tOf4qXwn z(U-kuUYoujJ?~|FY&hHy@$o+kTydN1B_X#QG$*bE+|aOe`fmjHov40&Mew>iuB+Al z$9sUUFP~7rOBpxi0InSMPg`on!mFU2&H$W#OTfYF&l1iaR&%$^cj0vq#qS^hHFAre z?+jen#KJ}D9~$DR2psDikxPy>@&JkXA_rCBE^J2d-pA33o5Ccg4gM;eya_(kF7BlAVUf;tc*dlWZ#MKr|yi=LvbBn^l=LY%<(A9ne*osP}Ne+)jgs zC?@jYaKp7{V?pHGm<88A?jllW-Zb^M_ci8U;JyV<)RG_ZxjJsP)VR*ke46@Z=eW_> zC;%Q2D?Dj&m)6@!*?@8e3WGv3gp3Hy5|xZx$x$hCa*?@X6!rjlglX_O_4;}?@>E>b zH6YT|2{?WEb3Oi(CVqldW6ZAT-DAuhuhqF{rfz&LVIJi8MI zLtLZ`B0GWL6l)HQOTiD4H*j3x-{a$CxU&E{TFghjiQ-nohVVrZ{NNMw5!+W9o_5tK zh%`YH@I!*rI2~Uz4~~0I>n&5l#sN!+-9pW7kV+9~#%-iffXh^}Igx;4FC0XN!`p=^ z3oyl$;r1jU8AKiFJP~Y8A~MBIMSyCH5V#70Yl{wT$qs`KEs%<-hA$5ZRl|8k7OdJx z#qhN$6NAvC+KAu-7;rLSR>QG3C`*G2$y+8afS!;{iMjqy*hg>LUuhDxItWax}d<;A$XsfcP=92CfXFUff8?6&I6E zJ#!(jb+a4N9{Pe`Tr$H5*vJI=bq!!Q(?@et3E9ChqIb`BL*3|f*aj{Dt}HDniCH8h z&bb$0m3lm-;5Xk^_ih_lT5kp_96`HW6X zow&HQV9A>0Y=cfP`7Ucf$pM4pbp9+O%d!-w&JqfmbKDVtR7@G6j`L=-q!$Evn>O7N z$l4dTWimn;1tlyG1bK`s$0@TRZo?GWvI|n|=%#x|I%><$&ycJqn+RTQ*^_N_P=f(a zfP|XHmA0a=y{?&ZzKGxrIfFFbZX<*ZnVL?mXtq^LjlyhW8a0Mx8#Ae4wq}T?LL8Y| z4QkV{PyoIc!hRc@_yw}(=s*5ovmcg!*!PPq+;`>q7e4s;Mn57Sc>C|p-{VK+mSc~- z81Qp|t(}rPaWn|mAjvXHfPr=Nfj6TSj}DIPlecBK_&{vgaIcm^m(M+E@G*KC`{lnPk?>=3xtgI zas3uzIau;Aq~Pkb7}7p?_YGv7;01)>CgAz^ME>={BY}ByNMbPl)kYVzK)BTuAt6^( zZIFtHXtmKvU!>hSIE0vazmJ|}-$-ap$-YY&yJN{C8TEDa<+tSFOw-FY@$Vc`?&54y zzc4ZVz}Qe(GX3s}W$DK|4+f2gZ%NOpJ@H~iNlB2 zJn`QBa}R!W?5V@KZ|sVcuN%`n_LDYvQuT_})UrpWi zvrpYq$L4O|`{pexUJtCQs!GMShX1~z#xEUru4&AT1<{(XFHVN*UkWGM#{J^RmaSu2 z9^G~3XFpuHvSi=-2NxdMeM{r3L(e?=tMb72-`YAU=lAP(CcL7`Si(Y?n$*1eat^V-dM_SjdczQ!@!)u+6@#;hWec@o&2aPxH`E2(W s2S3>`qWKuVYR;+$p8e%V=TE-4qiylEUe9m;<*5G88Z@$6ok-XIFExbOcK`qY delta 5243 zcmcIoYiwLs5k7P7d+*)d>-XB;IOks5@w$ogXc8w)+$Og)t&_B+MEQZzk_U-Pd+jud z?K+7mT{jpCOAW#HVg#r|XN2M(ijZzd)e<0uoK?qPqAwZi}q7)Udq;#uj4c4<#AqEH*=m9I1897 znBe`K2^Q&bbn5vpzW-^=B5Y&DjzjFmAt?q*_|y*mOVU>Nlx08wIEO=mycj}mOP7zA z4*?;$Q|r!E1cEJ*rGVRxTS}G!u^SK8g=lU0PUvRz797LElNc;QMX{7fv>` zhsY!+ngTQdO zVKp#3(Xa^@_(#J&9NW=22_#=>>;>9-)8{aLtmzw|ea*|Uva2~*V+%9VH>P#r0pCS2 ztWRM@8x9rTxfo~zyEx#Uu7Ea$9T7GfW8apin{zp}?Wk={ZKunA@O?|Z)!fl$SA|-M zP#O;5vP7utYFR*8xw%C-V(zKo`o+ z)+e1#sM3V)cga(&r*O*$+rC0QGTr`J(5Ks5*A`S%w1}pg>GNT3t8x&6C>3!(*=QD* ztx+-8DXcl#Tr+hdcT&DZEe4!bfYDe8&1(_zY!t}f4 z`TX6jZIOtw%8gDeD08!x-;E$RmNb0XbFR`G7}uz_j%|`G>n&2Qmg;Von^0$Ukjs zS`Lnb{>m`~2wcMbl>v4r5ajo@Qp@@x?#2Lu6xx2n{0_saU>Uo`TIop^*0HC`83 zh3EGRf-|+qj{^&t!iEvKK}N8^6?$O8XkDb@z*gpn3^Lyqnqt9I#sZFQI4BOCNQ)7s zK^9Xt4vosx7mix36NdVzmw()wB`)9IyRulhwy&;@TU?Z7s$@xIC6aJ#Yz>2<>JmIk z*j<=_B=A?kAmjiH4;`V$8-<0Tus0vT>j8NhY}15^V}kcF6-E{e!w3R-FQj7?SqFjz z6=#^U#L$XHTnmfx7Y~GC`JRxa2~BaS?7h-7ncX+Ps0k-b2?92*D?FeacQN;P?cdk3Uo;Wc7hC8oF5HDhCV>tBiw{cH^7^A9%3;+T^C%*167-aaP?Z@D%QuK#c$ylV~+eteD)UNsE2 zmgBq@!##7taOB!xc;APM;h{NTxc`G-_|R+&Eu`|}R14z9tL5@EeE<~KT}tLDG$pmE zG^cF7Zf@E96xHImYIZt*mF5U!ZgxHw_%XUVk|*YdgJ=G_(c&X7#ca-6&*c8|s$9c-7uOwKb+H zGB5T9s8ugknw8c-6{AVLS!oS0FE=C5U+xW5Nm(grlL}Oo&LL{?+NK7IOkpbSN^euC zS1<2rGO7g6pn}IoS}=jyS`%Ji3MJFfg$i?*9?U&@L>KbxpsQQ&jz|G@1x>v zXaIBsz>CuhX_b6^dT|=^aX;18xG!%n^*!-pi@$qk>F>uTdz|E_EmxabE~j4__z$5U BsA2#B diff --git a/packages/wasm/lib/utils.c b/packages/wasm/lib/utils.c deleted file mode 100644 index 8ba2cd36..00000000 --- a/packages/wasm/lib/utils.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * utils.c - utility functions for libdeflate - * - * Copyright 2016 Eric Biggers - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ - -#include "lib_common.h" - -extern unsigned char __heap_base; -static size_t __heap_tail = (size_t) &__heap_base; -static size_t __heap_mark = (size_t) &__heap_base; - -#define memory_size() __builtin_wasm_memory_size(0) - -#define memory_grow(delta) __builtin_wasm_memory_grow(0, delta) - -enum { - _mem_flag_used = 0xbf82583a, - _mem_flag_free = 0xab34d705 -}; - -__attribute__((visibility("default"))) void* __malloc(size_t n) { - n += (8 - (n % 4)) % 4; - // check if size is enough - size_t total = __heap_tail + n + 3 * sizeof(size_t); - size_t size = memory_size() << 16; - if (total > size) { - memory_grow((total >> 16) - (size >> 16) + 1); - } - unsigned int r = __heap_tail; - *((size_t*) r) = n; - r += sizeof(size_t); - *((size_t*) r) =_mem_flag_used; - r += sizeof(size_t); - __heap_tail = r + n; - *((size_t*) __heap_tail) = n; - __heap_tail += sizeof(size_t); - return (void*) r; -} - -__attribute__((visibility("default"))) void __free(void* p) { - size_t n; - // null case - if (!p) return; - size_t r=(size_t)p; - r -= sizeof(size_t); - // already free - if (*((size_t*) r) != _mem_flag_used) { - return; - } - // mark it as free - size_t flag = _mem_flag_free; - *((size_t*) r) = flag; - // calc ptr_tail - r -= sizeof(size_t); - n = *(size_t*) r; // size of current block - size_t ptr_tail = ((size_t) p) + n + sizeof(size_t); - // if not at tail return without moving __heap_tail - if (__heap_tail != ptr_tail) { - return; - } - __heap_tail = r; - while (r > (size_t) &__heap_base) { - r -= sizeof(size_t); - n = *(size_t*) r; // size of previous block - r -= n; - r -= sizeof(size_t); - flag = *((size_t*) r); - if (flag != _mem_flag_free) break; - r -= sizeof(size_t); - n = *(size_t*) r; // size of current block - __heap_tail = r; - } -} - -void * -libdeflate_aligned_malloc(size_t alignment, size_t size) -{ - void *ptr = __malloc(sizeof(void *) + alignment - 1 + size); - - if (ptr) { - void *orig_ptr = ptr; - - ptr = (void *)ALIGN((uintptr_t)ptr + sizeof(void *), alignment); - ((void **)ptr)[-1] = orig_ptr; - } - return ptr; -} - -void -libdeflate_aligned_free(void *ptr) -{ - __free((((void **)ptr)[-1])); -} - - -#ifdef LOGGING -char* __debug_log = 0; -char __debug_log_pos = 0; -__attribute__((visibility("default"))) char* __get_debug_log() { - return __debug_log; -} - -void __debug(char* str) { - if (!__debug_log) { - __debug_log = __malloc(1024); - } - - int i = 0; - while (str[i] != '\0') { - __debug_log[__debug_log_pos++] = str[i++]; - } - __debug_log[__debug_log_pos++] = '\n'; - __debug_log[__debug_log_pos] = '\0'; -} -#endif diff --git a/packages/wasm/lib/utils/allocator.c b/packages/wasm/lib/utils/allocator.c new file mode 100644 index 00000000..27304912 --- /dev/null +++ b/packages/wasm/lib/utils/allocator.c @@ -0,0 +1,74 @@ +#include "wasm.h" + +extern unsigned char __heap_base; +static size_t __heap_tail = (size_t) &__heap_base; +static size_t __heap_mark = (size_t) &__heap_base; + +#define memory_size() __builtin_wasm_memory_size(0) + +#define memory_grow(delta) __builtin_wasm_memory_grow(0, delta) + +enum { + _mem_flag_used = 0xbf82583a, + _mem_flag_free = 0xab34d705 +}; + +WASM_EXPORT void* __malloc(size_t n) { + n += (8 - (n % 4)) % 4; + // check if size is enough + size_t total = __heap_tail + n + 3 * sizeof(size_t); + size_t size = memory_size() << 16; + if (total > size) { + memory_grow((total >> 16) - (size >> 16) + 1); + } + unsigned int r = __heap_tail; + *((size_t*) r) = n; + r += sizeof(size_t); + *((size_t*) r) =_mem_flag_used; + r += sizeof(size_t); + __heap_tail = r + n; + *((size_t*) __heap_tail) = n; + __heap_tail += sizeof(size_t); + return (void*) r; +} + +WASM_EXPORT void __free(void* p) { + size_t n; + // null case + if (!p) return; + size_t r=(size_t)p; + r -= sizeof(size_t); + // already free + if (*((size_t*) r) != _mem_flag_used) { + return; + } + // mark it as free + size_t flag = _mem_flag_free; + *((size_t*) r) = flag; + // calc ptr_tail + r -= sizeof(size_t); + n = *(size_t*) r; // size of current block + size_t ptr_tail = ((size_t) p) + n + sizeof(size_t); + // if not at tail return without moving __heap_tail + if (__heap_tail != ptr_tail) { + return; + } + __heap_tail = r; + while (r > (size_t) &__heap_base) { + r -= sizeof(size_t); + n = *(size_t*) r; // size of previous block + r -= n; + r -= sizeof(size_t); + flag = *((size_t*) r); + if (flag != _mem_flag_free) break; + r -= sizeof(size_t); + n = *(size_t*) r; // size of current block + __heap_tail = r; + } +} + +uint8_t shared_out[256]; + +WASM_EXPORT uint8_t* __get_shared_out() { + return shared_out; +} \ No newline at end of file diff --git a/packages/wasm/lib/common_defs.h b/packages/wasm/lib/utils/common_defs.h similarity index 99% rename from packages/wasm/lib/common_defs.h rename to packages/wasm/lib/utils/common_defs.h index ce3eaf74..f5678845 100644 --- a/packages/wasm/lib/common_defs.h +++ b/packages/wasm/lib/utils/common_defs.h @@ -28,8 +28,6 @@ #ifndef COMMON_DEFS_H #define COMMON_DEFS_H -#include "libdeflate.h" - #include #include /* for size_t */ #include diff --git a/packages/wasm/lib/wasm.h b/packages/wasm/lib/wasm.h new file mode 100644 index 00000000..14360606 --- /dev/null +++ b/packages/wasm/lib/wasm.h @@ -0,0 +1,18 @@ +#ifndef MTCUTE_WASM_H +#define MTCUTE_WASM_H + +#include "common_defs.h" + +#define WASM_EXPORT __attribute__((visibility("default"))) + +// see utils/allocator.c +extern void* __malloc(size_t size); +extern void __free(void* ptr); + +#define memset(p,v,n) __builtin_memset(p,v,n) +#define memcpy(d,s,n) __builtin_memcpy(d,s,n) + +// more than enough for most of our cases +extern uint8_t shared_out[256]; + +#endif // MTCUTE_WASM_H \ No newline at end of file diff --git a/packages/wasm/src/index.ts b/packages/wasm/src/index.ts index 21a5080d..52867d14 100644 --- a/packages/wasm/src/index.ts +++ b/packages/wasm/src/index.ts @@ -6,11 +6,17 @@ export * from './types.js' let wasm!: MtcuteWasmModule let compressor!: number let decompressor!: number +let sharedOutPtr!: number +let sharedKeyPtr!: number +let sharedIvPtr!: number let cachedUint8Memory: Uint8Array | null = null function initCommon() { compressor = wasm.libdeflate_alloc_compressor(6) decompressor = wasm.libdeflate_alloc_decompressor() + sharedOutPtr = wasm.__get_shared_out() + sharedKeyPtr = wasm.__get_shared_key_buffer() + sharedIvPtr = wasm.__get_shared_iv_buffer() } function getUint8Memory() { @@ -58,7 +64,9 @@ export async function initAsync(input?: InitInput): Promise { export function deflateMaxSize(bytes: Uint8Array, size: number): Uint8Array | null { const outputPtr = wasm.__malloc(size) const inputPtr = wasm.__malloc(bytes.length) - getUint8Memory().set(bytes, inputPtr) + + const mem = getUint8Memory() + mem.set(bytes, inputPtr) const written = wasm.libdeflate_zlib_compress(compressor, inputPtr, bytes.length, outputPtr, size) wasm.__free(inputPtr) @@ -69,7 +77,7 @@ export function deflateMaxSize(bytes: Uint8Array, size: number): Uint8Array | nu return null } - const result = getUint8Memory().slice(outputPtr, outputPtr + written) + const result = mem.slice(outputPtr, outputPtr + written) wasm.__free(outputPtr) return result @@ -109,20 +117,18 @@ export function gunzip(bytes: Uint8Array): Uint8Array { * @param iv initialization vector (32 bytes) */ export function ige256Encrypt(data: Uint8Array, key: Uint8Array, iv: Uint8Array): Uint8Array { - const ptr = wasm.__malloc(key.length + iv.length + data.length + data.length) + const ptr = wasm.__malloc(data.length + data.length) - const keyPtr = ptr - const ivPtr = ptr + key.length - const inputPtr = ivPtr + iv.length + const inputPtr = ptr const outputPtr = inputPtr + data.length const mem = getUint8Memory() mem.set(data, inputPtr) - mem.set(key, keyPtr) - mem.set(iv, ivPtr) + mem.set(key, sharedKeyPtr) + mem.set(iv, sharedIvPtr) - wasm.ige256_encrypt(inputPtr, data.length, keyPtr, ivPtr, outputPtr) - const result = getUint8Memory().slice(outputPtr, outputPtr + data.length) + wasm.ige256_encrypt(inputPtr, data.length, outputPtr) + const result = mem.slice(outputPtr, outputPtr + data.length) wasm.__free(ptr) @@ -137,21 +143,19 @@ export function ige256Encrypt(data: Uint8Array, key: Uint8Array, iv: Uint8Array) * @param iv initialization vector (32 bytes) */ export function ige256Decrypt(data: Uint8Array, key: Uint8Array, iv: Uint8Array): Uint8Array { - const ptr = wasm.__malloc(key.length + iv.length + data.length + data.length) + const ptr = wasm.__malloc(data.length + data.length) - const keyPtr = ptr - const ivPtr = ptr + key.length - const inputPtr = ivPtr + iv.length + const inputPtr = ptr const outputPtr = inputPtr + data.length const mem = getUint8Memory() mem.set(data, inputPtr) - mem.set(key, keyPtr) - mem.set(iv, ivPtr) + mem.set(key, sharedKeyPtr) + mem.set(iv, sharedIvPtr) - wasm.ige256_decrypt(inputPtr, data.length, keyPtr, ivPtr, outputPtr) + wasm.ige256_decrypt(inputPtr, data.length, outputPtr) + const result = mem.slice(outputPtr, outputPtr + data.length) - const result = getUint8Memory().slice(outputPtr, outputPtr + data.length) wasm.__free(ptr) return result @@ -163,15 +167,10 @@ export function ige256Decrypt(data: Uint8Array, key: Uint8Array, iv: Uint8Array) * > **Note**: `freeCtr256` must be called on the returned context when it's no longer needed */ export function createCtr256(key: Uint8Array, iv: Uint8Array) { - const keyPtr = wasm.__malloc(key.length) - const ivPtr = wasm.__malloc(iv.length) - getUint8Memory().set(key, keyPtr) - getUint8Memory().set(iv, ivPtr) + getUint8Memory().set(key, sharedKeyPtr) + getUint8Memory().set(iv, sharedIvPtr) - const ctx = wasm.ctr256_alloc(keyPtr, ivPtr) - // pointers are "moved" and will be handled by c code - - return ctx + return wasm.ctr256_alloc() } /** @@ -203,6 +202,42 @@ export function ctr256(ctx: number, data: Uint8Array): Uint8Array { return result } +/** + * Calculate a SHA-256 hash + * + * @param data data to hash + */ +export function sha256(data: Uint8Array): Uint8Array { + const { __malloc, __free } = wasm + const inputPtr = __malloc(data.length) + + const mem = getUint8Memory() + mem.set(data, inputPtr) + + wasm.sha256(inputPtr, data.length) + __free(inputPtr) + + return mem.slice(sharedOutPtr, sharedOutPtr + 32) +} + +/** + * Calculate a SHA-1 hash + * + * @param data data to hash + */ +export function sha1(data: Uint8Array): Uint8Array { + const { __malloc, __free } = wasm + const inputPtr = __malloc(data.length) + + const mem = getUint8Memory() + mem.set(data, inputPtr) + + wasm.sha1(inputPtr, data.length) + __free(inputPtr) + + return mem.slice(sharedOutPtr, sharedOutPtr + 20) +} + /** * Get the WASM module instance. * diff --git a/packages/wasm/src/types.ts b/packages/wasm/src/types.ts index 9828a771..55ce1b60 100644 --- a/packages/wasm/src/types.ts +++ b/packages/wasm/src/types.ts @@ -2,6 +2,11 @@ export interface MtcuteWasmModule { memory: WebAssembly.Memory __malloc: (size: number) => number __free: (ptr: number) => void + + __get_shared_out: () => number + __get_shared_key_buffer: () => number + __get_shared_iv_buffer: () => number + libdeflate_alloc_decompressor: () => number libdeflate_alloc_compressor: (level: number) => number @@ -11,13 +16,16 @@ export interface MtcuteWasmModule { libdeflate_zlib_compress: (ctx: number, src: number, srcLen: number, dst: number, dstLen: number) => number - ige256_encrypt: (data: number, dataLen: number, key: number, iv: number, out: number) => void + ige256_encrypt: (data: number, dataLen: number, out: number) => void - ige256_decrypt: (data: number, dataLen: number, key: number, iv: number, out: number) => void + ige256_decrypt: (data: number, dataLen: number, out: number) => void - ctr256_alloc: (key: number, iv: number) => number + ctr256_alloc: () => number ctr256_free: (ctx: number) => void ctr256: (ctx: number, data: number, dataLen: number, out: number) => number + + sha256: (data: number, dataLen: number) => void + sha1: (data: number, dataLen: number) => void } export type SyncInitInput = BufferSource | WebAssembly.Module diff --git a/packages/wasm/tests/hash.spec.ts b/packages/wasm/tests/hash.spec.ts new file mode 100644 index 00000000..31d606a5 --- /dev/null +++ b/packages/wasm/tests/hash.spec.ts @@ -0,0 +1,49 @@ +/* eslint-disable no-restricted-globals */ +import { expect } from 'chai' +import { before, describe } from 'mocha' + +import { __getWasm, initAsync, sha1, sha256 } from '../src/index.js' + +before(async () => { + await initAsync() +}) + +describe('sha256', () => { + it('should correctly calculate sha-256 hash', () => { + const hash = sha256(Buffer.from('abc')) + + expect(Buffer.from(hash).toString('hex')).to.equal( + 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', + ) + }) + + it('should not leak memory', () => { + const mem = __getWasm().memory.buffer + const memSize = mem.byteLength + + for (let i = 0; i < 100; i++) { + sha256(Buffer.from('abc')) + } + + expect(mem.byteLength).to.equal(memSize) + }) +}) + +describe('sha1', () => { + it('should correctly calculate sha-1 hash', () => { + const hash = sha1(Buffer.from('abc')) + + expect(Buffer.from(hash).toString('hex')).to.equal('a9993e364706816aba3e25717850c26c9cd0d89d') + }) + + it('should not leak memory', () => { + const mem = __getWasm().memory.buffer + const memSize = mem.byteLength + + for (let i = 0; i < 100; i++) { + sha1(Buffer.from('abc')) + } + + expect(mem.byteLength).to.equal(memSize) + }) +})