Files
Baileys/src/Signal/libsignal.ts
João Lucas de Oliveira Lopes 482db6edc5 refactor: migrate WASignalGroup to TypeScript and remove deprecated files (#1320)
* refactor: migrate WASignalGroup to TypeScript and remove deprecated files

* chore: remove WASignalGroup JavaScript files from package.json

* refactor: update SenderKeyStore and SenderKeyStateStructure interfaces to export and add deserialize method. Fix types

* chore: lint issue

* refactor: improve constructor type checking and getSeed method in SenderChainKey

* refactor: update key handling to use Buffer for improved consistency

* signal: consistent naming and dir structure + add some missing types

* fix: lint issues

---------

Co-authored-by: Rajeh Taher <rajeh@reforward.dev>
2025-06-21 18:02:15 +03:00

162 lines
5.3 KiB
TypeScript

import * as libsignal from 'libsignal'
import { SignalAuthState } from '../Types'
import { SignalRepository } from '../Types/Signal'
import { generateSignalPubKey } from '../Utils'
import { jidDecode } from '../WABinary'
import type { SenderKeyStore } from './Group/group_cipher'
import { SenderKeyName } from './Group/sender-key-name'
import { SenderKeyRecord } from './Group/sender-key-record'
import { GroupCipher, GroupSessionBuilder, SenderKeyDistributionMessage } from './Group'
export function makeLibSignalRepository(auth: SignalAuthState): SignalRepository {
const storage: SenderKeyStore = signalStorage(auth)
return {
decryptGroupMessage({ group, authorJid, msg }) {
const senderName = jidToSignalSenderKeyName(group, authorJid)
const cipher = new GroupCipher(storage, senderName)
return cipher.decrypt(msg)
},
async processSenderKeyDistributionMessage({ item, authorJid }) {
const builder = new GroupSessionBuilder(storage)
if (!item.groupId) {
throw new Error('Group ID is required for sender key distribution message')
}
const senderName = jidToSignalSenderKeyName(item.groupId, authorJid)
const senderMsg = new SenderKeyDistributionMessage(
null,
null,
null,
null,
item.axolotlSenderKeyDistributionMessage
)
const senderNameStr = senderName.toString()
const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr])
if (!senderKey) {
await storage.storeSenderKey(senderName, new SenderKeyRecord())
}
await builder.process(senderName, senderMsg)
},
async decryptMessage({ jid, type, ciphertext }) {
const addr = jidToSignalProtocolAddress(jid)
const session = new libsignal.SessionCipher(storage, addr)
let result: Buffer
switch (type) {
case 'pkmsg':
result = await session.decryptPreKeyWhisperMessage(ciphertext)
break
case 'msg':
result = await session.decryptWhisperMessage(ciphertext)
break
default:
throw new Error(`Unknown message type: ${type}`)
}
return result
},
async encryptMessage({ jid, data }) {
const addr = jidToSignalProtocolAddress(jid)
const cipher = new libsignal.SessionCipher(storage, addr)
const { type: sigType, body } = await cipher.encrypt(data)
const type = sigType === 3 ? 'pkmsg' : 'msg'
return { type, ciphertext: Buffer.from(body, 'binary') }
},
async encryptGroupMessage({ group, meId, data }) {
const senderName = jidToSignalSenderKeyName(group, meId)
const builder = new GroupSessionBuilder(storage)
const senderNameStr = senderName.toString()
const { [senderNameStr]: senderKey } = await auth.keys.get('sender-key', [senderNameStr])
if (!senderKey) {
await storage.storeSenderKey(senderName, new SenderKeyRecord())
}
const senderKeyDistributionMessage = await builder.create(senderName)
const session = new GroupCipher(storage, senderName)
const ciphertext = await session.encrypt(data)
return {
ciphertext,
senderKeyDistributionMessage: senderKeyDistributionMessage.serialize()
}
},
async injectE2ESession({ jid, session }) {
const cipher = new libsignal.SessionBuilder(storage, jidToSignalProtocolAddress(jid))
await cipher.initOutgoing(session)
},
jidToSignalProtocolAddress(jid) {
return jidToSignalProtocolAddress(jid).toString()
}
}
}
const jidToSignalProtocolAddress = (jid: string) => {
const { user, device } = jidDecode(jid)!
return new libsignal.ProtocolAddress(user, device || 0)
}
const jidToSignalSenderKeyName = (group: string, user: string): SenderKeyName => {
return new SenderKeyName(group, jidToSignalProtocolAddress(user))
}
function signalStorage({ creds, keys }: SignalAuthState): SenderKeyStore & Record<string, any> {
return {
loadSession: async (id: string) => {
const { [id]: sess } = await keys.get('session', [id])
if (sess) {
return libsignal.SessionRecord.deserialize(sess)
}
},
storeSession: async (id: string, session: libsignal.SessionRecord) => {
await keys.set({ session: { [id]: session.serialize() } })
},
isTrustedIdentity: () => {
return true
},
loadPreKey: async (id: number | string) => {
const keyId = id.toString()
const { [keyId]: key } = await keys.get('pre-key', [keyId])
if (key) {
return {
privKey: Buffer.from(key.private),
pubKey: Buffer.from(key.public)
}
}
},
removePreKey: (id: number) => keys.set({ 'pre-key': { [id]: null } }),
loadSignedPreKey: () => {
const key = creds.signedPreKey
return {
privKey: Buffer.from(key.keyPair.private),
pubKey: Buffer.from(key.keyPair.public)
}
},
loadSenderKey: async (senderKeyName: SenderKeyName) => {
const keyId = senderKeyName.toString()
const { [keyId]: key } = await keys.get('sender-key', [keyId])
if (key) {
return SenderKeyRecord.deserialize(key)
}
return new SenderKeyRecord()
},
storeSenderKey: async (senderKeyName: SenderKeyName, key: SenderKeyRecord) => {
const keyId = senderKeyName.toString()
const serialized = JSON.stringify(key.serialize())
await keys.set({ 'sender-key': { [keyId]: Buffer.from(serialized, 'utf-8') } })
},
getOurRegistrationId: () => creds.registrationId,
getOurIdentity: () => {
const { signedIdentityKey } = creds
return {
privKey: Buffer.from(signedIdentityKey.private),
pubKey: generateSignalPubKey(signedIdentityKey.public)
}
}
}
}