@hamradio/meshcore 1.2.2 → 1.3.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/dist/{index.mjs → index.cjs} +339 -323
- package/dist/{index.d.mts → index.d.cts} +118 -132
- package/dist/index.d.ts +118 -132
- package/dist/index.js +296 -364
- package/package.json +8 -4
- package/dist/crypto.d.ts +0 -42
- package/dist/crypto.js +0 -199
- package/dist/crypto.types.d.ts +0 -26
- package/dist/crypto.types.js +0 -1
- package/dist/identity.d.ts +0 -65
- package/dist/identity.js +0 -302
- package/dist/identity.types.d.ts +0 -17
- package/dist/identity.types.js +0 -1
- package/dist/packet.d.ts +0 -32
- package/dist/packet.js +0 -242
- package/dist/packet.types.d.ts +0 -161
- package/dist/packet.types.js +0 -44
- package/dist/parser.d.ts +0 -31
- package/dist/parser.js +0 -124
package/dist/index.js
CHANGED
|
@@ -1,44 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
Contact: () => Contact,
|
|
24
|
-
Contacts: () => Contacts,
|
|
25
|
-
Group: () => Group,
|
|
26
|
-
Identity: () => Identity,
|
|
27
|
-
LocalIdentity: () => LocalIdentity,
|
|
28
|
-
NodeType: () => NodeType,
|
|
29
|
-
Packet: () => Packet,
|
|
30
|
-
PayloadType: () => PayloadType,
|
|
31
|
-
PrivateKey: () => PrivateKey,
|
|
32
|
-
PublicKey: () => PublicKey,
|
|
33
|
-
RequestType: () => RequestType,
|
|
34
|
-
RouteType: () => RouteType,
|
|
35
|
-
SharedSecret: () => SharedSecret,
|
|
36
|
-
StaticSecret: () => StaticSecret,
|
|
37
|
-
TextType: () => TextType,
|
|
38
|
-
parseNodeHash: () => parseNodeHash
|
|
39
|
-
});
|
|
40
|
-
module.exports = __toCommonJS(index_exports);
|
|
41
|
-
|
|
42
1
|
// src/packet.types.ts
|
|
43
2
|
var RouteType = /* @__PURE__ */ ((RouteType2) => {
|
|
44
3
|
RouteType2[RouteType2["TRANSPORT_FLOOD"] = 0] = "TRANSPORT_FLOOD";
|
|
@@ -86,127 +45,8 @@ var NodeType = /* @__PURE__ */ ((NodeType2) => {
|
|
|
86
45
|
})(NodeType || {});
|
|
87
46
|
|
|
88
47
|
// src/packet.ts
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// src/parser.ts
|
|
92
|
-
var import_utils = require("@noble/ciphers/utils.js");
|
|
93
|
-
var import_utils2 = require("@noble/hashes/utils.js");
|
|
94
|
-
var base64ToBytes = (base64, size) => {
|
|
95
|
-
let normalized = base64.replace(/-/g, "+").replace(/_/g, "/");
|
|
96
|
-
while (normalized.length % 4 !== 0) {
|
|
97
|
-
normalized += "=";
|
|
98
|
-
}
|
|
99
|
-
const binaryString = atob(normalized);
|
|
100
|
-
const bytes = new Uint8Array(binaryString.length);
|
|
101
|
-
for (let i = 0; i < binaryString.length; i++) {
|
|
102
|
-
bytes[i] = binaryString.charCodeAt(i);
|
|
103
|
-
}
|
|
104
|
-
if (size !== void 0 && bytes.length !== size) {
|
|
105
|
-
throw new Error(`Invalid base64 length: expected ${size} bytes, got ${bytes.length}`);
|
|
106
|
-
}
|
|
107
|
-
return bytes;
|
|
108
|
-
};
|
|
109
|
-
var hexToBytes = (hex, size) => {
|
|
110
|
-
const bytes = (0, import_utils2.hexToBytes)(hex);
|
|
111
|
-
if (size !== void 0 && bytes.length !== size) {
|
|
112
|
-
throw new Error(`Invalid hex length: expected ${size} bytes, got ${bytes.length}`);
|
|
113
|
-
}
|
|
114
|
-
return bytes;
|
|
115
|
-
};
|
|
116
|
-
var BufferReader = class {
|
|
117
|
-
constructor(buffer) {
|
|
118
|
-
this.buffer = buffer;
|
|
119
|
-
this.offset = 0;
|
|
120
|
-
}
|
|
121
|
-
readByte() {
|
|
122
|
-
if (!this.hasMore()) throw new Error("read past end");
|
|
123
|
-
return this.buffer[this.offset++];
|
|
124
|
-
}
|
|
125
|
-
readBytes(length) {
|
|
126
|
-
if (length === void 0) {
|
|
127
|
-
length = this.buffer.length - this.offset;
|
|
128
|
-
}
|
|
129
|
-
if (this.remainingBytes() < length) throw new Error("read past end");
|
|
130
|
-
const bytes = this.buffer.slice(this.offset, this.offset + length);
|
|
131
|
-
this.offset += length;
|
|
132
|
-
return bytes;
|
|
133
|
-
}
|
|
134
|
-
hasMore() {
|
|
135
|
-
return this.offset < this.buffer.length;
|
|
136
|
-
}
|
|
137
|
-
remainingBytes() {
|
|
138
|
-
return this.buffer.length - this.offset;
|
|
139
|
-
}
|
|
140
|
-
peekByte() {
|
|
141
|
-
if (!this.hasMore()) throw new Error("read past end");
|
|
142
|
-
return this.buffer[this.offset];
|
|
143
|
-
}
|
|
144
|
-
readUint16LE() {
|
|
145
|
-
if (this.remainingBytes() < 2) throw new Error("read past end");
|
|
146
|
-
const value = this.buffer[this.offset] | this.buffer[this.offset + 1] << 8;
|
|
147
|
-
this.offset += 2;
|
|
148
|
-
return value;
|
|
149
|
-
}
|
|
150
|
-
readUint32LE() {
|
|
151
|
-
if (this.remainingBytes() < 4) throw new Error("read past end");
|
|
152
|
-
const value = (this.buffer[this.offset] | this.buffer[this.offset + 1] << 8 | this.buffer[this.offset + 2] << 16 | this.buffer[this.offset + 3] << 24) >>> 0;
|
|
153
|
-
this.offset += 4;
|
|
154
|
-
return value;
|
|
155
|
-
}
|
|
156
|
-
readInt16LE() {
|
|
157
|
-
if (this.remainingBytes() < 2) throw new Error("read past end");
|
|
158
|
-
const value = this.buffer[this.offset] | this.buffer[this.offset + 1] << 8;
|
|
159
|
-
this.offset += 2;
|
|
160
|
-
return value < 32768 ? value : value - 65536;
|
|
161
|
-
}
|
|
162
|
-
readInt32LE() {
|
|
163
|
-
if (this.remainingBytes() < 4) throw new Error("read past end");
|
|
164
|
-
const u = (this.buffer[this.offset] | this.buffer[this.offset + 1] << 8 | this.buffer[this.offset + 2] << 16 | this.buffer[this.offset + 3] << 24) >>> 0;
|
|
165
|
-
this.offset += 4;
|
|
166
|
-
return u < 2147483648 ? u : u - 4294967296;
|
|
167
|
-
}
|
|
168
|
-
readTimestamp() {
|
|
169
|
-
const timestamp = this.readUint32LE();
|
|
170
|
-
return new Date(timestamp * 1e3);
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
var BufferWriter = class {
|
|
174
|
-
constructor() {
|
|
175
|
-
this.buffer = [];
|
|
176
|
-
}
|
|
177
|
-
writeByte(value) {
|
|
178
|
-
this.buffer.push(value & 255);
|
|
179
|
-
}
|
|
180
|
-
writeBytes(bytes) {
|
|
181
|
-
this.buffer.push(...bytes);
|
|
182
|
-
}
|
|
183
|
-
writeUint16LE(value) {
|
|
184
|
-
this.buffer.push(value & 255, value >> 8 & 255);
|
|
185
|
-
}
|
|
186
|
-
writeUint32LE(value) {
|
|
187
|
-
this.buffer.push(
|
|
188
|
-
value & 255,
|
|
189
|
-
value >> 8 & 255,
|
|
190
|
-
value >> 16 & 255,
|
|
191
|
-
value >> 24 & 255
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
writeInt16LE(value) {
|
|
195
|
-
this.writeUint16LE(value < 0 ? value + 65536 : value);
|
|
196
|
-
}
|
|
197
|
-
writeInt32LE(value) {
|
|
198
|
-
this.writeUint32LE(value < 0 ? value + 4294967296 : value);
|
|
199
|
-
}
|
|
200
|
-
writeTimestamp(date) {
|
|
201
|
-
const timestamp = Math.floor(date.getTime() / 1e3);
|
|
202
|
-
this.writeUint32LE(timestamp);
|
|
203
|
-
}
|
|
204
|
-
toBytes() {
|
|
205
|
-
return new Uint8Array(this.buffer);
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
// src/packet.ts
|
|
48
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
49
|
+
import { base64ToBytes, bytesToHex, FieldType, Reader } from "@hamradio/packet";
|
|
210
50
|
var Packet = class _Packet {
|
|
211
51
|
constructor(header, transport, pathLength, path, payload) {
|
|
212
52
|
this.header = header;
|
|
@@ -223,7 +63,7 @@ var Packet = class _Packet {
|
|
|
223
63
|
this.pathHashes = [];
|
|
224
64
|
for (let i = 0; i < this.pathHashBytes; i += this.pathHashSize) {
|
|
225
65
|
const hashBytes = this.path.slice(i, i + this.pathHashSize);
|
|
226
|
-
const hashHex =
|
|
66
|
+
const hashHex = bytesToHex(hashBytes);
|
|
227
67
|
this.pathHashes.push(hashHex);
|
|
228
68
|
}
|
|
229
69
|
}
|
|
@@ -250,14 +90,14 @@ var Packet = class _Packet {
|
|
|
250
90
|
return routeType === 0 /* TRANSPORT_FLOOD */ || routeType === 3 /* TRANSPORT_DIRECT */;
|
|
251
91
|
}
|
|
252
92
|
hash() {
|
|
253
|
-
const hash =
|
|
93
|
+
const hash = sha256.create();
|
|
254
94
|
hash.update(new Uint8Array([this.payloadType]));
|
|
255
95
|
if (this.payloadType === 9 /* TRACE */) {
|
|
256
96
|
hash.update(new Uint8Array([this.pathLength]));
|
|
257
97
|
}
|
|
258
98
|
hash.update(this.payload);
|
|
259
99
|
const digest = hash.digest();
|
|
260
|
-
return
|
|
100
|
+
return bytesToHex(digest.slice(0, 8));
|
|
261
101
|
}
|
|
262
102
|
ensureStructure() {
|
|
263
103
|
if (typeof this.structure !== "undefined") {
|
|
@@ -266,13 +106,13 @@ var Packet = class _Packet {
|
|
|
266
106
|
let pathHashType;
|
|
267
107
|
switch (this.pathHashSize) {
|
|
268
108
|
case 1:
|
|
269
|
-
pathHashType =
|
|
109
|
+
pathHashType = FieldType.BYTES;
|
|
270
110
|
break;
|
|
271
111
|
case 2:
|
|
272
|
-
pathHashType =
|
|
112
|
+
pathHashType = FieldType.WORDS;
|
|
273
113
|
break;
|
|
274
114
|
case 4:
|
|
275
|
-
pathHashType =
|
|
115
|
+
pathHashType = FieldType.DWORDS;
|
|
276
116
|
break;
|
|
277
117
|
default:
|
|
278
118
|
throw new Error(`Unsupported path hash size: ${this.pathHashSize}`);
|
|
@@ -281,13 +121,13 @@ var Packet = class _Packet {
|
|
|
281
121
|
/* Header segment */
|
|
282
122
|
{
|
|
283
123
|
name: "header",
|
|
284
|
-
data: new Uint8Array([this.header]),
|
|
124
|
+
data: new Uint8Array([this.header]).buffer,
|
|
285
125
|
fields: [
|
|
286
126
|
/* Header flags */
|
|
287
127
|
{
|
|
288
128
|
name: "flags",
|
|
289
|
-
type:
|
|
290
|
-
|
|
129
|
+
type: FieldType.BITS,
|
|
130
|
+
length: 1,
|
|
291
131
|
bits: [
|
|
292
132
|
{ name: "payload version", size: 2 },
|
|
293
133
|
{ name: "payload type", size: 4 },
|
|
@@ -297,38 +137,40 @@ var Packet = class _Packet {
|
|
|
297
137
|
]
|
|
298
138
|
},
|
|
299
139
|
/* Transport codes */
|
|
300
|
-
..._Packet.hasTransportCodes(this.routeType) ? [
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
140
|
+
..._Packet.hasTransportCodes(this.routeType) ? [
|
|
141
|
+
{
|
|
142
|
+
name: "transport codes",
|
|
143
|
+
data: new Uint8Array([
|
|
144
|
+
this.transport[0] >> 8 & 255,
|
|
145
|
+
this.transport[0] & 255,
|
|
146
|
+
this.transport[1] >> 8 & 255,
|
|
147
|
+
this.transport[1] & 255
|
|
148
|
+
]).buffer,
|
|
149
|
+
fields: [
|
|
150
|
+
{
|
|
151
|
+
name: "transport code 1",
|
|
152
|
+
type: FieldType.UINT16_BE,
|
|
153
|
+
length: 2,
|
|
154
|
+
value: this.transport[0]
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "transport code 2",
|
|
158
|
+
type: FieldType.UINT16_BE,
|
|
159
|
+
length: 2,
|
|
160
|
+
value: this.transport[1]
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
] : [],
|
|
323
165
|
/* Path length and hashes */
|
|
324
166
|
{
|
|
325
167
|
name: "path",
|
|
326
|
-
data: new Uint8Array([this.pathLength, ...this.path]),
|
|
168
|
+
data: new Uint8Array([this.pathLength, ...this.path]).buffer,
|
|
327
169
|
fields: [
|
|
328
170
|
{
|
|
329
171
|
name: "path length",
|
|
330
|
-
type:
|
|
331
|
-
|
|
172
|
+
type: FieldType.UINT8,
|
|
173
|
+
length: 1,
|
|
332
174
|
bits: [
|
|
333
175
|
{ name: "path hash size", size: 2 },
|
|
334
176
|
{ name: "path hash count", size: 6 }
|
|
@@ -337,7 +179,7 @@ var Packet = class _Packet {
|
|
|
337
179
|
{
|
|
338
180
|
name: "path hashes",
|
|
339
181
|
type: pathHashType,
|
|
340
|
-
|
|
182
|
+
length: this.path.length
|
|
341
183
|
}
|
|
342
184
|
]
|
|
343
185
|
}
|
|
@@ -382,7 +224,6 @@ var Packet = class _Packet {
|
|
|
382
224
|
default:
|
|
383
225
|
throw new Error(`Unsupported payload type: ${this.payloadType}`);
|
|
384
226
|
}
|
|
385
|
-
console.log("packet decode with structure:", typeof withStructure, withStructure, { result });
|
|
386
227
|
if (typeof withStructure === "boolean" && withStructure && "segment" in result && "payload" in result) {
|
|
387
228
|
this.ensureStructure();
|
|
388
229
|
const structure = [...this.structure, result.segment];
|
|
@@ -391,17 +232,17 @@ var Packet = class _Packet {
|
|
|
391
232
|
return result;
|
|
392
233
|
}
|
|
393
234
|
decodeEncryptedPayload(reader) {
|
|
394
|
-
const cipherMAC = reader.
|
|
395
|
-
const cipherText = reader.
|
|
235
|
+
const cipherMAC = reader.bytes(2);
|
|
236
|
+
const cipherText = reader.bytes();
|
|
396
237
|
return { cipherMAC, cipherText };
|
|
397
238
|
}
|
|
398
239
|
decodeRequest(withSegment) {
|
|
399
240
|
if (this.payload.length < 4) {
|
|
400
241
|
throw new Error("Invalid request payload: too short");
|
|
401
242
|
}
|
|
402
|
-
const reader =
|
|
403
|
-
const dst = reader.
|
|
404
|
-
const src = reader.
|
|
243
|
+
const reader = Reader.fromBytes(this.payload);
|
|
244
|
+
const dst = reader.uint8();
|
|
245
|
+
const src = reader.uint8();
|
|
405
246
|
const encrypted = this.decodeEncryptedPayload(reader);
|
|
406
247
|
const payload = {
|
|
407
248
|
type: 0 /* REQUEST */,
|
|
@@ -412,12 +253,17 @@ var Packet = class _Packet {
|
|
|
412
253
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
413
254
|
const segment = {
|
|
414
255
|
name: "request payload",
|
|
415
|
-
data: this.payload,
|
|
256
|
+
data: new Uint8Array(this.payload).buffer,
|
|
416
257
|
fields: [
|
|
417
|
-
{ name: "destination hash", type:
|
|
418
|
-
{ name: "source hash", type:
|
|
419
|
-
{ name: "cipher MAC", type:
|
|
420
|
-
{
|
|
258
|
+
{ name: "destination hash", type: FieldType.UINT8, length: 1, value: dst },
|
|
259
|
+
{ name: "source hash", type: FieldType.UINT8, length: 1, value: src },
|
|
260
|
+
{ name: "cipher MAC", type: FieldType.BYTES, length: 2, value: encrypted.cipherMAC },
|
|
261
|
+
{
|
|
262
|
+
name: "cipher text",
|
|
263
|
+
type: FieldType.BYTES,
|
|
264
|
+
length: encrypted.cipherText.length,
|
|
265
|
+
value: encrypted.cipherText
|
|
266
|
+
}
|
|
421
267
|
]
|
|
422
268
|
};
|
|
423
269
|
return { payload, segment };
|
|
@@ -428,9 +274,9 @@ var Packet = class _Packet {
|
|
|
428
274
|
if (this.payload.length < 4) {
|
|
429
275
|
throw new Error("Invalid response payload: too short");
|
|
430
276
|
}
|
|
431
|
-
const reader =
|
|
432
|
-
const dst = reader.
|
|
433
|
-
const src = reader.
|
|
277
|
+
const reader = Reader.fromBytes(this.payload);
|
|
278
|
+
const dst = reader.uint8();
|
|
279
|
+
const src = reader.uint8();
|
|
434
280
|
const encrypted = this.decodeEncryptedPayload(reader);
|
|
435
281
|
const payload = {
|
|
436
282
|
type: 1 /* RESPONSE */,
|
|
@@ -441,12 +287,17 @@ var Packet = class _Packet {
|
|
|
441
287
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
442
288
|
const segment = {
|
|
443
289
|
name: "response payload",
|
|
444
|
-
data: this.payload,
|
|
290
|
+
data: new Uint8Array(this.payload).buffer,
|
|
445
291
|
fields: [
|
|
446
|
-
{ name: "destination hash", type:
|
|
447
|
-
{ name: "source hash", type:
|
|
448
|
-
{ name: "cipher MAC", type:
|
|
449
|
-
{
|
|
292
|
+
{ name: "destination hash", type: FieldType.UINT8, length: 1, value: dst },
|
|
293
|
+
{ name: "source hash", type: FieldType.UINT8, length: 1, value: src },
|
|
294
|
+
{ name: "cipher MAC", type: FieldType.BYTES, length: 2, value: encrypted.cipherMAC },
|
|
295
|
+
{
|
|
296
|
+
name: "cipher text",
|
|
297
|
+
type: FieldType.BYTES,
|
|
298
|
+
length: encrypted.cipherText.length,
|
|
299
|
+
value: encrypted.cipherText
|
|
300
|
+
}
|
|
450
301
|
]
|
|
451
302
|
};
|
|
452
303
|
return { payload, segment };
|
|
@@ -457,9 +308,9 @@ var Packet = class _Packet {
|
|
|
457
308
|
if (this.payload.length < 4) {
|
|
458
309
|
throw new Error("Invalid text payload: too short");
|
|
459
310
|
}
|
|
460
|
-
const reader =
|
|
461
|
-
const dst = reader.
|
|
462
|
-
const src = reader.
|
|
311
|
+
const reader = Reader.fromBytes(this.payload);
|
|
312
|
+
const dst = reader.uint8();
|
|
313
|
+
const src = reader.uint8();
|
|
463
314
|
const encrypted = this.decodeEncryptedPayload(reader);
|
|
464
315
|
const payload = {
|
|
465
316
|
type: 2 /* TEXT */,
|
|
@@ -470,12 +321,17 @@ var Packet = class _Packet {
|
|
|
470
321
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
471
322
|
const segment = {
|
|
472
323
|
name: "text payload",
|
|
473
|
-
data: this.payload,
|
|
324
|
+
data: new Uint8Array(this.payload).buffer,
|
|
474
325
|
fields: [
|
|
475
|
-
{ name: "destination hash", type:
|
|
476
|
-
{ name: "source hash", type:
|
|
477
|
-
{ name: "cipher MAC", type:
|
|
478
|
-
{
|
|
326
|
+
{ name: "destination hash", type: FieldType.UINT8, length: 1, value: dst },
|
|
327
|
+
{ name: "source hash", type: FieldType.UINT8, length: 1, value: src },
|
|
328
|
+
{ name: "cipher MAC", type: FieldType.BYTES, length: 2, value: encrypted.cipherMAC },
|
|
329
|
+
{
|
|
330
|
+
name: "cipher text",
|
|
331
|
+
type: FieldType.BYTES,
|
|
332
|
+
length: encrypted.cipherText.length,
|
|
333
|
+
value: encrypted.cipherText
|
|
334
|
+
}
|
|
479
335
|
]
|
|
480
336
|
};
|
|
481
337
|
return { payload, segment };
|
|
@@ -486,8 +342,8 @@ var Packet = class _Packet {
|
|
|
486
342
|
if (this.payload.length < 4) {
|
|
487
343
|
throw new Error("Invalid ack payload: too short");
|
|
488
344
|
}
|
|
489
|
-
const reader =
|
|
490
|
-
const checksum = reader.
|
|
345
|
+
const reader = Reader.fromBytes(this.payload);
|
|
346
|
+
const checksum = reader.bytes(4);
|
|
491
347
|
const payload = {
|
|
492
348
|
type: 3 /* ACK */,
|
|
493
349
|
checksum
|
|
@@ -495,10 +351,8 @@ var Packet = class _Packet {
|
|
|
495
351
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
496
352
|
const segment = {
|
|
497
353
|
name: "ack payload",
|
|
498
|
-
data: this.payload,
|
|
499
|
-
fields: [
|
|
500
|
-
{ name: "checksum", type: 6 /* BYTES */, size: 4, value: checksum }
|
|
501
|
-
]
|
|
354
|
+
data: new Uint8Array(this.payload).buffer,
|
|
355
|
+
fields: [{ name: "checksum", type: FieldType.BYTES, length: 4, value: checksum }]
|
|
502
356
|
};
|
|
503
357
|
return { payload, segment };
|
|
504
358
|
}
|
|
@@ -508,26 +362,26 @@ var Packet = class _Packet {
|
|
|
508
362
|
if (this.payload.length < 4) {
|
|
509
363
|
throw new Error("Invalid advert payload: too short");
|
|
510
364
|
}
|
|
511
|
-
const reader =
|
|
365
|
+
const reader = Reader.fromBytes(this.payload);
|
|
512
366
|
const payload = {
|
|
513
367
|
type: 4 /* ADVERT */,
|
|
514
|
-
publicKey: reader.
|
|
515
|
-
timestamp: reader.
|
|
516
|
-
signature: reader.
|
|
368
|
+
publicKey: reader.bytes(32),
|
|
369
|
+
timestamp: reader.date32(),
|
|
370
|
+
signature: reader.bytes(64)
|
|
517
371
|
};
|
|
518
372
|
let segment;
|
|
519
373
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
520
374
|
segment = {
|
|
521
375
|
name: "advert payload",
|
|
522
|
-
data: this.payload,
|
|
376
|
+
data: new Uint8Array(this.payload).buffer,
|
|
523
377
|
fields: [
|
|
524
|
-
{ type:
|
|
525
|
-
{ type:
|
|
526
|
-
{ type:
|
|
378
|
+
{ type: FieldType.BYTES, name: "public key", length: 32 },
|
|
379
|
+
{ type: FieldType.UINT32_LE, name: "timestamp", length: 4, value: payload.timestamp },
|
|
380
|
+
{ type: FieldType.BYTES, name: "signature", length: 64 }
|
|
527
381
|
]
|
|
528
382
|
};
|
|
529
383
|
}
|
|
530
|
-
const flags = reader.
|
|
384
|
+
const flags = reader.uint8();
|
|
531
385
|
const appdata = {
|
|
532
386
|
nodeType: flags & 15,
|
|
533
387
|
hasLocation: (flags & 16 /* HAS_LOCATION */) !== 0,
|
|
@@ -536,44 +390,50 @@ var Packet = class _Packet {
|
|
|
536
390
|
hasName: (flags & 128 /* HAS_NAME */) !== 0
|
|
537
391
|
};
|
|
538
392
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
539
|
-
segment.fields.push({
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
393
|
+
segment.fields.push({
|
|
394
|
+
type: FieldType.BITS,
|
|
395
|
+
name: "flags",
|
|
396
|
+
length: 1,
|
|
397
|
+
value: flags,
|
|
398
|
+
bits: [
|
|
399
|
+
{ size: 1, name: "name flag" },
|
|
400
|
+
{ size: 1, name: "feature2 flag" },
|
|
401
|
+
{ size: 1, name: "feature1 flag" },
|
|
402
|
+
{ size: 1, name: "location flag" },
|
|
403
|
+
{ size: 4, name: "node type" }
|
|
404
|
+
]
|
|
405
|
+
});
|
|
546
406
|
}
|
|
547
407
|
if (appdata.hasLocation) {
|
|
548
|
-
const lat = reader.
|
|
549
|
-
const lon = reader.
|
|
408
|
+
const lat = reader.int32() / 1e6;
|
|
409
|
+
const lon = reader.int32() / 1e6;
|
|
550
410
|
appdata.location = [lat, lon];
|
|
551
411
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
552
|
-
segment.fields.push({ type:
|
|
553
|
-
segment.fields.push({ type:
|
|
412
|
+
segment.fields.push({ type: FieldType.UINT32_LE, name: "latitude", length: 4, value: lat });
|
|
413
|
+
segment.fields.push({ type: FieldType.UINT32_LE, name: "longitude", length: 4, value: lon });
|
|
554
414
|
}
|
|
555
415
|
}
|
|
556
416
|
if (appdata.hasFeature1) {
|
|
557
|
-
appdata.feature1 = reader.
|
|
417
|
+
appdata.feature1 = reader.uint16();
|
|
558
418
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
559
|
-
segment.fields.push({ type:
|
|
419
|
+
segment.fields.push({ type: FieldType.UINT16_LE, name: "feature1", length: 2, value: appdata.feature1 });
|
|
560
420
|
}
|
|
561
421
|
}
|
|
562
422
|
if (appdata.hasFeature2) {
|
|
563
|
-
appdata.feature2 = reader.
|
|
423
|
+
appdata.feature2 = reader.uint16();
|
|
564
424
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
565
|
-
segment.fields.push({ type:
|
|
425
|
+
segment.fields.push({ type: FieldType.UINT16_LE, name: "feature2", length: 2, value: appdata.feature2 });
|
|
566
426
|
}
|
|
567
427
|
}
|
|
568
428
|
if (appdata.hasName) {
|
|
569
|
-
|
|
570
|
-
let nullPos = nameBytes.indexOf(0);
|
|
571
|
-
if (nullPos === -1) {
|
|
572
|
-
nullPos = nameBytes.length;
|
|
573
|
-
}
|
|
574
|
-
appdata.name = new TextDecoder("utf-8").decode(nameBytes.subarray(0, nullPos));
|
|
429
|
+
appdata.name = reader.cString();
|
|
575
430
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
576
|
-
segment.fields.push({
|
|
431
|
+
segment.fields.push({
|
|
432
|
+
type: FieldType.C_STRING,
|
|
433
|
+
name: "name",
|
|
434
|
+
length: appdata.name.length,
|
|
435
|
+
value: appdata.name
|
|
436
|
+
});
|
|
577
437
|
}
|
|
578
438
|
}
|
|
579
439
|
if (typeof withSegment === "boolean" && withSegment && typeof segment !== "undefined") {
|
|
@@ -585,8 +445,8 @@ var Packet = class _Packet {
|
|
|
585
445
|
if (this.payload.length < 3) {
|
|
586
446
|
throw new Error("Invalid group text payload: too short");
|
|
587
447
|
}
|
|
588
|
-
const reader =
|
|
589
|
-
const channelHash = reader.
|
|
448
|
+
const reader = Reader.fromBytes(this.payload);
|
|
449
|
+
const channelHash = reader.uint8();
|
|
590
450
|
const encrypted = this.decodeEncryptedPayload(reader);
|
|
591
451
|
const payload = {
|
|
592
452
|
type: 5 /* GROUP_TEXT */,
|
|
@@ -596,11 +456,16 @@ var Packet = class _Packet {
|
|
|
596
456
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
597
457
|
const segment = {
|
|
598
458
|
name: "group text payload",
|
|
599
|
-
data: this.payload,
|
|
459
|
+
data: new Uint8Array(this.payload).buffer,
|
|
600
460
|
fields: [
|
|
601
|
-
{ name: "channel hash", type:
|
|
602
|
-
{ name: "cipher MAC", type:
|
|
603
|
-
{
|
|
461
|
+
{ name: "channel hash", type: FieldType.UINT8, length: 1, value: channelHash },
|
|
462
|
+
{ name: "cipher MAC", type: FieldType.BYTES, length: 2, value: encrypted.cipherMAC },
|
|
463
|
+
{
|
|
464
|
+
name: "cipher text",
|
|
465
|
+
type: FieldType.BYTES,
|
|
466
|
+
length: encrypted.cipherText.length,
|
|
467
|
+
value: encrypted.cipherText
|
|
468
|
+
}
|
|
604
469
|
]
|
|
605
470
|
};
|
|
606
471
|
return { payload, segment };
|
|
@@ -611,20 +476,25 @@ var Packet = class _Packet {
|
|
|
611
476
|
if (this.payload.length < 3) {
|
|
612
477
|
throw new Error("Invalid group data payload: too short");
|
|
613
478
|
}
|
|
614
|
-
const reader =
|
|
479
|
+
const reader = Reader.fromBytes(this.payload);
|
|
615
480
|
const payload = {
|
|
616
481
|
type: 6 /* GROUP_DATA */,
|
|
617
|
-
channelHash: reader.
|
|
482
|
+
channelHash: reader.uint8(),
|
|
618
483
|
encrypted: this.decodeEncryptedPayload(reader)
|
|
619
484
|
};
|
|
620
485
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
621
486
|
const segment = {
|
|
622
487
|
name: "group data payload",
|
|
623
|
-
data: this.payload,
|
|
488
|
+
data: new Uint8Array(this.payload).buffer,
|
|
624
489
|
fields: [
|
|
625
|
-
{ name: "channel hash", type:
|
|
626
|
-
{ name: "cipher MAC", type:
|
|
627
|
-
{
|
|
490
|
+
{ name: "channel hash", type: FieldType.UINT8, length: 1, value: payload.channelHash },
|
|
491
|
+
{ name: "cipher MAC", type: FieldType.BYTES, length: 2, value: payload.encrypted.cipherMAC },
|
|
492
|
+
{
|
|
493
|
+
name: "cipher text",
|
|
494
|
+
type: FieldType.BYTES,
|
|
495
|
+
length: payload.encrypted.cipherText.length,
|
|
496
|
+
value: payload.encrypted.cipherText
|
|
497
|
+
}
|
|
628
498
|
]
|
|
629
499
|
};
|
|
630
500
|
return { payload, segment };
|
|
@@ -635,22 +505,27 @@ var Packet = class _Packet {
|
|
|
635
505
|
if (this.payload.length < 1 + 32 + 2) {
|
|
636
506
|
throw new Error("Invalid anon req payload: too short");
|
|
637
507
|
}
|
|
638
|
-
const reader =
|
|
508
|
+
const reader = Reader.fromBytes(this.payload);
|
|
639
509
|
const payload = {
|
|
640
510
|
type: 7 /* ANON_REQ */,
|
|
641
|
-
dst: reader.
|
|
642
|
-
publicKey: reader.
|
|
511
|
+
dst: reader.uint8(),
|
|
512
|
+
publicKey: reader.bytes(32),
|
|
643
513
|
encrypted: this.decodeEncryptedPayload(reader)
|
|
644
514
|
};
|
|
645
515
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
646
516
|
const segment = {
|
|
647
517
|
name: "anon req payload",
|
|
648
|
-
data: this.payload,
|
|
518
|
+
data: new Uint8Array(this.payload).buffer,
|
|
649
519
|
fields: [
|
|
650
|
-
{ name: "destination hash", type:
|
|
651
|
-
{ name: "public key", type:
|
|
652
|
-
{ name: "cipher MAC", type:
|
|
653
|
-
{
|
|
520
|
+
{ name: "destination hash", type: FieldType.UINT8, length: 1, value: payload.dst },
|
|
521
|
+
{ name: "public key", type: FieldType.BYTES, length: 32, value: payload.publicKey },
|
|
522
|
+
{ name: "cipher MAC", type: FieldType.BYTES, length: 2, value: payload.encrypted.cipherMAC },
|
|
523
|
+
{
|
|
524
|
+
name: "cipher text",
|
|
525
|
+
type: FieldType.BYTES,
|
|
526
|
+
length: payload.encrypted.cipherText.length,
|
|
527
|
+
value: payload.encrypted.cipherText
|
|
528
|
+
}
|
|
654
529
|
]
|
|
655
530
|
};
|
|
656
531
|
return { payload, segment };
|
|
@@ -661,19 +536,19 @@ var Packet = class _Packet {
|
|
|
661
536
|
if (this.payload.length < 2) {
|
|
662
537
|
throw new Error("Invalid path payload: too short");
|
|
663
538
|
}
|
|
664
|
-
const reader =
|
|
539
|
+
const reader = Reader.fromBytes(this.payload);
|
|
665
540
|
const payload = {
|
|
666
541
|
type: 8 /* PATH */,
|
|
667
|
-
dst: reader.
|
|
668
|
-
src: reader.
|
|
542
|
+
dst: reader.uint8(),
|
|
543
|
+
src: reader.uint8()
|
|
669
544
|
};
|
|
670
545
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
671
546
|
const segment = {
|
|
672
547
|
name: "path payload",
|
|
673
|
-
data: this.payload,
|
|
548
|
+
data: new Uint8Array(this.payload).buffer,
|
|
674
549
|
fields: [
|
|
675
|
-
{ name: "destination hash", type:
|
|
676
|
-
{ name: "source hash", type:
|
|
550
|
+
{ name: "destination hash", type: FieldType.UINT8, length: 1, value: payload.dst },
|
|
551
|
+
{ name: "source hash", type: FieldType.UINT8, length: 1, value: payload.src }
|
|
677
552
|
]
|
|
678
553
|
};
|
|
679
554
|
return { payload, segment };
|
|
@@ -684,23 +559,23 @@ var Packet = class _Packet {
|
|
|
684
559
|
if (this.payload.length < 9) {
|
|
685
560
|
throw new Error("Invalid trace payload: too short");
|
|
686
561
|
}
|
|
687
|
-
const reader =
|
|
562
|
+
const reader = Reader.fromBytes(this.payload);
|
|
688
563
|
const payload = {
|
|
689
564
|
type: 9 /* TRACE */,
|
|
690
|
-
tag: reader.
|
|
691
|
-
authCode: reader.
|
|
692
|
-
flags: reader.
|
|
693
|
-
nodes: reader.
|
|
565
|
+
tag: reader.uint32(),
|
|
566
|
+
authCode: reader.uint32(),
|
|
567
|
+
flags: reader.uint8() & 3,
|
|
568
|
+
nodes: reader.bytes()
|
|
694
569
|
};
|
|
695
570
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
696
571
|
const segment = {
|
|
697
572
|
name: "trace payload",
|
|
698
|
-
data: this.payload,
|
|
573
|
+
data: new Uint8Array(this.payload).buffer,
|
|
699
574
|
fields: [
|
|
700
|
-
{ name: "tag", type:
|
|
701
|
-
{ name: "auth code", type:
|
|
702
|
-
{ name: "flags", type:
|
|
703
|
-
{ name: "nodes", type:
|
|
575
|
+
{ name: "tag", type: FieldType.DWORDS, length: 4, value: payload.tag },
|
|
576
|
+
{ name: "auth code", type: FieldType.DWORDS, length: 4, value: payload.authCode },
|
|
577
|
+
{ name: "flags", type: FieldType.UINT8, length: 1, value: payload.flags },
|
|
578
|
+
{ name: "nodes", type: FieldType.BYTES, length: payload.nodes.length, value: payload.nodes }
|
|
704
579
|
]
|
|
705
580
|
};
|
|
706
581
|
return { payload, segment };
|
|
@@ -715,10 +590,8 @@ var Packet = class _Packet {
|
|
|
715
590
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
716
591
|
const segment = {
|
|
717
592
|
name: "raw custom payload",
|
|
718
|
-
data: this.payload,
|
|
719
|
-
fields: [
|
|
720
|
-
{ name: "data", type: 6 /* BYTES */, size: this.payload.length, value: this.payload }
|
|
721
|
-
]
|
|
593
|
+
data: new Uint8Array(this.payload).buffer,
|
|
594
|
+
fields: [{ name: "data", type: FieldType.BYTES, length: this.payload.length, value: this.payload }]
|
|
722
595
|
};
|
|
723
596
|
return { payload, segment };
|
|
724
597
|
}
|
|
@@ -727,35 +600,39 @@ var Packet = class _Packet {
|
|
|
727
600
|
};
|
|
728
601
|
|
|
729
602
|
// src/crypto.ts
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
603
|
+
import { ed25519, x25519 } from "@noble/curves/ed25519.js";
|
|
604
|
+
import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
|
|
605
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
606
|
+
import { ecb } from "@noble/ciphers/aes.js";
|
|
607
|
+
import { bytesToHex as bytesToHex2, equalBytes, hexToBytes } from "@hamradio/packet";
|
|
734
608
|
var PUBLIC_KEY_SIZE = 32;
|
|
735
609
|
var SEED_SIZE = 32;
|
|
736
610
|
var HMAC_SIZE = 2;
|
|
737
|
-
var SHARED_SECRET_SIZE =
|
|
611
|
+
var SHARED_SECRET_SIZE = 16;
|
|
738
612
|
var SIGNATURE_SIZE = 64;
|
|
739
613
|
var STATIC_SECRET_SIZE = 32;
|
|
740
|
-
var publicSecret = hexToBytes("8b3387e9c5cdea6ac9e5edbaa115cd72"
|
|
614
|
+
var publicSecret = hexToBytes("8b3387e9c5cdea6ac9e5edbaa115cd72");
|
|
741
615
|
var PublicKey = class _PublicKey {
|
|
742
616
|
constructor(key) {
|
|
743
617
|
if (typeof key === "string") {
|
|
744
|
-
this.key = hexToBytes(key
|
|
618
|
+
this.key = hexToBytes(key);
|
|
745
619
|
} else if (key instanceof Uint8Array) {
|
|
746
620
|
this.key = key;
|
|
747
621
|
} else {
|
|
748
622
|
throw new Error("Invalid type for PublicKey constructor");
|
|
749
623
|
}
|
|
624
|
+
if (this.key.length !== PUBLIC_KEY_SIZE) {
|
|
625
|
+
throw new Error(`Invalid public key length: expected ${PUBLIC_KEY_SIZE} bytes, got ${this.key.length}`);
|
|
626
|
+
}
|
|
750
627
|
}
|
|
751
628
|
toHash() {
|
|
752
|
-
return
|
|
629
|
+
return sha2562.create().update(this.key).digest()[0];
|
|
753
630
|
}
|
|
754
631
|
toBytes() {
|
|
755
632
|
return this.key;
|
|
756
633
|
}
|
|
757
634
|
toString() {
|
|
758
|
-
return (
|
|
635
|
+
return bytesToHex2(this.key);
|
|
759
636
|
}
|
|
760
637
|
equals(other) {
|
|
761
638
|
let otherKey;
|
|
@@ -764,28 +641,39 @@ var PublicKey = class _PublicKey {
|
|
|
764
641
|
} else if (other instanceof Uint8Array) {
|
|
765
642
|
otherKey = other;
|
|
766
643
|
} else if (typeof other === "string") {
|
|
767
|
-
otherKey = hexToBytes(other
|
|
644
|
+
otherKey = hexToBytes(other);
|
|
768
645
|
} else {
|
|
769
646
|
throw new Error("Invalid type for PublicKey comparison");
|
|
770
647
|
}
|
|
771
|
-
|
|
648
|
+
if (otherKey.length !== PUBLIC_KEY_SIZE) {
|
|
649
|
+
throw new Error(
|
|
650
|
+
`Invalid public key length for comparison: expected ${PUBLIC_KEY_SIZE} bytes, got ${otherKey.length}`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
return equalBytes(this.key, otherKey);
|
|
772
654
|
}
|
|
773
655
|
verify(message, signature) {
|
|
774
656
|
if (signature.length !== SIGNATURE_SIZE) {
|
|
775
657
|
throw new Error(`Invalid signature length: expected ${SIGNATURE_SIZE} bytes, got ${signature.length}`);
|
|
776
658
|
}
|
|
777
|
-
return
|
|
659
|
+
return ed25519.verify(signature, message, this.key);
|
|
660
|
+
}
|
|
661
|
+
static fromBytes(key) {
|
|
662
|
+
return new _PublicKey(key);
|
|
663
|
+
}
|
|
664
|
+
static fromString(key) {
|
|
665
|
+
return new _PublicKey(key);
|
|
778
666
|
}
|
|
779
667
|
};
|
|
780
668
|
var PrivateKey = class _PrivateKey {
|
|
781
669
|
constructor(seed) {
|
|
782
670
|
if (typeof seed === "string") {
|
|
783
|
-
seed = hexToBytes(seed
|
|
671
|
+
seed = hexToBytes(seed);
|
|
784
672
|
}
|
|
785
673
|
if (seed.length !== SEED_SIZE) {
|
|
786
674
|
throw new Error(`Invalid seed length: expected ${SEED_SIZE} bytes, got ${seed.length}`);
|
|
787
675
|
}
|
|
788
|
-
const { secretKey, publicKey } =
|
|
676
|
+
const { secretKey, publicKey } = ed25519.keygen(seed);
|
|
789
677
|
this.secretKey = secretKey;
|
|
790
678
|
this.publicKey = new PublicKey(publicKey);
|
|
791
679
|
}
|
|
@@ -796,10 +684,10 @@ var PrivateKey = class _PrivateKey {
|
|
|
796
684
|
return this.secretKey;
|
|
797
685
|
}
|
|
798
686
|
toString() {
|
|
799
|
-
return (
|
|
687
|
+
return bytesToHex2(this.secretKey);
|
|
800
688
|
}
|
|
801
689
|
sign(message) {
|
|
802
|
-
return
|
|
690
|
+
return ed25519.sign(message, this.secretKey);
|
|
803
691
|
}
|
|
804
692
|
calculateSharedSecret(other) {
|
|
805
693
|
let otherPublicKey;
|
|
@@ -812,43 +700,40 @@ var PrivateKey = class _PrivateKey {
|
|
|
812
700
|
} else {
|
|
813
701
|
throw new Error("Invalid type for calculateSharedSecret comparison");
|
|
814
702
|
}
|
|
815
|
-
return
|
|
703
|
+
return x25519.getSharedSecret(this.secretKey, otherPublicKey.toBytes());
|
|
816
704
|
}
|
|
817
705
|
static generate() {
|
|
818
|
-
const { secretKey } =
|
|
706
|
+
const { secretKey } = ed25519.keygen();
|
|
819
707
|
return new _PrivateKey(secretKey);
|
|
820
708
|
}
|
|
821
709
|
};
|
|
822
710
|
var SharedSecret = class _SharedSecret {
|
|
823
711
|
constructor(secret) {
|
|
824
|
-
if (secret.length === SHARED_SECRET_SIZE / 2) {
|
|
825
|
-
const padded = new Uint8Array(SHARED_SECRET_SIZE);
|
|
826
|
-
padded.set(secret, SHARED_SECRET_SIZE - secret.length);
|
|
827
|
-
secret = padded;
|
|
828
|
-
}
|
|
829
712
|
if (secret.length !== SHARED_SECRET_SIZE) {
|
|
830
713
|
throw new Error(`Invalid shared secret length: expected ${SHARED_SECRET_SIZE} bytes, got ${secret.length}`);
|
|
831
714
|
}
|
|
832
|
-
this.secret =
|
|
715
|
+
this.secret = new Uint8Array(SHARED_SECRET_SIZE * 2);
|
|
716
|
+
this.secret.set(secret, 0);
|
|
833
717
|
}
|
|
834
718
|
toHash() {
|
|
835
|
-
|
|
719
|
+
const hash = sha2562.create().update(this.secret.slice(0, 16)).digest();
|
|
720
|
+
return hash[0];
|
|
836
721
|
}
|
|
837
722
|
toBytes() {
|
|
838
|
-
return this.secret;
|
|
723
|
+
return this.secret.slice(0, 16);
|
|
839
724
|
}
|
|
840
725
|
toString() {
|
|
841
|
-
return (0,
|
|
726
|
+
return bytesToHex2(this.secret.slice(0, 16));
|
|
842
727
|
}
|
|
843
728
|
decrypt(hmac2, ciphertext) {
|
|
844
729
|
if (hmac2.length !== HMAC_SIZE) {
|
|
845
730
|
throw new Error(`Invalid HMAC length: expected ${HMAC_SIZE} bytes, got ${hmac2.length}`);
|
|
846
731
|
}
|
|
847
732
|
const expectedHmac = this.calculateHmac(ciphertext);
|
|
848
|
-
if (!
|
|
849
|
-
throw new Error(`Invalid HMAC: decryption failed: expected ${(
|
|
733
|
+
if (!equalBytes(hmac2, expectedHmac)) {
|
|
734
|
+
throw new Error(`Invalid HMAC: decryption failed: expected ${bytesToHex2(expectedHmac)}, got ${bytesToHex2(hmac2)}`);
|
|
850
735
|
}
|
|
851
|
-
const cipher =
|
|
736
|
+
const cipher = ecb(this.secret.slice(0, 16), { disablePadding: true });
|
|
852
737
|
const plaintext = new Uint8Array(ciphertext.length);
|
|
853
738
|
for (let i = 0; i < ciphertext.length; i += 16) {
|
|
854
739
|
const block = ciphertext.slice(i, i + 16);
|
|
@@ -863,7 +748,7 @@ var SharedSecret = class _SharedSecret {
|
|
|
863
748
|
}
|
|
864
749
|
encrypt(data) {
|
|
865
750
|
const key = this.secret.slice(0, 16);
|
|
866
|
-
const cipher =
|
|
751
|
+
const cipher = ecb(key, { disablePadding: true });
|
|
867
752
|
const fullBlocks = Math.floor(data.length / 16);
|
|
868
753
|
const remaining = data.length % 16;
|
|
869
754
|
const ciphertext = new Uint8Array((fullBlocks + (remaining > 0 ? 1 : 0)) * 16);
|
|
@@ -882,7 +767,7 @@ var SharedSecret = class _SharedSecret {
|
|
|
882
767
|
return { hmac: hmac2, ciphertext };
|
|
883
768
|
}
|
|
884
769
|
calculateHmac(data) {
|
|
885
|
-
return
|
|
770
|
+
return hmac(sha2562, this.secret, data).slice(0, HMAC_SIZE);
|
|
886
771
|
}
|
|
887
772
|
static fromName(name) {
|
|
888
773
|
if (name === "Public") {
|
|
@@ -890,14 +775,14 @@ var SharedSecret = class _SharedSecret {
|
|
|
890
775
|
} else if (!/^#/.test(name)) {
|
|
891
776
|
throw new Error("Only the 'Public' group or groups starting with '#' are supported");
|
|
892
777
|
}
|
|
893
|
-
const hash =
|
|
778
|
+
const hash = sha2562.create().update(new TextEncoder().encode(name)).digest();
|
|
894
779
|
return new _SharedSecret(hash.slice(0, SHARED_SECRET_SIZE));
|
|
895
780
|
}
|
|
896
781
|
};
|
|
897
782
|
var StaticSecret = class {
|
|
898
783
|
constructor(secret) {
|
|
899
784
|
if (typeof secret === "string") {
|
|
900
|
-
secret = hexToBytes(secret
|
|
785
|
+
secret = hexToBytes(secret);
|
|
901
786
|
}
|
|
902
787
|
if (secret.length !== STATIC_SECRET_SIZE) {
|
|
903
788
|
throw new Error(`Invalid static secret length: expected ${STATIC_SECRET_SIZE} bytes, got ${secret.length}`);
|
|
@@ -905,16 +790,17 @@ var StaticSecret = class {
|
|
|
905
790
|
this.secret = secret;
|
|
906
791
|
}
|
|
907
792
|
publicKey() {
|
|
908
|
-
const publicKey =
|
|
793
|
+
const publicKey = x25519.getPublicKey(this.secret);
|
|
909
794
|
return new PublicKey(publicKey);
|
|
910
795
|
}
|
|
911
796
|
diffieHellman(otherPublicKey) {
|
|
912
|
-
const sharedSecret =
|
|
913
|
-
return new SharedSecret(sharedSecret);
|
|
797
|
+
const sharedSecret = x25519.getSharedSecret(this.secret, otherPublicKey.toBytes());
|
|
798
|
+
return new SharedSecret(sharedSecret.slice(0, 16));
|
|
914
799
|
}
|
|
915
800
|
};
|
|
916
801
|
|
|
917
802
|
// src/identity.ts
|
|
803
|
+
import { hexToBytes as hexToBytes2, Reader as Reader2, Writer } from "@hamradio/packet";
|
|
918
804
|
var parseNodeHash = (hash) => {
|
|
919
805
|
if (hash instanceof Uint8Array) {
|
|
920
806
|
return hash[0];
|
|
@@ -925,7 +811,7 @@ var parseNodeHash = (hash) => {
|
|
|
925
811
|
}
|
|
926
812
|
return hash;
|
|
927
813
|
} else if (typeof hash === "string") {
|
|
928
|
-
const parsed =
|
|
814
|
+
const parsed = hexToBytes2(hash);
|
|
929
815
|
if (parsed.length !== 1) {
|
|
930
816
|
throw new Error("NodeHash string must represent a single byte");
|
|
931
817
|
}
|
|
@@ -941,7 +827,7 @@ var toPublicKeyBytes = (key) => {
|
|
|
941
827
|
} else if (key instanceof Uint8Array) {
|
|
942
828
|
return key;
|
|
943
829
|
} else if (typeof key === "string") {
|
|
944
|
-
return
|
|
830
|
+
return hexToBytes2(key);
|
|
945
831
|
} else {
|
|
946
832
|
throw new Error("Invalid type for toPublicKeyBytes");
|
|
947
833
|
}
|
|
@@ -1000,7 +886,7 @@ var LocalIdentity = class extends Identity {
|
|
|
1000
886
|
} else {
|
|
1001
887
|
throw new Error("Invalid type for calculateSharedSecret comparison");
|
|
1002
888
|
}
|
|
1003
|
-
return new SharedSecret(this.privateKey.calculateSharedSecret(otherPublicKey));
|
|
889
|
+
return new SharedSecret(this.privateKey.calculateSharedSecret(otherPublicKey).slice(0, 16));
|
|
1004
890
|
}
|
|
1005
891
|
};
|
|
1006
892
|
var Contact = class {
|
|
@@ -1044,12 +930,12 @@ var Group = class {
|
|
|
1044
930
|
if (data.length < 5) {
|
|
1045
931
|
throw new Error("Invalid ciphertext");
|
|
1046
932
|
}
|
|
1047
|
-
const reader = new
|
|
1048
|
-
const timestamp = reader.
|
|
1049
|
-
const flags = reader.
|
|
933
|
+
const reader = new Reader2(data);
|
|
934
|
+
const timestamp = reader.date32();
|
|
935
|
+
const flags = reader.uint8();
|
|
1050
936
|
const textType = flags >> 2 & 63;
|
|
1051
937
|
const attempt = flags & 3;
|
|
1052
|
-
const message = new TextDecoder("utf-8").decode(reader.
|
|
938
|
+
const message = new TextDecoder("utf-8").decode(reader.bytes());
|
|
1053
939
|
return {
|
|
1054
940
|
timestamp,
|
|
1055
941
|
textType,
|
|
@@ -1058,11 +944,11 @@ var Group = class {
|
|
|
1058
944
|
};
|
|
1059
945
|
}
|
|
1060
946
|
encryptText(plain) {
|
|
1061
|
-
const writer = new
|
|
1062
|
-
writer.
|
|
947
|
+
const writer = new Writer(4 + 1 + new TextEncoder().encode(plain.message).length);
|
|
948
|
+
writer.date32(plain.timestamp);
|
|
1063
949
|
const flags = (plain.textType & 63) << 2 | plain.attempt & 3;
|
|
1064
|
-
writer.
|
|
1065
|
-
writer.
|
|
950
|
+
writer.uint8(flags);
|
|
951
|
+
writer.utf8String(plain.message);
|
|
1066
952
|
const data = writer.toBytes();
|
|
1067
953
|
return this.secret.encrypt(data);
|
|
1068
954
|
}
|
|
@@ -1071,16 +957,16 @@ var Group = class {
|
|
|
1071
957
|
if (data.length < 4) {
|
|
1072
958
|
throw new Error("Invalid ciphertext");
|
|
1073
959
|
}
|
|
1074
|
-
const reader = new
|
|
960
|
+
const reader = new Reader2(data);
|
|
1075
961
|
return {
|
|
1076
|
-
timestamp: reader.
|
|
1077
|
-
data: reader.
|
|
962
|
+
timestamp: reader.date32(),
|
|
963
|
+
data: reader.bytes()
|
|
1078
964
|
};
|
|
1079
965
|
}
|
|
1080
966
|
encryptData(plain) {
|
|
1081
|
-
const writer = new
|
|
1082
|
-
writer.
|
|
1083
|
-
writer.
|
|
967
|
+
const writer = new Writer(4 + plain.data.length);
|
|
968
|
+
writer.date32(plain.timestamp);
|
|
969
|
+
writer.bytes(plain.data);
|
|
1084
970
|
const data = writer.toBytes();
|
|
1085
971
|
return this.secret.encrypt(data);
|
|
1086
972
|
}
|
|
@@ -1090,6 +976,8 @@ var Contacts = class {
|
|
|
1090
976
|
this.localIdentities = [];
|
|
1091
977
|
this.contacts = {};
|
|
1092
978
|
this.groups = {};
|
|
979
|
+
this.addGroup(new Group("Public"));
|
|
980
|
+
this.addGroup(new Group("#test"));
|
|
1093
981
|
}
|
|
1094
982
|
addLocalIdentity(identity) {
|
|
1095
983
|
this.localIdentities.push({ identity, sharedSecrets: {} });
|
|
@@ -1101,6 +989,23 @@ var Contacts = class {
|
|
|
1101
989
|
}
|
|
1102
990
|
this.contacts[hash].push(contact);
|
|
1103
991
|
}
|
|
992
|
+
hasContact(nameOrHash) {
|
|
993
|
+
if (typeof nameOrHash === "string") {
|
|
994
|
+
return Object.values(this.contacts).flat().some((contact) => contact.name.toLowerCase() === nameOrHash.toLowerCase());
|
|
995
|
+
} else {
|
|
996
|
+
const hash = parseNodeHash(nameOrHash);
|
|
997
|
+
return (this.contacts[hash] || []).length > 0;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
getContactByName(name) {
|
|
1001
|
+
const contact = Object.values(this.contacts).flat().find((contact2) => contact2.name.toLowerCase() === name.toLowerCase());
|
|
1002
|
+
return contact || null;
|
|
1003
|
+
}
|
|
1004
|
+
getContacts() {
|
|
1005
|
+
const contacts = Object.values(this.contacts).flat();
|
|
1006
|
+
contacts.sort((a, b) => a.name.localeCompare(b.name));
|
|
1007
|
+
return contacts;
|
|
1008
|
+
}
|
|
1104
1009
|
decrypt(src, dst, hmac2, ciphertext) {
|
|
1105
1010
|
let contacts = [];
|
|
1106
1011
|
if (src instanceof PublicKey) {
|
|
@@ -1148,17 +1053,45 @@ var Contacts = class {
|
|
|
1148
1053
|
return sharedSecret;
|
|
1149
1054
|
}
|
|
1150
1055
|
addGroup(group) {
|
|
1151
|
-
const
|
|
1056
|
+
for (const key of Object.keys(this.groups)) {
|
|
1057
|
+
const hash2 = Number(key);
|
|
1058
|
+
this.groups[hash2] = this.groups[hash2].filter((g) => g.name.toLowerCase() !== group.name.toLowerCase());
|
|
1059
|
+
if (this.groups[hash2].length === 0) {
|
|
1060
|
+
delete this.groups[hash2];
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
const hash = group.hash();
|
|
1152
1064
|
if (!this.groups[hash]) {
|
|
1153
1065
|
this.groups[hash] = [];
|
|
1154
1066
|
}
|
|
1155
1067
|
this.groups[hash].push(group);
|
|
1156
1068
|
}
|
|
1069
|
+
hasGroup(nameOrHash) {
|
|
1070
|
+
if (typeof nameOrHash === "string") {
|
|
1071
|
+
return Object.values(this.groups).flat().some((group) => group.name.toLowerCase() === nameOrHash.toLowerCase());
|
|
1072
|
+
} else {
|
|
1073
|
+
const hash = parseNodeHash(nameOrHash);
|
|
1074
|
+
return (this.groups[hash] || []).length > 0;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
getGroupByName(name) {
|
|
1078
|
+
const group = Object.values(this.groups).flat().find((group2) => group2.name.toLowerCase() === name.toLowerCase());
|
|
1079
|
+
return group || null;
|
|
1080
|
+
}
|
|
1081
|
+
getGroups() {
|
|
1082
|
+
const groups = Object.values(this.groups).flat();
|
|
1083
|
+
groups.sort((a, b) => {
|
|
1084
|
+
if (a.name === "Public") return -1;
|
|
1085
|
+
if (b.name === "Public") return 1;
|
|
1086
|
+
return a.name.localeCompare(b.name);
|
|
1087
|
+
});
|
|
1088
|
+
return groups;
|
|
1089
|
+
}
|
|
1157
1090
|
decryptGroupText(channelHash, hmac2, ciphertext) {
|
|
1158
1091
|
const hash = parseNodeHash(channelHash);
|
|
1159
1092
|
const groups = this.groups[hash] || [];
|
|
1160
1093
|
if (groups.length === 0) {
|
|
1161
|
-
throw new Error(
|
|
1094
|
+
throw new Error(`Unknown group hash ${hash.toString(16).padStart(2, "0")}`);
|
|
1162
1095
|
}
|
|
1163
1096
|
for (const group of groups) {
|
|
1164
1097
|
try {
|
|
@@ -1167,13 +1100,13 @@ var Contacts = class {
|
|
|
1167
1100
|
} catch {
|
|
1168
1101
|
}
|
|
1169
1102
|
}
|
|
1170
|
-
throw new Error(
|
|
1103
|
+
throw new Error(`Decryption failed with all known groups with hash ${hash.toString(16).padStart(2, "0")}`);
|
|
1171
1104
|
}
|
|
1172
1105
|
decryptGroupData(channelHash, hmac2, ciphertext) {
|
|
1173
1106
|
const hash = parseNodeHash(channelHash);
|
|
1174
1107
|
const groups = this.groups[hash] || [];
|
|
1175
1108
|
if (groups.length === 0) {
|
|
1176
|
-
throw new Error(
|
|
1109
|
+
throw new Error(`Unknown group hash ${hash.toString(16).padStart(2, "0")}`);
|
|
1177
1110
|
}
|
|
1178
1111
|
for (const group of groups) {
|
|
1179
1112
|
try {
|
|
@@ -1182,11 +1115,10 @@ var Contacts = class {
|
|
|
1182
1115
|
} catch {
|
|
1183
1116
|
}
|
|
1184
1117
|
}
|
|
1185
|
-
throw new Error(
|
|
1118
|
+
throw new Error(`Decryption failed with all known groups with hash ${hash.toString(16).padStart(2, "0")}`);
|
|
1186
1119
|
}
|
|
1187
1120
|
};
|
|
1188
|
-
|
|
1189
|
-
0 && (module.exports = {
|
|
1121
|
+
export {
|
|
1190
1122
|
Contact,
|
|
1191
1123
|
Contacts,
|
|
1192
1124
|
Group,
|
|
@@ -1203,4 +1135,4 @@ var Contacts = class {
|
|
|
1203
1135
|
StaticSecret,
|
|
1204
1136
|
TextType,
|
|
1205
1137
|
parseNodeHash
|
|
1206
|
-
}
|
|
1138
|
+
};
|