@neykoor/libsignal-node 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +130 -0
- package/lib/base-key-type.d.ts +4 -0
- package/lib/base-key-type.js +5 -0
- package/lib/chain-type.d.ts +4 -0
- package/lib/chain-type.js +5 -0
- package/lib/crypto.d.ts +6 -0
- package/lib/crypto.js +73 -0
- package/lib/curve.d.ts +9 -0
- package/lib/curve.js +112 -0
- package/lib/direction.d.ts +4 -0
- package/lib/direction.js +5 -0
- package/lib/errors.d.ts +16 -0
- package/lib/errors.js +28 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.js +16 -0
- package/lib/keyhelper.d.ts +18 -0
- package/lib/keyhelper.js +52 -0
- package/lib/logger.d.ts +6 -0
- package/lib/logger.js +11 -0
- package/lib/memory-storage.d.ts +25 -0
- package/lib/memory-storage.js +68 -0
- package/lib/numeric-fingerprint.d.ts +5 -0
- package/lib/numeric-fingerprint.js +54 -0
- package/lib/prekey-bundle-validator.d.ts +2 -0
- package/lib/prekey-bundle-validator.js +44 -0
- package/lib/protobufs.d.ts +5 -0
- package/lib/protobufs.js +3 -0
- package/lib/protocol-address.d.ts +8 -0
- package/lib/protocol-address.js +31 -0
- package/lib/queue-job.d.ts +3 -0
- package/lib/queue-job.js +49 -0
- package/lib/session-builder.d.ts +12 -0
- package/lib/session-builder.js +153 -0
- package/lib/session-cipher.d.ts +29 -0
- package/lib/session-cipher.js +326 -0
- package/lib/session-record.d.ts +109 -0
- package/lib/session-record.js +286 -0
- package/lib/types.d.ts +42 -0
- package/lib/types.js +1 -0
- package/lib/util.d.ts +2 -0
- package/lib/util.js +10 -0
- package/lib/whisper-text-protocol.d.ts +3 -0
- package/lib/whisper-text-protocol.js +483 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Neykoor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="./assets/banner.png" alt="libsignal-node-ts banner" width="100%" />
|
|
4
|
+
|
|
5
|
+
# libsignal-node-ts
|
|
6
|
+
|
|
7
|
+
**Port 100% TypeScript de `libsignal-node`, compatible con [BaileysX](https://github.com/Neykoor/BaileysX)**
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/libsignal-node-ts)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
11
|
+
[](https://nodejs.org/api/esm.html)
|
|
12
|
+
[](https://nodejs.org/)
|
|
13
|
+
[](./LICENSE)
|
|
14
|
+
[]()
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## ✨ ¿Qué es esto?
|
|
21
|
+
|
|
22
|
+
`libsignal-node-ts` es una reescritura completa en **TypeScript** de la librería [`libsignal-node`](https://github.com/this-xys/libsignal-node) (Signal Protocol / Double Ratchet), pensada como **reemplazo directo** de la dependencia `libsignal` que usa BaileysX.
|
|
23
|
+
|
|
24
|
+
Mismo API, misma criptografía, cero `@ts-ignore`, tipado de punta a punta.
|
|
25
|
+
|
|
26
|
+
## 🚀 Características
|
|
27
|
+
|
|
28
|
+
- 🔒 Implementación completa del **Double Ratchet** (X3DH, sesiones, ratcheting)
|
|
29
|
+
- 📦 Tipado estricto (`strict: true`, `noUncheckedIndexedAccess`)
|
|
30
|
+
- ⚡ ESM nativo
|
|
31
|
+
- 🔁 API idéntica a `libsignal-node` — `ProtocolAddress`, `SessionBuilder`, `SessionCipher`, `SessionRecord`
|
|
32
|
+
- 🧩 Drop-in: solo cambias el import, no tocas el resto de tu código
|
|
33
|
+
|
|
34
|
+
## 📦 Instalación
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install libsignal-node-ts
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Dependencias en tiempo de ejecución: `curve25519-js` y `protobufjs` (se instalan solas junto con el paquete).
|
|
41
|
+
|
|
42
|
+
## 📂 Estructura del código fuente
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
libsignal-node-ts/
|
|
46
|
+
├── src/
|
|
47
|
+
│ ├── index.ts # punto de entrada
|
|
48
|
+
│ ├── protocol-address.ts
|
|
49
|
+
│ ├── session-builder.ts
|
|
50
|
+
│ ├── session-cipher.ts
|
|
51
|
+
│ ├── session-record.ts
|
|
52
|
+
│ ├── curve.ts
|
|
53
|
+
│ ├── curve25519-js.d.ts
|
|
54
|
+
│ ├── crypto.ts
|
|
55
|
+
│ ├── keyhelper.ts
|
|
56
|
+
│ ├── numeric-fingerprint.ts
|
|
57
|
+
│ ├── prekey-bundle-validator.ts
|
|
58
|
+
│ ├── memory-storage.ts
|
|
59
|
+
│ ├── logger.ts
|
|
60
|
+
│ ├── errors.ts
|
|
61
|
+
│ ├── direction.ts
|
|
62
|
+
│ ├── base-key-type.ts
|
|
63
|
+
│ ├── chain-type.ts
|
|
64
|
+
│ ├── queue-job.ts
|
|
65
|
+
│ ├── types.ts
|
|
66
|
+
│ ├── protobufs.ts
|
|
67
|
+
│ ├── whisper-text-protocol.js # protobufs generados (ESM)
|
|
68
|
+
│ └── whisper-text-protocol.d.ts
|
|
69
|
+
├── lib/ # salida compilada (lo que se publica en npm)
|
|
70
|
+
├── package.json
|
|
71
|
+
└── tsconfig.json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 🔧 Uso en BaileysX
|
|
75
|
+
|
|
76
|
+
En `src/Signal/libsignal.ts`, reemplaza:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// @ts-ignore
|
|
80
|
+
import * as libsignal from 'libsignal'
|
|
81
|
+
// @ts-ignore
|
|
82
|
+
import { PreKeyWhisperMessage } from 'libsignal/lib/protobufs.js'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
por:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import * as libsignal from 'libsignal-node-ts'
|
|
89
|
+
import { protobufs } from 'libsignal-node-ts'
|
|
90
|
+
const { PreKeyWhisperMessage } = protobufs
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Y en el `package.json` de tu proyecto, quita la dependencia `libsignal` y agrega:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
"libsignal-node-ts": "^1.0.0"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 📖 Uso básico
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { ProtocolAddress, SessionBuilder, SessionCipher, MemorySignalStorage, keyhelper } from 'libsignal-node-ts'
|
|
103
|
+
|
|
104
|
+
const identityKeyPair = keyhelper.generateIdentityKeyPair()
|
|
105
|
+
const registrationId = keyhelper.generateRegistrationId()
|
|
106
|
+
const storage = new MemorySignalStorage(identityKeyPair, registrationId)
|
|
107
|
+
const addr = new ProtocolAddress('5215512345678', 1)
|
|
108
|
+
|
|
109
|
+
const builder = new SessionBuilder(storage, addr)
|
|
110
|
+
await builder.initOutgoing(deviceBundle)
|
|
111
|
+
|
|
112
|
+
const cipher = new SessionCipher(storage, addr)
|
|
113
|
+
const { type, body } = await cipher.encrypt(Buffer.from('hola mundo'))
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`storage` debe implementar la interfaz `SignalStorage` exportada desde el paquete (o puedes usar `MemorySignalStorage`, incluida para pruebas rápidas y almacenamiento en memoria).
|
|
117
|
+
|
|
118
|
+
## 🧪 Compilar desde el código fuente
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npm run build
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Compila `src/` a `lib/` con `tsc` en modo `strict` y corrige las extensiones `.js` de los imports ESM con `tsc-esm-fix`.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
<div align="center">
|
|
129
|
+
Hecho con ☕ para el ecosistema <b>Eris-MD</b> / <b>BaileysX</b>
|
|
130
|
+
</div>
|
package/lib/crypto.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function encrypt(key: Uint8Array, data: Uint8Array, iv: Uint8Array): Buffer;
|
|
2
|
+
export declare function decrypt(key: Uint8Array, data: Uint8Array, iv: Uint8Array): Buffer;
|
|
3
|
+
export declare function calculateMAC(key: Uint8Array, data: Uint8Array): Buffer;
|
|
4
|
+
export declare function hash(data: Uint8Array): Buffer;
|
|
5
|
+
export declare function deriveSecrets(input: Uint8Array, salt: Uint8Array, info: Uint8Array, chunks?: number): Buffer[];
|
|
6
|
+
export declare function verifyMAC(data: Uint8Array, key: Uint8Array, mac: Uint8Array, length: number): void;
|
package/lib/crypto.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as nodeCrypto from 'crypto';
|
|
2
|
+
function assertBuffer(value) {
|
|
3
|
+
if (!(value instanceof Uint8Array)) {
|
|
4
|
+
throw TypeError(`Expected Uint8Array instead of: ${value?.constructor?.name}`);
|
|
5
|
+
}
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
export function encrypt(key, data, iv) {
|
|
9
|
+
assertBuffer(key);
|
|
10
|
+
assertBuffer(data);
|
|
11
|
+
assertBuffer(iv);
|
|
12
|
+
const cipher = nodeCrypto.createCipheriv('aes-256-cbc', key, iv);
|
|
13
|
+
return Buffer.concat([cipher.update(data), cipher.final()]);
|
|
14
|
+
}
|
|
15
|
+
export function decrypt(key, data, iv) {
|
|
16
|
+
assertBuffer(key);
|
|
17
|
+
assertBuffer(data);
|
|
18
|
+
assertBuffer(iv);
|
|
19
|
+
const decipher = nodeCrypto.createDecipheriv('aes-256-cbc', key, iv);
|
|
20
|
+
return Buffer.concat([decipher.update(data), decipher.final()]);
|
|
21
|
+
}
|
|
22
|
+
export function calculateMAC(key, data) {
|
|
23
|
+
assertBuffer(key);
|
|
24
|
+
assertBuffer(data);
|
|
25
|
+
const hmac = nodeCrypto.createHmac('sha256', key);
|
|
26
|
+
hmac.update(data);
|
|
27
|
+
return Buffer.from(hmac.digest());
|
|
28
|
+
}
|
|
29
|
+
export function hash(data) {
|
|
30
|
+
assertBuffer(data);
|
|
31
|
+
const sha512 = nodeCrypto.createHash('sha512');
|
|
32
|
+
sha512.update(data);
|
|
33
|
+
return sha512.digest();
|
|
34
|
+
}
|
|
35
|
+
export function deriveSecrets(input, salt, info, chunks) {
|
|
36
|
+
assertBuffer(input);
|
|
37
|
+
assertBuffer(salt);
|
|
38
|
+
assertBuffer(info);
|
|
39
|
+
if (salt.byteLength !== 32) {
|
|
40
|
+
throw new Error('Got salt of incorrect length');
|
|
41
|
+
}
|
|
42
|
+
const count = chunks || 3;
|
|
43
|
+
if (count < 1 || count > 3) {
|
|
44
|
+
throw new Error('chunks must be between 1 and 3');
|
|
45
|
+
}
|
|
46
|
+
const PRK = calculateMAC(salt, input);
|
|
47
|
+
const infoArray = new Uint8Array(info.byteLength + 1 + 32);
|
|
48
|
+
infoArray.set(info, 32);
|
|
49
|
+
infoArray[infoArray.length - 1] = 1;
|
|
50
|
+
const signed = [calculateMAC(PRK, Buffer.from(infoArray.slice(32)))];
|
|
51
|
+
if (count > 1) {
|
|
52
|
+
infoArray.set(signed[signed.length - 1]);
|
|
53
|
+
infoArray[infoArray.length - 1] = 2;
|
|
54
|
+
signed.push(calculateMAC(PRK, Buffer.from(infoArray)));
|
|
55
|
+
}
|
|
56
|
+
if (count > 2) {
|
|
57
|
+
infoArray.set(signed[signed.length - 1]);
|
|
58
|
+
infoArray[infoArray.length - 1] = 3;
|
|
59
|
+
signed.push(calculateMAC(PRK, Buffer.from(infoArray)));
|
|
60
|
+
}
|
|
61
|
+
PRK.fill(0);
|
|
62
|
+
infoArray.fill(0);
|
|
63
|
+
return signed;
|
|
64
|
+
}
|
|
65
|
+
export function verifyMAC(data, key, mac, length) {
|
|
66
|
+
const calculatedMac = calculateMAC(key, data).slice(0, length);
|
|
67
|
+
if (mac.length !== length || calculatedMac.length !== length) {
|
|
68
|
+
throw new Error('Bad MAC length');
|
|
69
|
+
}
|
|
70
|
+
if (!nodeCrypto.timingSafeEqual(mac, calculatedMac)) {
|
|
71
|
+
throw new Error('Bad MAC');
|
|
72
|
+
}
|
|
73
|
+
}
|
package/lib/curve.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface KeyPair {
|
|
2
|
+
pubKey: Buffer;
|
|
3
|
+
privKey: Buffer;
|
|
4
|
+
}
|
|
5
|
+
export declare function getPublicFromPrivateKey(privKey: Uint8Array): Buffer;
|
|
6
|
+
export declare function generateKeyPair(): KeyPair;
|
|
7
|
+
export declare function calculateAgreement(pubKeyInput: Uint8Array, privKey: Uint8Array): Buffer;
|
|
8
|
+
export declare function calculateSignature(privKey: Uint8Array, message: Uint8Array): Buffer;
|
|
9
|
+
export declare function verifySignature(pubKeyInput: Uint8Array, msg: Uint8Array, sig: Uint8Array): boolean;
|
package/lib/curve.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import * as curveJs from 'curve25519-js';
|
|
2
|
+
import * as nodeCrypto from 'crypto';
|
|
3
|
+
import { getLogger } from './logger.js';
|
|
4
|
+
const PUBLIC_KEY_DER_PREFIX = Buffer.from([48, 42, 48, 5, 6, 3, 43, 101, 110, 3, 33, 0]);
|
|
5
|
+
const PRIVATE_KEY_DER_PREFIX = Buffer.from([48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 110, 4, 34, 4, 32]);
|
|
6
|
+
const KEY_BUNDLE_TYPE = Buffer.from([5]);
|
|
7
|
+
function prefixKeyInPublicKey(pubKey) {
|
|
8
|
+
return Buffer.concat([KEY_BUNDLE_TYPE, pubKey]);
|
|
9
|
+
}
|
|
10
|
+
function validatePrivKey(privKey) {
|
|
11
|
+
if (privKey === undefined) {
|
|
12
|
+
throw new Error('Undefined private key');
|
|
13
|
+
}
|
|
14
|
+
if (!(privKey instanceof Uint8Array)) {
|
|
15
|
+
throw new Error(`Invalid private key type: ${privKey.constructor.name}`);
|
|
16
|
+
}
|
|
17
|
+
if (privKey.byteLength !== 32) {
|
|
18
|
+
throw new Error(`Incorrect private key length: ${privKey.byteLength}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function scrubPubKeyFormat(pubKey) {
|
|
22
|
+
if (!(pubKey instanceof Uint8Array)) {
|
|
23
|
+
throw new Error(`Invalid public key type: ${pubKey.constructor.name}`);
|
|
24
|
+
}
|
|
25
|
+
if (pubKey === undefined || ((pubKey.byteLength !== 33 || pubKey[0] !== 5) && pubKey.byteLength !== 32)) {
|
|
26
|
+
throw new Error('Invalid public key');
|
|
27
|
+
}
|
|
28
|
+
if (pubKey.byteLength === 33) {
|
|
29
|
+
return Buffer.from(pubKey.slice(1));
|
|
30
|
+
}
|
|
31
|
+
getLogger().warn('Expected pubkey of length 33, please report the ST and client that generated the pubkey');
|
|
32
|
+
return Buffer.from(pubKey);
|
|
33
|
+
}
|
|
34
|
+
function unclampEd25519PrivateKey(clampedSk) {
|
|
35
|
+
const unclampedSk = new Uint8Array(clampedSk);
|
|
36
|
+
unclampedSk[0] = unclampedSk[0] | 6;
|
|
37
|
+
unclampedSk[31] = unclampedSk[31] | 128;
|
|
38
|
+
unclampedSk[31] = unclampedSk[31] & ~64;
|
|
39
|
+
return unclampedSk;
|
|
40
|
+
}
|
|
41
|
+
export function getPublicFromPrivateKey(privKey) {
|
|
42
|
+
const unclampedPK = unclampEd25519PrivateKey(privKey);
|
|
43
|
+
const keyPair = curveJs.generateKeyPair(unclampedPK);
|
|
44
|
+
return prefixKeyInPublicKey(Buffer.from(keyPair.public));
|
|
45
|
+
}
|
|
46
|
+
export function generateKeyPair() {
|
|
47
|
+
try {
|
|
48
|
+
const { publicKey: publicDerBytes, privateKey: privateDerBytes } = nodeCrypto.generateKeyPairSync('x25519', {
|
|
49
|
+
publicKeyEncoding: { format: 'der', type: 'spki' },
|
|
50
|
+
privateKeyEncoding: { format: 'der', type: 'pkcs8' }
|
|
51
|
+
});
|
|
52
|
+
const pubKey = publicDerBytes.slice(PUBLIC_KEY_DER_PREFIX.length, PUBLIC_KEY_DER_PREFIX.length + 32);
|
|
53
|
+
const privKey = privateDerBytes.slice(PRIVATE_KEY_DER_PREFIX.length, PRIVATE_KEY_DER_PREFIX.length + 32);
|
|
54
|
+
return {
|
|
55
|
+
pubKey: prefixKeyInPublicKey(pubKey),
|
|
56
|
+
privKey
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
getLogger().debug(`x25519 native keygen failed, falling back to curve25519-js: ${e?.message}`);
|
|
61
|
+
const keyPair = curveJs.generateKeyPair(nodeCrypto.randomBytes(32));
|
|
62
|
+
return {
|
|
63
|
+
privKey: Buffer.from(keyPair.private),
|
|
64
|
+
pubKey: prefixKeyInPublicKey(Buffer.from(keyPair.public))
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function calculateAgreement(pubKeyInput, privKey) {
|
|
69
|
+
const pubKey = scrubPubKeyFormat(pubKeyInput);
|
|
70
|
+
validatePrivKey(privKey);
|
|
71
|
+
if (!pubKey || pubKey.byteLength !== 32) {
|
|
72
|
+
throw new Error('Invalid public key');
|
|
73
|
+
}
|
|
74
|
+
if (typeof nodeCrypto.diffieHellman === 'function') {
|
|
75
|
+
const nodePrivateKey = nodeCrypto.createPrivateKey({
|
|
76
|
+
key: Buffer.concat([PRIVATE_KEY_DER_PREFIX, privKey]),
|
|
77
|
+
format: 'der',
|
|
78
|
+
type: 'pkcs8'
|
|
79
|
+
});
|
|
80
|
+
const nodePublicKey = nodeCrypto.createPublicKey({
|
|
81
|
+
key: Buffer.concat([PUBLIC_KEY_DER_PREFIX, pubKey]),
|
|
82
|
+
format: 'der',
|
|
83
|
+
type: 'spki'
|
|
84
|
+
});
|
|
85
|
+
return nodeCrypto.diffieHellman({
|
|
86
|
+
privateKey: nodePrivateKey,
|
|
87
|
+
publicKey: nodePublicKey
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const secret = curveJs.sharedKey(privKey, pubKey);
|
|
91
|
+
return Buffer.from(secret);
|
|
92
|
+
}
|
|
93
|
+
export function calculateSignature(privKey, message) {
|
|
94
|
+
validatePrivKey(privKey);
|
|
95
|
+
if (!message) {
|
|
96
|
+
throw new Error('Invalid message');
|
|
97
|
+
}
|
|
98
|
+
return Buffer.from(curveJs.sign(privKey, message));
|
|
99
|
+
}
|
|
100
|
+
export function verifySignature(pubKeyInput, msg, sig) {
|
|
101
|
+
const pubKey = scrubPubKeyFormat(pubKeyInput);
|
|
102
|
+
if (!pubKey || pubKey.byteLength !== 32) {
|
|
103
|
+
throw new Error('Invalid public key');
|
|
104
|
+
}
|
|
105
|
+
if (!msg) {
|
|
106
|
+
throw new Error('Invalid message');
|
|
107
|
+
}
|
|
108
|
+
if (!sig || sig.byteLength !== 64) {
|
|
109
|
+
throw new Error('Invalid signature');
|
|
110
|
+
}
|
|
111
|
+
return curveJs.verify(pubKey, msg, sig);
|
|
112
|
+
}
|
package/lib/direction.js
ADDED
package/lib/errors.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class SignalError extends Error {
|
|
2
|
+
}
|
|
3
|
+
export declare class UntrustedIdentityKeyError extends SignalError {
|
|
4
|
+
addr: string;
|
|
5
|
+
identityKey?: Buffer;
|
|
6
|
+
constructor(addr: string, identityKey?: Buffer);
|
|
7
|
+
}
|
|
8
|
+
export declare class SessionError extends SignalError {
|
|
9
|
+
constructor(message?: string);
|
|
10
|
+
}
|
|
11
|
+
export declare class MessageCounterError extends SessionError {
|
|
12
|
+
constructor(message?: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class PreKeyError extends SessionError {
|
|
15
|
+
constructor(message?: string);
|
|
16
|
+
}
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export class SignalError extends Error {
|
|
2
|
+
}
|
|
3
|
+
export class UntrustedIdentityKeyError extends SignalError {
|
|
4
|
+
constructor(addr, identityKey) {
|
|
5
|
+
super();
|
|
6
|
+
this.name = 'UntrustedIdentityKeyError';
|
|
7
|
+
this.addr = addr;
|
|
8
|
+
this.identityKey = identityKey;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class SessionError extends SignalError {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'SessionError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class MessageCounterError extends SessionError {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'MessageCounterError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class PreKeyError extends SessionError {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'PreKeyError';
|
|
27
|
+
}
|
|
28
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export * as crypto from './crypto.js';
|
|
2
|
+
export * as curve from './curve.js';
|
|
3
|
+
export * as keyhelper from './keyhelper.js';
|
|
4
|
+
export { ProtocolAddress } from './protocol-address.js';
|
|
5
|
+
export { SessionBuilder } from './session-builder.js';
|
|
6
|
+
export { assertValidDeviceKeyBundle } from './prekey-bundle-validator.js';
|
|
7
|
+
export { SessionCipher } from './session-cipher.js';
|
|
8
|
+
export { SessionRecord, SessionEntry } from './session-record.js';
|
|
9
|
+
export { FingerprintGenerator } from './numeric-fingerprint.js';
|
|
10
|
+
export { BaseKeyType } from './base-key-type.js';
|
|
11
|
+
export { ChainType } from './chain-type.js';
|
|
12
|
+
export { Direction } from './direction.js';
|
|
13
|
+
export { MemorySignalStorage } from './memory-storage.js';
|
|
14
|
+
export { setLogger, getLogger } from './logger.js';
|
|
15
|
+
export type { SignalLogger } from './logger.js';
|
|
16
|
+
export { wipeBuffer, wipeBuffers } from './util.js';
|
|
17
|
+
export * from './errors.js';
|
|
18
|
+
export type { KeyPair } from './curve.js';
|
|
19
|
+
export type { SignalStorage, DeviceKeyBundle, IncomingPreKeyMessage, EncryptedMessage } from './types.js';
|
|
20
|
+
export type { Chain, ChainKey, CurrentRatchet, IndexInfo, PendingPreKey, SerializedSessionEntry } from './session-record.js';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * as crypto from './crypto.js';
|
|
2
|
+
export * as curve from './curve.js';
|
|
3
|
+
export * as keyhelper from './keyhelper.js';
|
|
4
|
+
export { ProtocolAddress } from './protocol-address.js';
|
|
5
|
+
export { SessionBuilder } from './session-builder.js';
|
|
6
|
+
export { assertValidDeviceKeyBundle } from './prekey-bundle-validator.js';
|
|
7
|
+
export { SessionCipher } from './session-cipher.js';
|
|
8
|
+
export { SessionRecord, SessionEntry } from './session-record.js';
|
|
9
|
+
export { FingerprintGenerator } from './numeric-fingerprint.js';
|
|
10
|
+
export { BaseKeyType } from './base-key-type.js';
|
|
11
|
+
export { ChainType } from './chain-type.js';
|
|
12
|
+
export { Direction } from './direction.js';
|
|
13
|
+
export { MemorySignalStorage } from './memory-storage.js';
|
|
14
|
+
export { setLogger, getLogger } from './logger.js';
|
|
15
|
+
export { wipeBuffer, wipeBuffers } from './util.js';
|
|
16
|
+
export * from './errors.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as curve from './curve.js';
|
|
2
|
+
import type { KeyPair } from './curve.js';
|
|
3
|
+
export interface SignedPreKey {
|
|
4
|
+
keyId: number;
|
|
5
|
+
keyPair: KeyPair;
|
|
6
|
+
signature: Buffer;
|
|
7
|
+
createdAt: number;
|
|
8
|
+
}
|
|
9
|
+
export interface PreKey {
|
|
10
|
+
keyId: number;
|
|
11
|
+
keyPair: KeyPair;
|
|
12
|
+
}
|
|
13
|
+
export declare const DEFAULT_SIGNED_PRE_KEY_MAX_AGE_MS: number;
|
|
14
|
+
export declare function shouldRotateSignedPreKey(createdAt: number, maxAgeMs?: number): boolean;
|
|
15
|
+
export declare const generateIdentityKeyPair: typeof curve.generateKeyPair;
|
|
16
|
+
export declare function generateRegistrationId(): number;
|
|
17
|
+
export declare function generateSignedPreKey(identityKeyPair: KeyPair, signedKeyId: number): SignedPreKey;
|
|
18
|
+
export declare function generatePreKey(keyId: number): PreKey;
|
package/lib/keyhelper.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as nodeCrypto from 'crypto';
|
|
2
|
+
import * as curve from './curve.js';
|
|
3
|
+
function isNonNegativeInteger(n) {
|
|
4
|
+
return typeof n === 'number' && n % 1 === 0 && n >= 0;
|
|
5
|
+
}
|
|
6
|
+
export const DEFAULT_SIGNED_PRE_KEY_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
7
|
+
export function shouldRotateSignedPreKey(createdAt, maxAgeMs = DEFAULT_SIGNED_PRE_KEY_MAX_AGE_MS) {
|
|
8
|
+
if (!isNonNegativeInteger(createdAt)) {
|
|
9
|
+
throw new TypeError('Invalid argument for createdAt: ' + createdAt);
|
|
10
|
+
}
|
|
11
|
+
if (!isNonNegativeInteger(maxAgeMs)) {
|
|
12
|
+
throw new TypeError('Invalid argument for maxAgeMs: ' + maxAgeMs);
|
|
13
|
+
}
|
|
14
|
+
return Date.now() - createdAt >= maxAgeMs;
|
|
15
|
+
}
|
|
16
|
+
export const generateIdentityKeyPair = curve.generateKeyPair;
|
|
17
|
+
export function generateRegistrationId() {
|
|
18
|
+
let registrationId;
|
|
19
|
+
do {
|
|
20
|
+
registrationId = nodeCrypto.randomBytes(2).readUInt16BE(0) & 0x3fff;
|
|
21
|
+
} while (registrationId === 0);
|
|
22
|
+
return registrationId;
|
|
23
|
+
}
|
|
24
|
+
export function generateSignedPreKey(identityKeyPair, signedKeyId) {
|
|
25
|
+
if (!(identityKeyPair.privKey instanceof Buffer) ||
|
|
26
|
+
identityKeyPair.privKey.byteLength !== 32 ||
|
|
27
|
+
!(identityKeyPair.pubKey instanceof Buffer) ||
|
|
28
|
+
identityKeyPair.pubKey.byteLength !== 33) {
|
|
29
|
+
throw new TypeError('Invalid argument for identityKeyPair');
|
|
30
|
+
}
|
|
31
|
+
if (!isNonNegativeInteger(signedKeyId)) {
|
|
32
|
+
throw new TypeError('Invalid argument for signedKeyId: ' + signedKeyId);
|
|
33
|
+
}
|
|
34
|
+
const keyPair = curve.generateKeyPair();
|
|
35
|
+
const sig = curve.calculateSignature(identityKeyPair.privKey, keyPair.pubKey);
|
|
36
|
+
return {
|
|
37
|
+
keyId: signedKeyId,
|
|
38
|
+
keyPair,
|
|
39
|
+
signature: sig,
|
|
40
|
+
createdAt: Date.now()
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function generatePreKey(keyId) {
|
|
44
|
+
if (!isNonNegativeInteger(keyId)) {
|
|
45
|
+
throw new TypeError('Invalid argument for keyId: ' + keyId);
|
|
46
|
+
}
|
|
47
|
+
const keyPair = curve.generateKeyPair();
|
|
48
|
+
return {
|
|
49
|
+
keyId,
|
|
50
|
+
keyPair
|
|
51
|
+
};
|
|
52
|
+
}
|
package/lib/logger.d.ts
ADDED
package/lib/logger.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { KeyPair } from './curve.js';
|
|
2
|
+
import type { Direction } from './direction.js';
|
|
3
|
+
import { SessionRecord } from './session-record.js';
|
|
4
|
+
import type { SignalStorage } from './types.js';
|
|
5
|
+
export declare class MemorySignalStorage implements SignalStorage {
|
|
6
|
+
private identityKeyPair;
|
|
7
|
+
private registrationId;
|
|
8
|
+
private sessions;
|
|
9
|
+
private preKeys;
|
|
10
|
+
private signedPreKeys;
|
|
11
|
+
private trustedIdentities;
|
|
12
|
+
constructor(identityKeyPair: KeyPair, registrationId: number);
|
|
13
|
+
loadSession(id: string): Promise<SessionRecord | undefined>;
|
|
14
|
+
storeSession(id: string, session: SessionRecord): Promise<void>;
|
|
15
|
+
isTrustedIdentity(identifier: string, identityKey: Buffer, _direction: Direction): Promise<boolean>;
|
|
16
|
+
saveIdentity(identifier: string, identityKey: Buffer): Promise<boolean>;
|
|
17
|
+
removeIdentity(identifier: string): Promise<void>;
|
|
18
|
+
loadPreKey(id?: number | string): Promise<KeyPair | undefined>;
|
|
19
|
+
removePreKey(id: number): Promise<void>;
|
|
20
|
+
loadSignedPreKey(id?: number | string): Promise<KeyPair | undefined>;
|
|
21
|
+
getOurRegistrationId(): Promise<number>;
|
|
22
|
+
getOurIdentity(): Promise<KeyPair>;
|
|
23
|
+
storePreKey(id: number, keyPair: KeyPair): void;
|
|
24
|
+
storeSignedPreKey(id: number, keyPair: KeyPair): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'crypto';
|
|
2
|
+
import { SessionRecord } from './session-record.js';
|
|
3
|
+
function safeEqual(a, b) {
|
|
4
|
+
if (a.byteLength !== b.byteLength) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
return timingSafeEqual(a, b);
|
|
8
|
+
}
|
|
9
|
+
export class MemorySignalStorage {
|
|
10
|
+
constructor(identityKeyPair, registrationId) {
|
|
11
|
+
this.sessions = new Map();
|
|
12
|
+
this.preKeys = new Map();
|
|
13
|
+
this.signedPreKeys = new Map();
|
|
14
|
+
this.trustedIdentities = new Map();
|
|
15
|
+
this.identityKeyPair = identityKeyPair;
|
|
16
|
+
this.registrationId = registrationId;
|
|
17
|
+
}
|
|
18
|
+
async loadSession(id) {
|
|
19
|
+
return this.sessions.get(id);
|
|
20
|
+
}
|
|
21
|
+
async storeSession(id, session) {
|
|
22
|
+
this.sessions.set(id, session);
|
|
23
|
+
}
|
|
24
|
+
async isTrustedIdentity(identifier, identityKey, _direction) {
|
|
25
|
+
const existing = this.trustedIdentities.get(identifier);
|
|
26
|
+
if (!existing) {
|
|
27
|
+
this.trustedIdentities.set(identifier, identityKey);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return safeEqual(existing, identityKey);
|
|
31
|
+
}
|
|
32
|
+
async saveIdentity(identifier, identityKey) {
|
|
33
|
+
const existing = this.trustedIdentities.get(identifier);
|
|
34
|
+
this.trustedIdentities.set(identifier, identityKey);
|
|
35
|
+
return !existing || !safeEqual(existing, identityKey);
|
|
36
|
+
}
|
|
37
|
+
async removeIdentity(identifier) {
|
|
38
|
+
this.trustedIdentities.delete(identifier);
|
|
39
|
+
this.sessions.delete(identifier);
|
|
40
|
+
}
|
|
41
|
+
async loadPreKey(id) {
|
|
42
|
+
if (id === undefined) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
return this.preKeys.get(Number(id));
|
|
46
|
+
}
|
|
47
|
+
async removePreKey(id) {
|
|
48
|
+
this.preKeys.delete(id);
|
|
49
|
+
}
|
|
50
|
+
async loadSignedPreKey(id) {
|
|
51
|
+
if (id === undefined) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
return this.signedPreKeys.get(Number(id));
|
|
55
|
+
}
|
|
56
|
+
async getOurRegistrationId() {
|
|
57
|
+
return this.registrationId;
|
|
58
|
+
}
|
|
59
|
+
async getOurIdentity() {
|
|
60
|
+
return this.identityKeyPair;
|
|
61
|
+
}
|
|
62
|
+
storePreKey(id, keyPair) {
|
|
63
|
+
this.preKeys.set(id, keyPair);
|
|
64
|
+
}
|
|
65
|
+
storeSignedPreKey(id, keyPair) {
|
|
66
|
+
this.signedPreKeys.set(id, keyPair);
|
|
67
|
+
}
|
|
68
|
+
}
|