refactor(types): Strengthen Type Safety in Signal Handling & Core Utilities (#1764)

* refactor(types): Strengthen Type Safety in Signal Handling & Core Utilities

* chore: revert typescript and move types to libsignal

* chore: update libsignal dependency resolution and checksum

* fix: a minor regression

* chore: update libsignal resolution and checksum
This commit is contained in:
João Lucas de Oliveira Lopes
2025-09-13 09:24:57 -03:00
committed by GitHub
parent e305d4848e
commit 421ca2eb92
9 changed files with 33 additions and 37 deletions
-1
View File
@@ -1,4 +1,3 @@
/* @ts-ignore */
import { decrypt, encrypt } from 'libsignal/src/crypto'
import { SenderKeyMessage } from './sender-key-message'
import { SenderKeyName } from './sender-key-name'
-1
View File
@@ -1,5 +1,4 @@
import * as nodeCrypto from 'crypto'
/* @ts-ignore */
import { generateKeyPair } from 'libsignal/src/curve'
type KeyPairType = ReturnType<typeof generateKeyPair>
+1 -2
View File
@@ -1,4 +1,3 @@
/* @ts-ignore */
import { calculateMAC } from 'libsignal/src/crypto'
import { SenderMessageKey } from './sender-message-key'
@@ -8,7 +7,7 @@ export class SenderChainKey {
private readonly iteration: number
private readonly chainKey: Buffer
constructor(iteration: number, chainKey: any) {
constructor(iteration: number, chainKey: Uint8Array | Buffer) {
this.iteration = iteration
if (Buffer.isBuffer(chainKey)) {
// backported from @MartinSchere's PR
-1
View File
@@ -1,4 +1,3 @@
/* @ts-ignore */
import { calculateSignature, verifySignature } from 'libsignal/src/curve'
import { proto } from '../../../WAProto/index.js'
import { CiphertextMessage } from './ciphertext-message'
-1
View File
@@ -1,4 +1,3 @@
/* @ts-ignore */
import { deriveSecrets } from 'libsignal/src/crypto'
export class SenderMessageKey {
+5 -8
View File
@@ -1,6 +1,4 @@
/* @ts-ignore */
import * as libsignal from 'libsignal'
/* @ts-ignore */
import { LRUCache } from 'lru-cache'
import type { SignalAuthState, SignalKeyStoreWithTransaction } from '../Types'
import type { SignalRepository } from '../Types/Signal'
@@ -28,7 +26,7 @@ export function makeLibSignalRepository(
const parsedKeys = auth.keys as SignalKeyStoreWithTransaction
function isLikelySyncMessage(addr: any): boolean {
function isLikelySyncMessage(addr: libsignal.ProtocolAddress): boolean {
const key = addr.toString()
// Only bypass for WhatsApp system addresses, not regular user contacts
@@ -285,7 +283,7 @@ export function makeLibSignalRepository(
return repository
}
const jidToSignalProtocolAddress = (jid: string) => {
const jidToSignalProtocolAddress = (jid: string): libsignal.ProtocolAddress => {
const decoded = jidDecode(jid)!
const { user, device, server } = decoded
@@ -303,7 +301,7 @@ const jidToSignalSenderKeyName = (group: string, user: string): SenderKeyName =>
function signalStorage(
{ creds, keys }: SignalAuthState,
lidMapping: LIDMappingStore
): SenderKeyStore & Record<string, any> {
): SenderKeyStore & libsignal.SignalStorage {
return {
loadSession: async (id: string) => {
try {
@@ -340,8 +338,7 @@ function signalStorage(
return null
},
// TODO: Replace with libsignal.SessionRecord when type exports are added to libsignal
storeSession: async (id: string, session: any) => {
storeSession: async (id: string, session: libsignal.SessionRecord) => {
await keys.set({ session: { [id]: session.serialize() } })
},
isTrustedIdentity: () => {
@@ -384,7 +381,7 @@ function signalStorage(
const { signedIdentityKey } = creds
return {
privKey: Buffer.from(signedIdentityKey.private),
pubKey: generateSignalPubKey(signedIdentityKey.public)
pubKey: Buffer.from(generateSignalPubKey(signedIdentityKey.public))
}
}
}
+18 -14
View File
@@ -1,6 +1,5 @@
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from 'crypto'
/* @ts-ignore */
import * as libsignal from 'libsignal'
import * as curve from 'libsignal/src/curve'
import { KEY_BUNDLE_TYPE } from '../Defaults'
import type { KeyPair } from '../Types'
@@ -13,21 +12,21 @@ export const generateSignalPubKey = (pubKey: Uint8Array | Buffer) =>
export const Curve = {
generateKeyPair: (): KeyPair => {
const { pubKey, privKey } = libsignal.curve.generateKeyPair()
const { pubKey, privKey } = curve.generateKeyPair()
return {
private: Buffer.from(privKey),
// remove version byte
public: Buffer.from((pubKey as Uint8Array).slice(1))
public: Buffer.from(pubKey.slice(1))
}
},
sharedKey: (privateKey: Uint8Array, publicKey: Uint8Array) => {
const shared = libsignal.curve.calculateAgreement(generateSignalPubKey(publicKey), privateKey)
const shared = curve.calculateAgreement(generateSignalPubKey(publicKey), privateKey)
return Buffer.from(shared)
},
sign: (privateKey: Uint8Array, buf: Uint8Array) => libsignal.curve.calculateSignature(privateKey, buf),
sign: (privateKey: Uint8Array, buf: Uint8Array) => curve.calculateSignature(privateKey, buf),
verify: (pubKey: Uint8Array, message: Uint8Array, signature: Uint8Array) => {
try {
libsignal.curve.verifySignature(generateSignalPubKey(pubKey), message, signature)
curve.verifySignature(generateSignalPubKey(pubKey), message, signature)
return true
} catch (error) {
return false
@@ -129,15 +128,18 @@ export async function hkdf(
expandedLength: number,
info: { salt?: Buffer; info?: string }
): Promise<Buffer> {
// Ensure we have a Uint8Array for the key material
const inputKeyMaterial = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)
// Normalize to a Uint8Array whose underlying buffer is a regular ArrayBuffer (not ArrayBufferLike)
// Cloning via new Uint8Array(...) guarantees the generic parameter is ArrayBuffer which satisfies WebCrypto types.
const inputKeyMaterial = new Uint8Array(buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer))
// Set default values if not provided
const salt = info.salt ? new Uint8Array(info.salt) : new Uint8Array(0)
const infoBytes = info.info ? new TextEncoder().encode(info.info) : new Uint8Array(0)
// Import the input key material
const importedKey = await subtle.importKey('raw', inputKeyMaterial, { name: 'HKDF' }, false, ['deriveBits'])
// Import the input key material (cast to BufferSource to appease TS DOM typings)
const importedKey = await subtle.importKey('raw', inputKeyMaterial as BufferSource, { name: 'HKDF' }, false, [
'deriveBits'
])
// Derive bits using HKDF
const derivedBits = await subtle.deriveBits(
@@ -158,17 +160,19 @@ export async function derivePairingCodeKey(pairingCode: string, salt: Buffer): P
// Convert inputs to formats Web Crypto API can work with
const encoder = new TextEncoder()
const pairingCodeBuffer = encoder.encode(pairingCode)
const saltBuffer = salt instanceof Uint8Array ? salt : new Uint8Array(salt)
const saltBuffer = new Uint8Array(salt instanceof Uint8Array ? salt : new Uint8Array(salt))
// Import the pairing code as key material
const keyMaterial = await subtle.importKey('raw', pairingCodeBuffer, { name: 'PBKDF2' }, false, ['deriveBits'])
const keyMaterial = await subtle.importKey('raw', pairingCodeBuffer as BufferSource, { name: 'PBKDF2' }, false, [
'deriveBits'
])
// Derive bits using PBKDF2 with the same parameters
// 2 << 16 = 131,072 iterations
const derivedBits = await subtle.deriveBits(
{
name: 'PBKDF2',
salt: saltBuffer,
salt: saltBuffer as BufferSource,
iterations: 2 << 16,
hash: 'SHA-256'
},
+1 -1
View File
@@ -17,7 +17,7 @@ export const downloadHistory = async (msg: proto.Message.IHistorySyncNotificatio
bufferArray.push(chunk)
}
let buffer = Buffer.concat(bufferArray)
let buffer: Buffer = Buffer.concat(bufferArray)
// decompress buffer
buffer = await inflatePromise(buffer)
+8 -8
View File
@@ -7339,11 +7339,11 @@ __metadata:
"libsignal@git+https://github.com/whiskeysockets/libsignal-node":
version: 2.0.1
resolution: "libsignal@https://github.com/whiskeysockets/libsignal-node.git#commit=4d08331a833727c338c1a90041d17b870210dfae"
resolution: "libsignal@https://github.com/whiskeysockets/libsignal-node.git#commit=e81ecfc32eb74951d789ab37f7e341ab66d5fff1"
dependencies:
curve25519-js: "npm:^0.0.4"
protobufjs: "npm:6.8.8"
checksum: 10c0/0c6bf1bb2255c3c4eb1e1c35f8a51b5a4b38b2c334178991add7f0e232ebc80c061d1571087348eaf57c34aa8db77d09bcf29eecbbe0c0a49b66aede546952ef
checksum: 10c0/d1ae7d8a5fadd6bb1c486d1b2ebc388967fee57c13f52b473127c1cbd9cd647b44545ff07c2b9cc49b3dea4e25ccfcfece31c526fdbdbf065837c85d189e97a0
languageName: node
linkType: hard
@@ -10557,22 +10557,22 @@ __metadata:
linkType: hard
"typescript@npm:^5.8.2":
version: 5.8.3
resolution: "typescript@npm:5.8.3"
version: 5.9.2
resolution: "typescript@npm:5.9.2"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10c0/5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48
checksum: 10c0/cd635d50f02d6cf98ed42de2f76289701c1ec587a363369255f01ed15aaf22be0813226bff3c53e99d971f9b540e0b3cc7583dbe05faded49b1b0bed2f638a18
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A^5.8.2#optional!builtin<compat/typescript>":
version: 5.8.3
resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>::version=5.8.3&hash=5786d5"
version: 5.9.2
resolution: "typescript@patch:typescript@npm%3A5.9.2#optional!builtin<compat/typescript>::version=5.9.2&hash=5786d5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10c0/39117e346ff8ebd87ae1510b3a77d5d92dae5a89bde588c747d25da5c146603a99c8ee588c7ef80faaf123d89ed46f6dbd918d534d641083177d5fac38b8a1cb
checksum: 10c0/34d2a8e23eb8e0d1875072064d5e1d9c102e0bdce56a10a25c0b917b8aa9001a9cf5c225df12497e99da107dc379360bc138163c66b55b95f5b105b50578067e
languageName: node
linkType: hard