space-data-module-sdk 0.3.1 → 0.5.3
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/README.md +17 -9
- package/package.json +12 -4
- package/src/browser.js +8 -0
- package/src/bundle/constants.js +5 -0
- package/src/bundle/wasm.js +21 -4
- package/src/capabilities.js +46 -0
- package/src/compiler/compileModule.js +346 -17
- package/src/compliance/index.js +4 -2
- package/src/compliance/pluginCompliance.js +4 -47
- package/src/index.d.ts +174 -6
- package/src/index.js +1 -0
- package/src/manifest/browser.js +15 -0
- package/src/transport/index.js +19 -0
- package/src/transport/pki.js +125 -4
- package/src/transport/records.js +493 -0
- package/src/utils/wasmCrypto.js +43 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import * as flatbuffers from "flatbuffers";
|
|
2
|
+
|
|
3
|
+
import { ENC, ENCT, KDF, KeyExchange, SymmetricAlgo } from "spacedatastandards.org/lib/js/ENC/main.js";
|
|
4
|
+
import { PNM, PNMT } from "spacedatastandards.org/lib/js/PNM/main.js";
|
|
5
|
+
import { REC, RECT } from "spacedatastandards.org/lib/js/REC/REC.js";
|
|
6
|
+
import { RecordT } from "spacedatastandards.org/lib/js/REC/Record.js";
|
|
7
|
+
import { RecordType } from "spacedatastandards.org/lib/js/REC/RecordType.js";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
base64ToBytes,
|
|
11
|
+
bytesToBase64,
|
|
12
|
+
bytesToHex,
|
|
13
|
+
toUint8Array,
|
|
14
|
+
} from "../utils/encoding.js";
|
|
15
|
+
import { sha256Bytes } from "../utils/wasmCrypto.js";
|
|
16
|
+
|
|
17
|
+
const TRAILER_MAGIC_TEXT = "$REC";
|
|
18
|
+
const TRAILER_MAGIC_BYTES = new TextEncoder().encode(TRAILER_MAGIC_TEXT);
|
|
19
|
+
const TRAILER_FOOTER_LENGTH = 8;
|
|
20
|
+
const DEFAULT_RECORD_COLLECTION_VERSION = "1.0.0";
|
|
21
|
+
const KEY_EXCHANGE_BY_NAME = Object.freeze({
|
|
22
|
+
X25519: KeyExchange.X25519,
|
|
23
|
+
SECP256K1: KeyExchange.Secp256k1,
|
|
24
|
+
P256: KeyExchange.P256,
|
|
25
|
+
});
|
|
26
|
+
const KEY_EXCHANGE_NAME_BY_VALUE = Object.freeze(
|
|
27
|
+
Object.fromEntries(
|
|
28
|
+
Object.entries(KEY_EXCHANGE_BY_NAME).map(([name, value]) => [value, name]),
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
const SYMMETRIC_ALGO_BY_NAME = Object.freeze({
|
|
32
|
+
AES_256_CTR: SymmetricAlgo.AES_256_CTR,
|
|
33
|
+
});
|
|
34
|
+
const SYMMETRIC_ALGO_NAME_BY_VALUE = Object.freeze(
|
|
35
|
+
Object.fromEntries(
|
|
36
|
+
Object.entries(SYMMETRIC_ALGO_BY_NAME).map(([name, value]) => [value, name]),
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
const KDF_BY_NAME = Object.freeze({
|
|
40
|
+
HKDF_SHA256: KDF.HKDF_SHA256,
|
|
41
|
+
});
|
|
42
|
+
const KDF_NAME_BY_VALUE = Object.freeze(
|
|
43
|
+
Object.fromEntries(
|
|
44
|
+
Object.entries(KDF_BY_NAME).map(([name, value]) => [value, name]),
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
const RECORD_TYPE_BY_STANDARD = Object.freeze({
|
|
48
|
+
ENC: RecordType.ENC,
|
|
49
|
+
PNM: RecordType.PNM,
|
|
50
|
+
});
|
|
51
|
+
const STANDARD_BY_RECORD_TYPE = Object.freeze(
|
|
52
|
+
Object.fromEntries(
|
|
53
|
+
Object.entries(RECORD_TYPE_BY_STANDARD).map(([standard, value]) => [
|
|
54
|
+
value,
|
|
55
|
+
standard,
|
|
56
|
+
]),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
const textEncoder = new TextEncoder();
|
|
60
|
+
|
|
61
|
+
function concatBytes(chunks) {
|
|
62
|
+
const totalLength = chunks.reduce((total, chunk) => total + chunk.length, 0);
|
|
63
|
+
const out = new Uint8Array(totalLength);
|
|
64
|
+
let offset = 0;
|
|
65
|
+
for (const chunk of chunks) {
|
|
66
|
+
out.set(chunk, offset);
|
|
67
|
+
offset += chunk.length;
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeByteField(value) {
|
|
73
|
+
if (value === undefined || value === null) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return toUint8Array(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeStringField(value) {
|
|
80
|
+
if (value === undefined || value === null) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const normalized = String(value).trim();
|
|
84
|
+
return normalized.length > 0 ? normalized : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeKeyExchange(value) {
|
|
88
|
+
if (typeof value === "number") {
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
return KEY_EXCHANGE_BY_NAME[
|
|
92
|
+
String(value ?? "X25519")
|
|
93
|
+
.trim()
|
|
94
|
+
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
95
|
+
.toUpperCase()
|
|
96
|
+
] ?? KeyExchange.X25519;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function normalizeSymmetricAlgorithm(value) {
|
|
100
|
+
if (typeof value === "number") {
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
return SYMMETRIC_ALGO_BY_NAME[
|
|
104
|
+
String(value ?? "AES_256_CTR")
|
|
105
|
+
.trim()
|
|
106
|
+
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
107
|
+
.toUpperCase()
|
|
108
|
+
] ?? SymmetricAlgo.AES_256_CTR;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function normalizeKdf(value) {
|
|
112
|
+
if (typeof value === "number") {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
return KDF_BY_NAME[
|
|
116
|
+
String(value ?? "HKDF_SHA256")
|
|
117
|
+
.trim()
|
|
118
|
+
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
119
|
+
.toUpperCase()
|
|
120
|
+
] ?? KDF.HKDF_SHA256;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function encTableFromObject(record = {}) {
|
|
124
|
+
return new ENCT(
|
|
125
|
+
Number(record.version ?? 1),
|
|
126
|
+
normalizeKeyExchange(record.keyExchange),
|
|
127
|
+
normalizeSymmetricAlgorithm(record.symmetric),
|
|
128
|
+
normalizeKdf(record.keyDerivation),
|
|
129
|
+
Array.from(toUint8Array(record.ephemeralPublicKey)),
|
|
130
|
+
Array.from(toUint8Array(record.nonceStart)),
|
|
131
|
+
Array.from(normalizeByteField(record.recipientKeyId) ?? []),
|
|
132
|
+
normalizeStringField(record.context),
|
|
133
|
+
Array.from(normalizeByteField(record.schemaHash) ?? []),
|
|
134
|
+
normalizeStringField(record.rootType),
|
|
135
|
+
BigInt(record.timestamp ?? 0),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function pnmTableFromObject(record = {}) {
|
|
140
|
+
return new PNMT(
|
|
141
|
+
normalizeStringField(record.multiformatAddress),
|
|
142
|
+
normalizeStringField(record.publishTimestamp),
|
|
143
|
+
normalizeStringField(record.cid),
|
|
144
|
+
normalizeStringField(record.fileName),
|
|
145
|
+
normalizeStringField(record.fileId),
|
|
146
|
+
normalizeStringField(record.signature),
|
|
147
|
+
normalizeStringField(record.timestampSignature),
|
|
148
|
+
normalizeStringField(record.signatureType),
|
|
149
|
+
normalizeStringField(record.timestampSignatureType),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function normalizeEncTable(table) {
|
|
154
|
+
if (!table) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
version: Number(table.VERSION ?? 1),
|
|
159
|
+
keyExchange:
|
|
160
|
+
KEY_EXCHANGE_NAME_BY_VALUE[table.KEY_EXCHANGE] ?? String(table.KEY_EXCHANGE),
|
|
161
|
+
symmetric:
|
|
162
|
+
SYMMETRIC_ALGO_NAME_BY_VALUE[table.SYMMETRIC] ?? String(table.SYMMETRIC),
|
|
163
|
+
keyDerivation:
|
|
164
|
+
KDF_NAME_BY_VALUE[table.KEY_DERIVATION] ?? String(table.KEY_DERIVATION),
|
|
165
|
+
ephemeralPublicKey: normalizeByteField(table.EPHEMERAL_PUBLIC_KEY),
|
|
166
|
+
nonceStart: normalizeByteField(table.NONCE_START),
|
|
167
|
+
recipientKeyId: normalizeByteField(table.RECIPIENT_KEY_ID),
|
|
168
|
+
context: normalizeStringField(table.CONTEXT),
|
|
169
|
+
schemaHash: normalizeByteField(table.SCHEMA_HASH),
|
|
170
|
+
rootType: normalizeStringField(table.ROOT_TYPE),
|
|
171
|
+
timestamp:
|
|
172
|
+
table.TIMESTAMP === undefined || table.TIMESTAMP === null
|
|
173
|
+
? 0
|
|
174
|
+
: Number(table.TIMESTAMP),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function normalizePnmTable(table) {
|
|
179
|
+
if (!table) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
multiformatAddress: normalizeStringField(table.MULTIFORMAT_ADDRESS),
|
|
184
|
+
publishTimestamp: normalizeStringField(table.PUBLISH_TIMESTAMP),
|
|
185
|
+
cid: normalizeStringField(table.CID),
|
|
186
|
+
fileName: normalizeStringField(table.FILE_NAME),
|
|
187
|
+
fileId: normalizeStringField(table.FILE_ID),
|
|
188
|
+
signature: normalizeStringField(table.SIGNATURE),
|
|
189
|
+
timestampSignature: normalizeStringField(table.TIMESTAMP_SIGNATURE),
|
|
190
|
+
signatureType: normalizeStringField(table.SIGNATURE_TYPE),
|
|
191
|
+
timestampSignatureType: normalizeStringField(table.TIMESTAMP_SIGNATURE_TYPE),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function readFooterLength(bytes) {
|
|
196
|
+
const view = toUint8Array(bytes);
|
|
197
|
+
if (view.length < TRAILER_FOOTER_LENGTH) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const footerOffset = view.length - TRAILER_FOOTER_LENGTH;
|
|
201
|
+
for (let index = 0; index < TRAILER_MAGIC_BYTES.length; index += 1) {
|
|
202
|
+
if (view[footerOffset + 4 + index] !== TRAILER_MAGIC_BYTES[index]) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return new DataView(
|
|
207
|
+
view.buffer,
|
|
208
|
+
view.byteOffset + footerOffset,
|
|
209
|
+
TRAILER_FOOTER_LENGTH,
|
|
210
|
+
).getUint32(0, true);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function encodeFooter(recordCollectionLength) {
|
|
214
|
+
if (
|
|
215
|
+
!Number.isSafeInteger(recordCollectionLength) ||
|
|
216
|
+
recordCollectionLength < 0 ||
|
|
217
|
+
recordCollectionLength > 0xffff_ffff
|
|
218
|
+
) {
|
|
219
|
+
throw new RangeError("REC trailer length must fit in uint32.");
|
|
220
|
+
}
|
|
221
|
+
const footer = new Uint8Array(TRAILER_FOOTER_LENGTH);
|
|
222
|
+
const view = new DataView(footer.buffer);
|
|
223
|
+
view.setUint32(0, recordCollectionLength, true);
|
|
224
|
+
footer.set(TRAILER_MAGIC_BYTES, 4);
|
|
225
|
+
return footer;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function toBase32Lower(bytes) {
|
|
229
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz234567";
|
|
230
|
+
let bits = 0;
|
|
231
|
+
let value = 0;
|
|
232
|
+
let out = "";
|
|
233
|
+
for (const byte of toUint8Array(bytes)) {
|
|
234
|
+
value = (value << 8) | byte;
|
|
235
|
+
bits += 8;
|
|
236
|
+
while (bits >= 5) {
|
|
237
|
+
out += alphabet[(value >>> (bits - 5)) & 31];
|
|
238
|
+
bits -= 5;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (bits > 0) {
|
|
242
|
+
out += alphabet[(value << (5 - bits)) & 31];
|
|
243
|
+
}
|
|
244
|
+
return out;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export async function createCidV1Raw(payloadBytes) {
|
|
248
|
+
const digest = await sha256Bytes(payloadBytes);
|
|
249
|
+
const cidBytes = concatBytes([
|
|
250
|
+
Uint8Array.of(0x01), // cidv1
|
|
251
|
+
Uint8Array.of(0x55), // raw
|
|
252
|
+
Uint8Array.of(0x12, digest.length), // sha2-256 multihash
|
|
253
|
+
digest,
|
|
254
|
+
]);
|
|
255
|
+
return `b${toBase32Lower(cidBytes)}`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function encodeEncRecord(record = {}) {
|
|
259
|
+
const builder = new flatbuffers.Builder(256);
|
|
260
|
+
const table = encTableFromObject(record);
|
|
261
|
+
const root = table.pack(builder);
|
|
262
|
+
ENC.finishENCBuffer(builder, root);
|
|
263
|
+
return builder.asUint8Array();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function decodeEncRecord(bytes) {
|
|
267
|
+
const bb = new flatbuffers.ByteBuffer(toUint8Array(bytes));
|
|
268
|
+
if (!ENC.bufferHasIdentifier(bb)) {
|
|
269
|
+
throw new Error("ENC record is missing the $ENC file identifier.");
|
|
270
|
+
}
|
|
271
|
+
const record = ENC.getRootAsENC(bb).unpack();
|
|
272
|
+
return normalizeEncTable(record);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function encodePnmRecord(record = {}) {
|
|
276
|
+
const builder = new flatbuffers.Builder(256);
|
|
277
|
+
const table = pnmTableFromObject(record);
|
|
278
|
+
const root = table.pack(builder);
|
|
279
|
+
PNM.finishPNMBuffer(builder, root);
|
|
280
|
+
return builder.asUint8Array();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function decodePnmRecord(bytes) {
|
|
284
|
+
const bb = new flatbuffers.ByteBuffer(toUint8Array(bytes));
|
|
285
|
+
if (!PNM.bufferHasIdentifier(bb)) {
|
|
286
|
+
throw new Error("PNM record is missing the $PNM file identifier.");
|
|
287
|
+
}
|
|
288
|
+
const record = PNM.getRootAsPNM(bb).unpack();
|
|
289
|
+
return normalizePnmTable(record);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function encodePublicationRecordCollection(options = {}) {
|
|
293
|
+
const records = [];
|
|
294
|
+
if (options.enc) {
|
|
295
|
+
records.push(new RecordT(RECORD_TYPE_BY_STANDARD.ENC, encTableFromObject(options.enc), "ENC"));
|
|
296
|
+
}
|
|
297
|
+
if (options.pnm) {
|
|
298
|
+
records.push(new RecordT(RECORD_TYPE_BY_STANDARD.PNM, pnmTableFromObject(options.pnm), "PNM"));
|
|
299
|
+
}
|
|
300
|
+
if (records.length === 0) {
|
|
301
|
+
throw new Error("At least one ENC or PNM record is required.");
|
|
302
|
+
}
|
|
303
|
+
const builder = new flatbuffers.Builder(512);
|
|
304
|
+
const root = new RECT(
|
|
305
|
+
normalizeStringField(options.version) ?? DEFAULT_RECORD_COLLECTION_VERSION,
|
|
306
|
+
records,
|
|
307
|
+
).pack(builder);
|
|
308
|
+
REC.finishRECBuffer(builder, root);
|
|
309
|
+
return builder.asUint8Array();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function decodePublicationRecordCollection(bytes) {
|
|
313
|
+
const buffer = toUint8Array(bytes);
|
|
314
|
+
const bb = new flatbuffers.ByteBuffer(buffer);
|
|
315
|
+
if (!REC.bufferHasIdentifier(bb)) {
|
|
316
|
+
throw new Error("REC trailer is missing the $REC file identifier.");
|
|
317
|
+
}
|
|
318
|
+
const collection = REC.getRootAsREC(bb).unpack();
|
|
319
|
+
const records = [];
|
|
320
|
+
let enc = null;
|
|
321
|
+
let pnm = null;
|
|
322
|
+
for (const unpackedRecord of Array.isArray(collection.RECORDS) ? collection.RECORDS : []) {
|
|
323
|
+
const standard =
|
|
324
|
+
normalizeStringField(unpackedRecord.standard) ??
|
|
325
|
+
STANDARD_BY_RECORD_TYPE[unpackedRecord.value_type] ??
|
|
326
|
+
null;
|
|
327
|
+
if (standard === "ENC") {
|
|
328
|
+
enc = normalizeEncTable(unpackedRecord.value);
|
|
329
|
+
} else if (standard === "PNM") {
|
|
330
|
+
pnm = normalizePnmTable(unpackedRecord.value);
|
|
331
|
+
}
|
|
332
|
+
records.push({
|
|
333
|
+
standard,
|
|
334
|
+
recordType: unpackedRecord.value_type,
|
|
335
|
+
value:
|
|
336
|
+
standard === "ENC"
|
|
337
|
+
? enc
|
|
338
|
+
: standard === "PNM"
|
|
339
|
+
? pnm
|
|
340
|
+
: unpackedRecord.value,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
version: normalizeStringField(collection.version) ?? DEFAULT_RECORD_COLLECTION_VERSION,
|
|
345
|
+
records,
|
|
346
|
+
enc,
|
|
347
|
+
pnm,
|
|
348
|
+
recordCollectionBytes: buffer,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function appendPublicationRecordCollection(
|
|
353
|
+
payloadBytes,
|
|
354
|
+
recordCollectionBytes,
|
|
355
|
+
) {
|
|
356
|
+
const payload = toUint8Array(payloadBytes);
|
|
357
|
+
const recordCollection = toUint8Array(recordCollectionBytes);
|
|
358
|
+
return concatBytes([
|
|
359
|
+
payload,
|
|
360
|
+
recordCollection,
|
|
361
|
+
encodeFooter(recordCollection.length),
|
|
362
|
+
]);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function stripPublicationRecordCollection(bytes) {
|
|
366
|
+
const parsed = extractPublicationRecordCollection(bytes);
|
|
367
|
+
return parsed?.payloadBytes ?? toUint8Array(bytes);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function extractPublicationRecordCollection(bytes) {
|
|
371
|
+
const buffer = toUint8Array(bytes);
|
|
372
|
+
const recordCollectionLength = readFooterLength(buffer);
|
|
373
|
+
if (recordCollectionLength === null) {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
const footerOffset = buffer.length - TRAILER_FOOTER_LENGTH;
|
|
377
|
+
const recordCollectionOffset = footerOffset - recordCollectionLength;
|
|
378
|
+
if (recordCollectionOffset < 0) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
const recordCollectionBytes = buffer.subarray(
|
|
382
|
+
recordCollectionOffset,
|
|
383
|
+
footerOffset,
|
|
384
|
+
);
|
|
385
|
+
try {
|
|
386
|
+
const decoded = decodePublicationRecordCollection(recordCollectionBytes);
|
|
387
|
+
return {
|
|
388
|
+
...decoded,
|
|
389
|
+
payloadBytes: buffer.subarray(0, recordCollectionOffset),
|
|
390
|
+
protectedBytes: buffer,
|
|
391
|
+
footerBytes: buffer.subarray(footerOffset),
|
|
392
|
+
footerMagic: TRAILER_MAGIC_TEXT,
|
|
393
|
+
recordCollectionLength,
|
|
394
|
+
};
|
|
395
|
+
} catch {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export async function createPublicationNotice(options = {}) {
|
|
401
|
+
const payloadBytes = toUint8Array(options.payloadBytes);
|
|
402
|
+
const cid = normalizeStringField(options.cid) ?? (await createCidV1Raw(payloadBytes));
|
|
403
|
+
const publishTimestamp =
|
|
404
|
+
normalizeStringField(options.publishTimestamp) ??
|
|
405
|
+
new Date(
|
|
406
|
+
Number.isFinite(options.publishTimestampMs)
|
|
407
|
+
? options.publishTimestampMs
|
|
408
|
+
: Date.now(),
|
|
409
|
+
).toISOString();
|
|
410
|
+
const fileName =
|
|
411
|
+
normalizeStringField(options.fileName) ??
|
|
412
|
+
normalizeStringField(options.artifactId) ??
|
|
413
|
+
"module.wasm";
|
|
414
|
+
const fileId =
|
|
415
|
+
normalizeStringField(options.fileId) ??
|
|
416
|
+
normalizeStringField(options.programId) ??
|
|
417
|
+
normalizeStringField(options.artifactId) ??
|
|
418
|
+
"module";
|
|
419
|
+
const multiformatAddress =
|
|
420
|
+
normalizeStringField(options.multiformatAddress) ?? `/ipfs/${cid}`;
|
|
421
|
+
|
|
422
|
+
let signature = normalizeStringField(options.signature);
|
|
423
|
+
let timestampSignature = normalizeStringField(options.timestampSignature);
|
|
424
|
+
let signatureType = normalizeStringField(options.signatureType);
|
|
425
|
+
let timestampSignatureType = normalizeStringField(options.timestampSignatureType);
|
|
426
|
+
if (options.signer && typeof options.signer.sign === "function") {
|
|
427
|
+
signature = bytesToHex(await options.signer.sign(textEncoder.encode(cid)));
|
|
428
|
+
timestampSignature = bytesToHex(
|
|
429
|
+
await options.signer.sign(textEncoder.encode(publishTimestamp)),
|
|
430
|
+
);
|
|
431
|
+
signatureType =
|
|
432
|
+
signatureType ??
|
|
433
|
+
normalizeStringField(options.signer.algorithm) ??
|
|
434
|
+
"unknown";
|
|
435
|
+
timestampSignatureType =
|
|
436
|
+
timestampSignatureType ??
|
|
437
|
+
normalizeStringField(options.signer.algorithm) ??
|
|
438
|
+
"unknown";
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
multiformatAddress,
|
|
443
|
+
publishTimestamp,
|
|
444
|
+
cid,
|
|
445
|
+
fileName,
|
|
446
|
+
fileId,
|
|
447
|
+
signature,
|
|
448
|
+
timestampSignature,
|
|
449
|
+
signatureType,
|
|
450
|
+
timestampSignatureType,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function createEncryptedEnvelopePayload(options = {}) {
|
|
455
|
+
const protectedBlob = toUint8Array(options.protectedBlobBytes);
|
|
456
|
+
const parsed =
|
|
457
|
+
options.parsedProtectedBlob ?? extractPublicationRecordCollection(protectedBlob);
|
|
458
|
+
const enc = options.enc ?? parsed?.enc ?? null;
|
|
459
|
+
const envelope = {
|
|
460
|
+
version: Number(options.version ?? 2),
|
|
461
|
+
scheme:
|
|
462
|
+
normalizeStringField(options.scheme) ?? "x25519-hkdf-aes-256-ctr-rec",
|
|
463
|
+
context: normalizeStringField(options.context ?? enc?.context) ?? "",
|
|
464
|
+
protectedBlobBase64: bytesToBase64(protectedBlob),
|
|
465
|
+
recordCollectionBase64: parsed
|
|
466
|
+
? bytesToBase64(parsed.recordCollectionBytes)
|
|
467
|
+
: null,
|
|
468
|
+
ciphertextBase64: parsed ? bytesToBase64(parsed.payloadBytes) : null,
|
|
469
|
+
};
|
|
470
|
+
if (enc?.ephemeralPublicKey) {
|
|
471
|
+
envelope.senderPublicKeyBase64 = bytesToBase64(enc.ephemeralPublicKey);
|
|
472
|
+
}
|
|
473
|
+
if (enc?.nonceStart) {
|
|
474
|
+
envelope.nonceStartBase64 = bytesToBase64(enc.nonceStart);
|
|
475
|
+
}
|
|
476
|
+
if (enc?.recipientKeyId) {
|
|
477
|
+
envelope.recipientKeyIdBase64 = bytesToBase64(enc.recipientKeyId);
|
|
478
|
+
}
|
|
479
|
+
if (enc) {
|
|
480
|
+
envelope.encRecordBase64 = bytesToBase64(encodeEncRecord(enc));
|
|
481
|
+
}
|
|
482
|
+
if (parsed?.pnm) {
|
|
483
|
+
envelope.pnmRecordBase64 = bytesToBase64(encodePnmRecord(parsed.pnm));
|
|
484
|
+
}
|
|
485
|
+
return envelope;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export function decodeProtectedBlobBase64(base64) {
|
|
489
|
+
const bytes = base64ToBytes(base64);
|
|
490
|
+
return extractPublicationRecordCollection(bytes);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export { TRAILER_MAGIC_TEXT, TRAILER_FOOTER_LENGTH };
|
package/src/utils/wasmCrypto.js
CHANGED
|
@@ -116,3 +116,46 @@ export async function aesGcmDecrypt(key, ciphertext, tag, iv, aad = null) {
|
|
|
116
116
|
aad ? toUint8Array(aad) : undefined,
|
|
117
117
|
);
|
|
118
118
|
}
|
|
119
|
+
|
|
120
|
+
function normalizeCtrIv(nonceStart) {
|
|
121
|
+
const nonce = toUint8Array(nonceStart);
|
|
122
|
+
if (nonce.length !== 12) {
|
|
123
|
+
throw new Error("AES-256-CTR expects a 12-byte NONCE_START value.");
|
|
124
|
+
}
|
|
125
|
+
const iv = new Uint8Array(16);
|
|
126
|
+
iv.set(nonce, 0);
|
|
127
|
+
return iv;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function getAesCtrApi() {
|
|
131
|
+
const wallet = await getWasmWallet();
|
|
132
|
+
const aesCtr = wallet?.aesCtr;
|
|
133
|
+
if (
|
|
134
|
+
!aesCtr ||
|
|
135
|
+
typeof aesCtr.encrypt !== "function" ||
|
|
136
|
+
typeof aesCtr.decrypt !== "function"
|
|
137
|
+
) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
"hd-wallet-wasm aesCtr API is unavailable; cannot process ENC payloads.",
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return aesCtr;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function aesCtrEncrypt(key, plaintext, nonceStart) {
|
|
146
|
+
const aesCtr = await getAesCtrApi();
|
|
147
|
+
return aesCtr.encrypt(
|
|
148
|
+
toUint8Array(key),
|
|
149
|
+
toUint8Array(plaintext),
|
|
150
|
+
normalizeCtrIv(nonceStart),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function aesCtrDecrypt(key, ciphertext, nonceStart) {
|
|
155
|
+
const aesCtr = await getAesCtrApi();
|
|
156
|
+
return aesCtr.decrypt(
|
|
157
|
+
toUint8Array(key),
|
|
158
|
+
toUint8Array(ciphertext),
|
|
159
|
+
normalizeCtrIv(nonceStart),
|
|
160
|
+
);
|
|
161
|
+
}
|