@hamradio/meshcore 1.2.1 → 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} +365 -328
- package/dist/{index.d.mts → index.d.cts} +118 -132
- package/dist/index.d.ts +118 -132
- package/dist/index.js +322 -369
- 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,60 +106,83 @@ 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}`);
|
|
279
119
|
}
|
|
280
120
|
this.structure = [
|
|
281
121
|
/* Header segment */
|
|
282
|
-
{
|
|
283
|
-
|
|
122
|
+
{
|
|
123
|
+
name: "header",
|
|
124
|
+
data: new Uint8Array([this.header]).buffer,
|
|
125
|
+
fields: [
|
|
126
|
+
/* Header flags */
|
|
127
|
+
{
|
|
128
|
+
name: "flags",
|
|
129
|
+
type: FieldType.BITS,
|
|
130
|
+
length: 1,
|
|
131
|
+
bits: [
|
|
132
|
+
{ name: "payload version", size: 2 },
|
|
133
|
+
{ name: "payload type", size: 4 },
|
|
134
|
+
{ name: "route type", size: 2 }
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
/* Transport codes */
|
|
140
|
+
..._Packet.hasTransportCodes(this.routeType) ? [
|
|
284
141
|
{
|
|
285
|
-
name: "
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
}
|
|
292
162
|
]
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
|
|
163
|
+
}
|
|
164
|
+
] : [],
|
|
165
|
+
/* Path length and hashes */
|
|
166
|
+
{
|
|
167
|
+
name: "path",
|
|
168
|
+
data: new Uint8Array([this.pathLength, ...this.path]).buffer,
|
|
169
|
+
fields: [
|
|
296
170
|
{
|
|
297
|
-
name: "
|
|
298
|
-
type:
|
|
299
|
-
|
|
171
|
+
name: "path length",
|
|
172
|
+
type: FieldType.UINT8,
|
|
173
|
+
length: 1,
|
|
174
|
+
bits: [
|
|
175
|
+
{ name: "path hash size", size: 2 },
|
|
176
|
+
{ name: "path hash count", size: 6 }
|
|
177
|
+
]
|
|
300
178
|
},
|
|
301
179
|
{
|
|
302
|
-
name: "
|
|
303
|
-
type:
|
|
304
|
-
|
|
180
|
+
name: "path hashes",
|
|
181
|
+
type: pathHashType,
|
|
182
|
+
length: this.path.length
|
|
305
183
|
}
|
|
306
|
-
]
|
|
307
|
-
|
|
308
|
-
{
|
|
309
|
-
name: "path length",
|
|
310
|
-
type: 1 /* UINT8 */,
|
|
311
|
-
size: 1,
|
|
312
|
-
bits: [
|
|
313
|
-
{ name: "path hash size", size: 2 },
|
|
314
|
-
{ name: "path hash count", size: 6 }
|
|
315
|
-
]
|
|
316
|
-
},
|
|
317
|
-
{
|
|
318
|
-
name: "path hashes",
|
|
319
|
-
type: pathHashType,
|
|
320
|
-
size: this.path.length
|
|
321
|
-
}
|
|
322
|
-
] }
|
|
184
|
+
]
|
|
185
|
+
}
|
|
323
186
|
];
|
|
324
187
|
}
|
|
325
188
|
decode(withStructure) {
|
|
@@ -361,7 +224,6 @@ var Packet = class _Packet {
|
|
|
361
224
|
default:
|
|
362
225
|
throw new Error(`Unsupported payload type: ${this.payloadType}`);
|
|
363
226
|
}
|
|
364
|
-
console.log("packet decode with structure:", typeof withStructure, withStructure, { result });
|
|
365
227
|
if (typeof withStructure === "boolean" && withStructure && "segment" in result && "payload" in result) {
|
|
366
228
|
this.ensureStructure();
|
|
367
229
|
const structure = [...this.structure, result.segment];
|
|
@@ -370,17 +232,17 @@ var Packet = class _Packet {
|
|
|
370
232
|
return result;
|
|
371
233
|
}
|
|
372
234
|
decodeEncryptedPayload(reader) {
|
|
373
|
-
const cipherMAC = reader.
|
|
374
|
-
const cipherText = reader.
|
|
235
|
+
const cipherMAC = reader.bytes(2);
|
|
236
|
+
const cipherText = reader.bytes();
|
|
375
237
|
return { cipherMAC, cipherText };
|
|
376
238
|
}
|
|
377
239
|
decodeRequest(withSegment) {
|
|
378
240
|
if (this.payload.length < 4) {
|
|
379
241
|
throw new Error("Invalid request payload: too short");
|
|
380
242
|
}
|
|
381
|
-
const reader =
|
|
382
|
-
const dst = reader.
|
|
383
|
-
const src = reader.
|
|
243
|
+
const reader = Reader.fromBytes(this.payload);
|
|
244
|
+
const dst = reader.uint8();
|
|
245
|
+
const src = reader.uint8();
|
|
384
246
|
const encrypted = this.decodeEncryptedPayload(reader);
|
|
385
247
|
const payload = {
|
|
386
248
|
type: 0 /* REQUEST */,
|
|
@@ -391,12 +253,17 @@ var Packet = class _Packet {
|
|
|
391
253
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
392
254
|
const segment = {
|
|
393
255
|
name: "request payload",
|
|
394
|
-
data: this.payload,
|
|
256
|
+
data: new Uint8Array(this.payload).buffer,
|
|
395
257
|
fields: [
|
|
396
|
-
{ name: "destination hash", type:
|
|
397
|
-
{ name: "source hash", type:
|
|
398
|
-
{ name: "cipher MAC", type:
|
|
399
|
-
{
|
|
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
|
+
}
|
|
400
267
|
]
|
|
401
268
|
};
|
|
402
269
|
return { payload, segment };
|
|
@@ -407,9 +274,9 @@ var Packet = class _Packet {
|
|
|
407
274
|
if (this.payload.length < 4) {
|
|
408
275
|
throw new Error("Invalid response payload: too short");
|
|
409
276
|
}
|
|
410
|
-
const reader =
|
|
411
|
-
const dst = reader.
|
|
412
|
-
const src = reader.
|
|
277
|
+
const reader = Reader.fromBytes(this.payload);
|
|
278
|
+
const dst = reader.uint8();
|
|
279
|
+
const src = reader.uint8();
|
|
413
280
|
const encrypted = this.decodeEncryptedPayload(reader);
|
|
414
281
|
const payload = {
|
|
415
282
|
type: 1 /* RESPONSE */,
|
|
@@ -420,12 +287,17 @@ var Packet = class _Packet {
|
|
|
420
287
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
421
288
|
const segment = {
|
|
422
289
|
name: "response payload",
|
|
423
|
-
data: this.payload,
|
|
290
|
+
data: new Uint8Array(this.payload).buffer,
|
|
424
291
|
fields: [
|
|
425
|
-
{ name: "destination hash", type:
|
|
426
|
-
{ name: "source hash", type:
|
|
427
|
-
{ name: "cipher MAC", type:
|
|
428
|
-
{
|
|
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
|
+
}
|
|
429
301
|
]
|
|
430
302
|
};
|
|
431
303
|
return { payload, segment };
|
|
@@ -436,9 +308,9 @@ var Packet = class _Packet {
|
|
|
436
308
|
if (this.payload.length < 4) {
|
|
437
309
|
throw new Error("Invalid text payload: too short");
|
|
438
310
|
}
|
|
439
|
-
const reader =
|
|
440
|
-
const dst = reader.
|
|
441
|
-
const src = reader.
|
|
311
|
+
const reader = Reader.fromBytes(this.payload);
|
|
312
|
+
const dst = reader.uint8();
|
|
313
|
+
const src = reader.uint8();
|
|
442
314
|
const encrypted = this.decodeEncryptedPayload(reader);
|
|
443
315
|
const payload = {
|
|
444
316
|
type: 2 /* TEXT */,
|
|
@@ -449,12 +321,17 @@ var Packet = class _Packet {
|
|
|
449
321
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
450
322
|
const segment = {
|
|
451
323
|
name: "text payload",
|
|
452
|
-
data: this.payload,
|
|
324
|
+
data: new Uint8Array(this.payload).buffer,
|
|
453
325
|
fields: [
|
|
454
|
-
{ name: "destination hash", type:
|
|
455
|
-
{ name: "source hash", type:
|
|
456
|
-
{ name: "cipher MAC", type:
|
|
457
|
-
{
|
|
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
|
+
}
|
|
458
335
|
]
|
|
459
336
|
};
|
|
460
337
|
return { payload, segment };
|
|
@@ -465,8 +342,8 @@ var Packet = class _Packet {
|
|
|
465
342
|
if (this.payload.length < 4) {
|
|
466
343
|
throw new Error("Invalid ack payload: too short");
|
|
467
344
|
}
|
|
468
|
-
const reader =
|
|
469
|
-
const checksum = reader.
|
|
345
|
+
const reader = Reader.fromBytes(this.payload);
|
|
346
|
+
const checksum = reader.bytes(4);
|
|
470
347
|
const payload = {
|
|
471
348
|
type: 3 /* ACK */,
|
|
472
349
|
checksum
|
|
@@ -474,10 +351,8 @@ var Packet = class _Packet {
|
|
|
474
351
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
475
352
|
const segment = {
|
|
476
353
|
name: "ack payload",
|
|
477
|
-
data: this.payload,
|
|
478
|
-
fields: [
|
|
479
|
-
{ name: "checksum", type: 6 /* BYTES */, size: 4, value: checksum }
|
|
480
|
-
]
|
|
354
|
+
data: new Uint8Array(this.payload).buffer,
|
|
355
|
+
fields: [{ name: "checksum", type: FieldType.BYTES, length: 4, value: checksum }]
|
|
481
356
|
};
|
|
482
357
|
return { payload, segment };
|
|
483
358
|
}
|
|
@@ -487,26 +362,26 @@ var Packet = class _Packet {
|
|
|
487
362
|
if (this.payload.length < 4) {
|
|
488
363
|
throw new Error("Invalid advert payload: too short");
|
|
489
364
|
}
|
|
490
|
-
const reader =
|
|
365
|
+
const reader = Reader.fromBytes(this.payload);
|
|
491
366
|
const payload = {
|
|
492
367
|
type: 4 /* ADVERT */,
|
|
493
|
-
publicKey: reader.
|
|
494
|
-
timestamp: reader.
|
|
495
|
-
signature: reader.
|
|
368
|
+
publicKey: reader.bytes(32),
|
|
369
|
+
timestamp: reader.date32(),
|
|
370
|
+
signature: reader.bytes(64)
|
|
496
371
|
};
|
|
497
372
|
let segment;
|
|
498
373
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
499
374
|
segment = {
|
|
500
375
|
name: "advert payload",
|
|
501
|
-
data: this.payload,
|
|
376
|
+
data: new Uint8Array(this.payload).buffer,
|
|
502
377
|
fields: [
|
|
503
|
-
{ type:
|
|
504
|
-
{ type:
|
|
505
|
-
{ 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 }
|
|
506
381
|
]
|
|
507
382
|
};
|
|
508
383
|
}
|
|
509
|
-
const flags = reader.
|
|
384
|
+
const flags = reader.uint8();
|
|
510
385
|
const appdata = {
|
|
511
386
|
nodeType: flags & 15,
|
|
512
387
|
hasLocation: (flags & 16 /* HAS_LOCATION */) !== 0,
|
|
@@ -515,44 +390,50 @@ var Packet = class _Packet {
|
|
|
515
390
|
hasName: (flags & 128 /* HAS_NAME */) !== 0
|
|
516
391
|
};
|
|
517
392
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
518
|
-
segment.fields.push({
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
+
});
|
|
525
406
|
}
|
|
526
407
|
if (appdata.hasLocation) {
|
|
527
|
-
const lat = reader.
|
|
528
|
-
const lon = reader.
|
|
408
|
+
const lat = reader.int32() / 1e6;
|
|
409
|
+
const lon = reader.int32() / 1e6;
|
|
529
410
|
appdata.location = [lat, lon];
|
|
530
411
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
531
|
-
segment.fields.push({ type:
|
|
532
|
-
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 });
|
|
533
414
|
}
|
|
534
415
|
}
|
|
535
416
|
if (appdata.hasFeature1) {
|
|
536
|
-
appdata.feature1 = reader.
|
|
417
|
+
appdata.feature1 = reader.uint16();
|
|
537
418
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
538
|
-
segment.fields.push({ type:
|
|
419
|
+
segment.fields.push({ type: FieldType.UINT16_LE, name: "feature1", length: 2, value: appdata.feature1 });
|
|
539
420
|
}
|
|
540
421
|
}
|
|
541
422
|
if (appdata.hasFeature2) {
|
|
542
|
-
appdata.feature2 = reader.
|
|
423
|
+
appdata.feature2 = reader.uint16();
|
|
543
424
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
544
|
-
segment.fields.push({ type:
|
|
425
|
+
segment.fields.push({ type: FieldType.UINT16_LE, name: "feature2", length: 2, value: appdata.feature2 });
|
|
545
426
|
}
|
|
546
427
|
}
|
|
547
428
|
if (appdata.hasName) {
|
|
548
|
-
|
|
549
|
-
let nullPos = nameBytes.indexOf(0);
|
|
550
|
-
if (nullPos === -1) {
|
|
551
|
-
nullPos = nameBytes.length;
|
|
552
|
-
}
|
|
553
|
-
appdata.name = new TextDecoder("utf-8").decode(nameBytes.subarray(0, nullPos));
|
|
429
|
+
appdata.name = reader.cString();
|
|
554
430
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
555
|
-
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
|
+
});
|
|
556
437
|
}
|
|
557
438
|
}
|
|
558
439
|
if (typeof withSegment === "boolean" && withSegment && typeof segment !== "undefined") {
|
|
@@ -564,8 +445,8 @@ var Packet = class _Packet {
|
|
|
564
445
|
if (this.payload.length < 3) {
|
|
565
446
|
throw new Error("Invalid group text payload: too short");
|
|
566
447
|
}
|
|
567
|
-
const reader =
|
|
568
|
-
const channelHash = reader.
|
|
448
|
+
const reader = Reader.fromBytes(this.payload);
|
|
449
|
+
const channelHash = reader.uint8();
|
|
569
450
|
const encrypted = this.decodeEncryptedPayload(reader);
|
|
570
451
|
const payload = {
|
|
571
452
|
type: 5 /* GROUP_TEXT */,
|
|
@@ -575,11 +456,16 @@ var Packet = class _Packet {
|
|
|
575
456
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
576
457
|
const segment = {
|
|
577
458
|
name: "group text payload",
|
|
578
|
-
data: this.payload,
|
|
459
|
+
data: new Uint8Array(this.payload).buffer,
|
|
579
460
|
fields: [
|
|
580
|
-
{ name: "channel hash", type:
|
|
581
|
-
{ name: "cipher MAC", type:
|
|
582
|
-
{
|
|
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
|
+
}
|
|
583
469
|
]
|
|
584
470
|
};
|
|
585
471
|
return { payload, segment };
|
|
@@ -590,20 +476,25 @@ var Packet = class _Packet {
|
|
|
590
476
|
if (this.payload.length < 3) {
|
|
591
477
|
throw new Error("Invalid group data payload: too short");
|
|
592
478
|
}
|
|
593
|
-
const reader =
|
|
479
|
+
const reader = Reader.fromBytes(this.payload);
|
|
594
480
|
const payload = {
|
|
595
481
|
type: 6 /* GROUP_DATA */,
|
|
596
|
-
channelHash: reader.
|
|
482
|
+
channelHash: reader.uint8(),
|
|
597
483
|
encrypted: this.decodeEncryptedPayload(reader)
|
|
598
484
|
};
|
|
599
485
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
600
486
|
const segment = {
|
|
601
487
|
name: "group data payload",
|
|
602
|
-
data: this.payload,
|
|
488
|
+
data: new Uint8Array(this.payload).buffer,
|
|
603
489
|
fields: [
|
|
604
|
-
{ name: "channel hash", type:
|
|
605
|
-
{ name: "cipher MAC", type:
|
|
606
|
-
{
|
|
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
|
+
}
|
|
607
498
|
]
|
|
608
499
|
};
|
|
609
500
|
return { payload, segment };
|
|
@@ -614,22 +505,27 @@ var Packet = class _Packet {
|
|
|
614
505
|
if (this.payload.length < 1 + 32 + 2) {
|
|
615
506
|
throw new Error("Invalid anon req payload: too short");
|
|
616
507
|
}
|
|
617
|
-
const reader =
|
|
508
|
+
const reader = Reader.fromBytes(this.payload);
|
|
618
509
|
const payload = {
|
|
619
510
|
type: 7 /* ANON_REQ */,
|
|
620
|
-
dst: reader.
|
|
621
|
-
publicKey: reader.
|
|
511
|
+
dst: reader.uint8(),
|
|
512
|
+
publicKey: reader.bytes(32),
|
|
622
513
|
encrypted: this.decodeEncryptedPayload(reader)
|
|
623
514
|
};
|
|
624
515
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
625
516
|
const segment = {
|
|
626
517
|
name: "anon req payload",
|
|
627
|
-
data: this.payload,
|
|
518
|
+
data: new Uint8Array(this.payload).buffer,
|
|
628
519
|
fields: [
|
|
629
|
-
{ name: "destination hash", type:
|
|
630
|
-
{ name: "public key", type:
|
|
631
|
-
{ name: "cipher MAC", type:
|
|
632
|
-
{
|
|
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
|
+
}
|
|
633
529
|
]
|
|
634
530
|
};
|
|
635
531
|
return { payload, segment };
|
|
@@ -640,19 +536,19 @@ var Packet = class _Packet {
|
|
|
640
536
|
if (this.payload.length < 2) {
|
|
641
537
|
throw new Error("Invalid path payload: too short");
|
|
642
538
|
}
|
|
643
|
-
const reader =
|
|
539
|
+
const reader = Reader.fromBytes(this.payload);
|
|
644
540
|
const payload = {
|
|
645
541
|
type: 8 /* PATH */,
|
|
646
|
-
dst: reader.
|
|
647
|
-
src: reader.
|
|
542
|
+
dst: reader.uint8(),
|
|
543
|
+
src: reader.uint8()
|
|
648
544
|
};
|
|
649
545
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
650
546
|
const segment = {
|
|
651
547
|
name: "path payload",
|
|
652
|
-
data: this.payload,
|
|
548
|
+
data: new Uint8Array(this.payload).buffer,
|
|
653
549
|
fields: [
|
|
654
|
-
{ name: "destination hash", type:
|
|
655
|
-
{ 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 }
|
|
656
552
|
]
|
|
657
553
|
};
|
|
658
554
|
return { payload, segment };
|
|
@@ -663,23 +559,23 @@ var Packet = class _Packet {
|
|
|
663
559
|
if (this.payload.length < 9) {
|
|
664
560
|
throw new Error("Invalid trace payload: too short");
|
|
665
561
|
}
|
|
666
|
-
const reader =
|
|
562
|
+
const reader = Reader.fromBytes(this.payload);
|
|
667
563
|
const payload = {
|
|
668
564
|
type: 9 /* TRACE */,
|
|
669
|
-
tag: reader.
|
|
670
|
-
authCode: reader.
|
|
671
|
-
flags: reader.
|
|
672
|
-
nodes: reader.
|
|
565
|
+
tag: reader.uint32(),
|
|
566
|
+
authCode: reader.uint32(),
|
|
567
|
+
flags: reader.uint8() & 3,
|
|
568
|
+
nodes: reader.bytes()
|
|
673
569
|
};
|
|
674
570
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
675
571
|
const segment = {
|
|
676
572
|
name: "trace payload",
|
|
677
|
-
data: this.payload,
|
|
573
|
+
data: new Uint8Array(this.payload).buffer,
|
|
678
574
|
fields: [
|
|
679
|
-
{ name: "tag", type:
|
|
680
|
-
{ name: "auth code", type:
|
|
681
|
-
{ name: "flags", type:
|
|
682
|
-
{ 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 }
|
|
683
579
|
]
|
|
684
580
|
};
|
|
685
581
|
return { payload, segment };
|
|
@@ -694,10 +590,8 @@ var Packet = class _Packet {
|
|
|
694
590
|
if (typeof withSegment === "boolean" && withSegment) {
|
|
695
591
|
const segment = {
|
|
696
592
|
name: "raw custom payload",
|
|
697
|
-
data: this.payload,
|
|
698
|
-
fields: [
|
|
699
|
-
{ name: "data", type: 6 /* BYTES */, size: this.payload.length, value: this.payload }
|
|
700
|
-
]
|
|
593
|
+
data: new Uint8Array(this.payload).buffer,
|
|
594
|
+
fields: [{ name: "data", type: FieldType.BYTES, length: this.payload.length, value: this.payload }]
|
|
701
595
|
};
|
|
702
596
|
return { payload, segment };
|
|
703
597
|
}
|
|
@@ -706,35 +600,39 @@ var Packet = class _Packet {
|
|
|
706
600
|
};
|
|
707
601
|
|
|
708
602
|
// src/crypto.ts
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
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";
|
|
713
608
|
var PUBLIC_KEY_SIZE = 32;
|
|
714
609
|
var SEED_SIZE = 32;
|
|
715
610
|
var HMAC_SIZE = 2;
|
|
716
|
-
var SHARED_SECRET_SIZE =
|
|
611
|
+
var SHARED_SECRET_SIZE = 16;
|
|
717
612
|
var SIGNATURE_SIZE = 64;
|
|
718
613
|
var STATIC_SECRET_SIZE = 32;
|
|
719
|
-
var publicSecret = hexToBytes("8b3387e9c5cdea6ac9e5edbaa115cd72"
|
|
614
|
+
var publicSecret = hexToBytes("8b3387e9c5cdea6ac9e5edbaa115cd72");
|
|
720
615
|
var PublicKey = class _PublicKey {
|
|
721
616
|
constructor(key) {
|
|
722
617
|
if (typeof key === "string") {
|
|
723
|
-
this.key = hexToBytes(key
|
|
618
|
+
this.key = hexToBytes(key);
|
|
724
619
|
} else if (key instanceof Uint8Array) {
|
|
725
620
|
this.key = key;
|
|
726
621
|
} else {
|
|
727
622
|
throw new Error("Invalid type for PublicKey constructor");
|
|
728
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
|
+
}
|
|
729
627
|
}
|
|
730
628
|
toHash() {
|
|
731
|
-
return
|
|
629
|
+
return sha2562.create().update(this.key).digest()[0];
|
|
732
630
|
}
|
|
733
631
|
toBytes() {
|
|
734
632
|
return this.key;
|
|
735
633
|
}
|
|
736
634
|
toString() {
|
|
737
|
-
return (
|
|
635
|
+
return bytesToHex2(this.key);
|
|
738
636
|
}
|
|
739
637
|
equals(other) {
|
|
740
638
|
let otherKey;
|
|
@@ -743,28 +641,39 @@ var PublicKey = class _PublicKey {
|
|
|
743
641
|
} else if (other instanceof Uint8Array) {
|
|
744
642
|
otherKey = other;
|
|
745
643
|
} else if (typeof other === "string") {
|
|
746
|
-
otherKey = hexToBytes(other
|
|
644
|
+
otherKey = hexToBytes(other);
|
|
747
645
|
} else {
|
|
748
646
|
throw new Error("Invalid type for PublicKey comparison");
|
|
749
647
|
}
|
|
750
|
-
|
|
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);
|
|
751
654
|
}
|
|
752
655
|
verify(message, signature) {
|
|
753
656
|
if (signature.length !== SIGNATURE_SIZE) {
|
|
754
657
|
throw new Error(`Invalid signature length: expected ${SIGNATURE_SIZE} bytes, got ${signature.length}`);
|
|
755
658
|
}
|
|
756
|
-
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);
|
|
757
666
|
}
|
|
758
667
|
};
|
|
759
668
|
var PrivateKey = class _PrivateKey {
|
|
760
669
|
constructor(seed) {
|
|
761
670
|
if (typeof seed === "string") {
|
|
762
|
-
seed = hexToBytes(seed
|
|
671
|
+
seed = hexToBytes(seed);
|
|
763
672
|
}
|
|
764
673
|
if (seed.length !== SEED_SIZE) {
|
|
765
674
|
throw new Error(`Invalid seed length: expected ${SEED_SIZE} bytes, got ${seed.length}`);
|
|
766
675
|
}
|
|
767
|
-
const { secretKey, publicKey } =
|
|
676
|
+
const { secretKey, publicKey } = ed25519.keygen(seed);
|
|
768
677
|
this.secretKey = secretKey;
|
|
769
678
|
this.publicKey = new PublicKey(publicKey);
|
|
770
679
|
}
|
|
@@ -775,10 +684,10 @@ var PrivateKey = class _PrivateKey {
|
|
|
775
684
|
return this.secretKey;
|
|
776
685
|
}
|
|
777
686
|
toString() {
|
|
778
|
-
return (
|
|
687
|
+
return bytesToHex2(this.secretKey);
|
|
779
688
|
}
|
|
780
689
|
sign(message) {
|
|
781
|
-
return
|
|
690
|
+
return ed25519.sign(message, this.secretKey);
|
|
782
691
|
}
|
|
783
692
|
calculateSharedSecret(other) {
|
|
784
693
|
let otherPublicKey;
|
|
@@ -791,43 +700,40 @@ var PrivateKey = class _PrivateKey {
|
|
|
791
700
|
} else {
|
|
792
701
|
throw new Error("Invalid type for calculateSharedSecret comparison");
|
|
793
702
|
}
|
|
794
|
-
return
|
|
703
|
+
return x25519.getSharedSecret(this.secretKey, otherPublicKey.toBytes());
|
|
795
704
|
}
|
|
796
705
|
static generate() {
|
|
797
|
-
const { secretKey } =
|
|
706
|
+
const { secretKey } = ed25519.keygen();
|
|
798
707
|
return new _PrivateKey(secretKey);
|
|
799
708
|
}
|
|
800
709
|
};
|
|
801
710
|
var SharedSecret = class _SharedSecret {
|
|
802
711
|
constructor(secret) {
|
|
803
|
-
if (secret.length === SHARED_SECRET_SIZE / 2) {
|
|
804
|
-
const padded = new Uint8Array(SHARED_SECRET_SIZE);
|
|
805
|
-
padded.set(secret, SHARED_SECRET_SIZE - secret.length);
|
|
806
|
-
secret = padded;
|
|
807
|
-
}
|
|
808
712
|
if (secret.length !== SHARED_SECRET_SIZE) {
|
|
809
713
|
throw new Error(`Invalid shared secret length: expected ${SHARED_SECRET_SIZE} bytes, got ${secret.length}`);
|
|
810
714
|
}
|
|
811
|
-
this.secret =
|
|
715
|
+
this.secret = new Uint8Array(SHARED_SECRET_SIZE * 2);
|
|
716
|
+
this.secret.set(secret, 0);
|
|
812
717
|
}
|
|
813
718
|
toHash() {
|
|
814
|
-
|
|
719
|
+
const hash = sha2562.create().update(this.secret.slice(0, 16)).digest();
|
|
720
|
+
return hash[0];
|
|
815
721
|
}
|
|
816
722
|
toBytes() {
|
|
817
|
-
return this.secret;
|
|
723
|
+
return this.secret.slice(0, 16);
|
|
818
724
|
}
|
|
819
725
|
toString() {
|
|
820
|
-
return (0,
|
|
726
|
+
return bytesToHex2(this.secret.slice(0, 16));
|
|
821
727
|
}
|
|
822
728
|
decrypt(hmac2, ciphertext) {
|
|
823
729
|
if (hmac2.length !== HMAC_SIZE) {
|
|
824
730
|
throw new Error(`Invalid HMAC length: expected ${HMAC_SIZE} bytes, got ${hmac2.length}`);
|
|
825
731
|
}
|
|
826
732
|
const expectedHmac = this.calculateHmac(ciphertext);
|
|
827
|
-
if (!
|
|
828
|
-
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)}`);
|
|
829
735
|
}
|
|
830
|
-
const cipher =
|
|
736
|
+
const cipher = ecb(this.secret.slice(0, 16), { disablePadding: true });
|
|
831
737
|
const plaintext = new Uint8Array(ciphertext.length);
|
|
832
738
|
for (let i = 0; i < ciphertext.length; i += 16) {
|
|
833
739
|
const block = ciphertext.slice(i, i + 16);
|
|
@@ -842,7 +748,7 @@ var SharedSecret = class _SharedSecret {
|
|
|
842
748
|
}
|
|
843
749
|
encrypt(data) {
|
|
844
750
|
const key = this.secret.slice(0, 16);
|
|
845
|
-
const cipher =
|
|
751
|
+
const cipher = ecb(key, { disablePadding: true });
|
|
846
752
|
const fullBlocks = Math.floor(data.length / 16);
|
|
847
753
|
const remaining = data.length % 16;
|
|
848
754
|
const ciphertext = new Uint8Array((fullBlocks + (remaining > 0 ? 1 : 0)) * 16);
|
|
@@ -861,7 +767,7 @@ var SharedSecret = class _SharedSecret {
|
|
|
861
767
|
return { hmac: hmac2, ciphertext };
|
|
862
768
|
}
|
|
863
769
|
calculateHmac(data) {
|
|
864
|
-
return
|
|
770
|
+
return hmac(sha2562, this.secret, data).slice(0, HMAC_SIZE);
|
|
865
771
|
}
|
|
866
772
|
static fromName(name) {
|
|
867
773
|
if (name === "Public") {
|
|
@@ -869,14 +775,14 @@ var SharedSecret = class _SharedSecret {
|
|
|
869
775
|
} else if (!/^#/.test(name)) {
|
|
870
776
|
throw new Error("Only the 'Public' group or groups starting with '#' are supported");
|
|
871
777
|
}
|
|
872
|
-
const hash =
|
|
778
|
+
const hash = sha2562.create().update(new TextEncoder().encode(name)).digest();
|
|
873
779
|
return new _SharedSecret(hash.slice(0, SHARED_SECRET_SIZE));
|
|
874
780
|
}
|
|
875
781
|
};
|
|
876
782
|
var StaticSecret = class {
|
|
877
783
|
constructor(secret) {
|
|
878
784
|
if (typeof secret === "string") {
|
|
879
|
-
secret = hexToBytes(secret
|
|
785
|
+
secret = hexToBytes(secret);
|
|
880
786
|
}
|
|
881
787
|
if (secret.length !== STATIC_SECRET_SIZE) {
|
|
882
788
|
throw new Error(`Invalid static secret length: expected ${STATIC_SECRET_SIZE} bytes, got ${secret.length}`);
|
|
@@ -884,16 +790,17 @@ var StaticSecret = class {
|
|
|
884
790
|
this.secret = secret;
|
|
885
791
|
}
|
|
886
792
|
publicKey() {
|
|
887
|
-
const publicKey =
|
|
793
|
+
const publicKey = x25519.getPublicKey(this.secret);
|
|
888
794
|
return new PublicKey(publicKey);
|
|
889
795
|
}
|
|
890
796
|
diffieHellman(otherPublicKey) {
|
|
891
|
-
const sharedSecret =
|
|
892
|
-
return new SharedSecret(sharedSecret);
|
|
797
|
+
const sharedSecret = x25519.getSharedSecret(this.secret, otherPublicKey.toBytes());
|
|
798
|
+
return new SharedSecret(sharedSecret.slice(0, 16));
|
|
893
799
|
}
|
|
894
800
|
};
|
|
895
801
|
|
|
896
802
|
// src/identity.ts
|
|
803
|
+
import { hexToBytes as hexToBytes2, Reader as Reader2, Writer } from "@hamradio/packet";
|
|
897
804
|
var parseNodeHash = (hash) => {
|
|
898
805
|
if (hash instanceof Uint8Array) {
|
|
899
806
|
return hash[0];
|
|
@@ -904,7 +811,7 @@ var parseNodeHash = (hash) => {
|
|
|
904
811
|
}
|
|
905
812
|
return hash;
|
|
906
813
|
} else if (typeof hash === "string") {
|
|
907
|
-
const parsed =
|
|
814
|
+
const parsed = hexToBytes2(hash);
|
|
908
815
|
if (parsed.length !== 1) {
|
|
909
816
|
throw new Error("NodeHash string must represent a single byte");
|
|
910
817
|
}
|
|
@@ -920,7 +827,7 @@ var toPublicKeyBytes = (key) => {
|
|
|
920
827
|
} else if (key instanceof Uint8Array) {
|
|
921
828
|
return key;
|
|
922
829
|
} else if (typeof key === "string") {
|
|
923
|
-
return
|
|
830
|
+
return hexToBytes2(key);
|
|
924
831
|
} else {
|
|
925
832
|
throw new Error("Invalid type for toPublicKeyBytes");
|
|
926
833
|
}
|
|
@@ -979,7 +886,7 @@ var LocalIdentity = class extends Identity {
|
|
|
979
886
|
} else {
|
|
980
887
|
throw new Error("Invalid type for calculateSharedSecret comparison");
|
|
981
888
|
}
|
|
982
|
-
return new SharedSecret(this.privateKey.calculateSharedSecret(otherPublicKey));
|
|
889
|
+
return new SharedSecret(this.privateKey.calculateSharedSecret(otherPublicKey).slice(0, 16));
|
|
983
890
|
}
|
|
984
891
|
};
|
|
985
892
|
var Contact = class {
|
|
@@ -1023,12 +930,12 @@ var Group = class {
|
|
|
1023
930
|
if (data.length < 5) {
|
|
1024
931
|
throw new Error("Invalid ciphertext");
|
|
1025
932
|
}
|
|
1026
|
-
const reader = new
|
|
1027
|
-
const timestamp = reader.
|
|
1028
|
-
const flags = reader.
|
|
933
|
+
const reader = new Reader2(data);
|
|
934
|
+
const timestamp = reader.date32();
|
|
935
|
+
const flags = reader.uint8();
|
|
1029
936
|
const textType = flags >> 2 & 63;
|
|
1030
937
|
const attempt = flags & 3;
|
|
1031
|
-
const message = new TextDecoder("utf-8").decode(reader.
|
|
938
|
+
const message = new TextDecoder("utf-8").decode(reader.bytes());
|
|
1032
939
|
return {
|
|
1033
940
|
timestamp,
|
|
1034
941
|
textType,
|
|
@@ -1037,11 +944,11 @@ var Group = class {
|
|
|
1037
944
|
};
|
|
1038
945
|
}
|
|
1039
946
|
encryptText(plain) {
|
|
1040
|
-
const writer = new
|
|
1041
|
-
writer.
|
|
947
|
+
const writer = new Writer(4 + 1 + new TextEncoder().encode(plain.message).length);
|
|
948
|
+
writer.date32(plain.timestamp);
|
|
1042
949
|
const flags = (plain.textType & 63) << 2 | plain.attempt & 3;
|
|
1043
|
-
writer.
|
|
1044
|
-
writer.
|
|
950
|
+
writer.uint8(flags);
|
|
951
|
+
writer.utf8String(plain.message);
|
|
1045
952
|
const data = writer.toBytes();
|
|
1046
953
|
return this.secret.encrypt(data);
|
|
1047
954
|
}
|
|
@@ -1050,16 +957,16 @@ var Group = class {
|
|
|
1050
957
|
if (data.length < 4) {
|
|
1051
958
|
throw new Error("Invalid ciphertext");
|
|
1052
959
|
}
|
|
1053
|
-
const reader = new
|
|
960
|
+
const reader = new Reader2(data);
|
|
1054
961
|
return {
|
|
1055
|
-
timestamp: reader.
|
|
1056
|
-
data: reader.
|
|
962
|
+
timestamp: reader.date32(),
|
|
963
|
+
data: reader.bytes()
|
|
1057
964
|
};
|
|
1058
965
|
}
|
|
1059
966
|
encryptData(plain) {
|
|
1060
|
-
const writer = new
|
|
1061
|
-
writer.
|
|
1062
|
-
writer.
|
|
967
|
+
const writer = new Writer(4 + plain.data.length);
|
|
968
|
+
writer.date32(plain.timestamp);
|
|
969
|
+
writer.bytes(plain.data);
|
|
1063
970
|
const data = writer.toBytes();
|
|
1064
971
|
return this.secret.encrypt(data);
|
|
1065
972
|
}
|
|
@@ -1069,6 +976,8 @@ var Contacts = class {
|
|
|
1069
976
|
this.localIdentities = [];
|
|
1070
977
|
this.contacts = {};
|
|
1071
978
|
this.groups = {};
|
|
979
|
+
this.addGroup(new Group("Public"));
|
|
980
|
+
this.addGroup(new Group("#test"));
|
|
1072
981
|
}
|
|
1073
982
|
addLocalIdentity(identity) {
|
|
1074
983
|
this.localIdentities.push({ identity, sharedSecrets: {} });
|
|
@@ -1080,6 +989,23 @@ var Contacts = class {
|
|
|
1080
989
|
}
|
|
1081
990
|
this.contacts[hash].push(contact);
|
|
1082
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
|
+
}
|
|
1083
1009
|
decrypt(src, dst, hmac2, ciphertext) {
|
|
1084
1010
|
let contacts = [];
|
|
1085
1011
|
if (src instanceof PublicKey) {
|
|
@@ -1127,17 +1053,45 @@ var Contacts = class {
|
|
|
1127
1053
|
return sharedSecret;
|
|
1128
1054
|
}
|
|
1129
1055
|
addGroup(group) {
|
|
1130
|
-
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();
|
|
1131
1064
|
if (!this.groups[hash]) {
|
|
1132
1065
|
this.groups[hash] = [];
|
|
1133
1066
|
}
|
|
1134
1067
|
this.groups[hash].push(group);
|
|
1135
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
|
+
}
|
|
1136
1090
|
decryptGroupText(channelHash, hmac2, ciphertext) {
|
|
1137
1091
|
const hash = parseNodeHash(channelHash);
|
|
1138
1092
|
const groups = this.groups[hash] || [];
|
|
1139
1093
|
if (groups.length === 0) {
|
|
1140
|
-
throw new Error(
|
|
1094
|
+
throw new Error(`Unknown group hash ${hash.toString(16).padStart(2, "0")}`);
|
|
1141
1095
|
}
|
|
1142
1096
|
for (const group of groups) {
|
|
1143
1097
|
try {
|
|
@@ -1146,13 +1100,13 @@ var Contacts = class {
|
|
|
1146
1100
|
} catch {
|
|
1147
1101
|
}
|
|
1148
1102
|
}
|
|
1149
|
-
throw new Error(
|
|
1103
|
+
throw new Error(`Decryption failed with all known groups with hash ${hash.toString(16).padStart(2, "0")}`);
|
|
1150
1104
|
}
|
|
1151
1105
|
decryptGroupData(channelHash, hmac2, ciphertext) {
|
|
1152
1106
|
const hash = parseNodeHash(channelHash);
|
|
1153
1107
|
const groups = this.groups[hash] || [];
|
|
1154
1108
|
if (groups.length === 0) {
|
|
1155
|
-
throw new Error(
|
|
1109
|
+
throw new Error(`Unknown group hash ${hash.toString(16).padStart(2, "0")}`);
|
|
1156
1110
|
}
|
|
1157
1111
|
for (const group of groups) {
|
|
1158
1112
|
try {
|
|
@@ -1161,11 +1115,10 @@ var Contacts = class {
|
|
|
1161
1115
|
} catch {
|
|
1162
1116
|
}
|
|
1163
1117
|
}
|
|
1164
|
-
throw new Error(
|
|
1118
|
+
throw new Error(`Decryption failed with all known groups with hash ${hash.toString(16).padStart(2, "0")}`);
|
|
1165
1119
|
}
|
|
1166
1120
|
};
|
|
1167
|
-
|
|
1168
|
-
0 && (module.exports = {
|
|
1121
|
+
export {
|
|
1169
1122
|
Contact,
|
|
1170
1123
|
Contacts,
|
|
1171
1124
|
Group,
|
|
@@ -1182,4 +1135,4 @@ var Contacts = class {
|
|
|
1182
1135
|
StaticSecret,
|
|
1183
1136
|
TextType,
|
|
1184
1137
|
parseNodeHash
|
|
1185
|
-
}
|
|
1138
|
+
};
|