Files
InfiniteAPI/src/Signal/Group/sender-key-message.ts
T
2025-10-05 22:51:13 +03:00

97 lines
2.9 KiB
TypeScript

import { calculateSignature, verifySignature } from 'libsignal/src/curve'
import { proto } from '../../../WAProto/index.js'
import { CiphertextMessage } from './ciphertext-message'
interface SenderKeyMessageStructure {
id: number
iteration: number
ciphertext: string | Buffer
}
export class SenderKeyMessage extends CiphertextMessage {
private readonly SIGNATURE_LENGTH = 64
private readonly messageVersion: number
private readonly keyId: number
private readonly iteration: number
private readonly ciphertext: Uint8Array
private readonly signature: Uint8Array
private readonly serialized: Uint8Array
constructor(
keyId?: number | null,
iteration?: number | null,
ciphertext?: Uint8Array | null,
signatureKey?: Uint8Array | null,
serialized?: Uint8Array | null
) {
super()
if (serialized) {
const version = serialized[0]!
const message = serialized.slice(1, serialized.length - this.SIGNATURE_LENGTH)
const signature = serialized.slice(-1 * this.SIGNATURE_LENGTH)
const senderKeyMessage = proto.SenderKeyMessage.decode(message).toJSON() as SenderKeyMessageStructure
this.serialized = serialized
this.messageVersion = (version & 0xff) >> 4
this.keyId = senderKeyMessage.id
this.iteration = senderKeyMessage.iteration
this.ciphertext =
typeof senderKeyMessage.ciphertext === 'string'
? Buffer.from(senderKeyMessage.ciphertext, 'base64')
: senderKeyMessage.ciphertext
this.signature = signature
} else {
const version = (((this.CURRENT_VERSION << 4) | this.CURRENT_VERSION) & 0xff) % 256
const ciphertextBuffer = Buffer.from(ciphertext!)
const message = proto.SenderKeyMessage.encode(
proto.SenderKeyMessage.create({
id: keyId!,
iteration: iteration!,
ciphertext: ciphertextBuffer
})
).finish()
const signature = this.getSignature(signatureKey!, Buffer.concat([Buffer.from([version]), message]))
this.serialized = Buffer.concat([Buffer.from([version]), message, Buffer.from(signature)])
this.messageVersion = this.CURRENT_VERSION
this.keyId = keyId!
this.iteration = iteration!
this.ciphertext = ciphertextBuffer
this.signature = signature
}
}
public getKeyId(): number {
return this.keyId
}
public getIteration(): number {
return this.iteration
}
public getCipherText(): Uint8Array {
return this.ciphertext
}
public verifySignature(signatureKey: Uint8Array): void {
const part1 = this.serialized.slice(0, this.serialized.length - this.SIGNATURE_LENGTH)
const part2 = this.serialized.slice(-1 * this.SIGNATURE_LENGTH)
const res = verifySignature(signatureKey, part1, part2)
if (!res) throw new Error('Invalid signature!')
}
private getSignature(signatureKey: Uint8Array, serialized: Uint8Array): Uint8Array {
return Buffer.from(calculateSignature(signatureKey, serialized))
}
public serialize(): Uint8Array {
return this.serialized
}
public getType(): number {
return 4
}
}