space-data-module-sdk 0.8.5 → 0.8.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "space-data-module-sdk",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "description": "Module SDK for building, validating, signing, and deploying WebAssembly modules on the Space Data Network.",
5
5
  "type": "module",
6
6
  "types": "./src/index.d.ts",
package/src/index.d.ts CHANGED
@@ -855,9 +855,16 @@ export function protectMarketplaceContent(options: {
855
855
  wrapNonce?: Uint8Array | ArrayBuffer | ArrayBufferView | number[] | null;
856
856
  }): Promise<MarketplaceProtectedContent>;
857
857
 
858
+ export type KeyAgreementProvider = (params: {
859
+ ephemeralPublicKey: Uint8Array;
860
+ context: string;
861
+ keyExchange: string;
862
+ }) => Promise<Uint8Array>;
863
+
858
864
  export function decryptMarketplaceContentKeyWrap(options: {
859
865
  wrap: MarketplaceContentKeyWrap;
860
- recipientPrivateKey: Uint8Array | ArrayBuffer | ArrayBufferView | number[] | string;
866
+ recipientPrivateKey?: Uint8Array | ArrayBuffer | ArrayBufferView | number[] | string;
867
+ keyAgreement?: KeyAgreementProvider;
861
868
  }): Promise<Uint8Array>;
862
869
 
863
870
  export function encryptBytesForRecipient(options: {
@@ -872,7 +879,8 @@ export function encryptBytesForRecipient(options: {
872
879
 
873
880
  export function decryptProtectedBytes(options: {
874
881
  protectedBytes: Uint8Array | ArrayBuffer;
875
- recipientPrivateKey: Uint8Array | string;
882
+ recipientPrivateKey?: Uint8Array | string;
883
+ keyAgreement?: KeyAgreementProvider;
876
884
  }): Promise<Uint8Array>;
877
885
 
878
886
  export function decryptBytesFromEnvelope(options: {
@@ -80,6 +80,45 @@ async function deriveAesKey(sharedSecret, salt, context) {
80
80
  );
81
81
  }
82
82
 
83
+ // SAW-M3: the recipient's raw private scalar never has to enter this module.
84
+ // A caller (e.g. a browser session backed by hd-wallet-wasm) may supply a
85
+ // `keyAgreement` provider that performs step 1 (the X25519 ECDH, the ONLY
86
+ // step that needs the scalar) behind its own custody boundary and resolves
87
+ // the 32-byte shared secret. Steps 2-3 (HKDF derive, AES-GCM) stay here —
88
+ // public math, curve-agnostic, not duplicated into the wallet.
89
+ function keyExchangeFromAlgorithm(algorithm) {
90
+ if (typeof algorithm !== "string" || !algorithm.length) {
91
+ return "X25519";
92
+ }
93
+ return algorithm.split("-")[0] || "X25519";
94
+ }
95
+
96
+ async function resolveSharedSecret(
97
+ label,
98
+ { recipientPrivateKey, keyAgreement, ephemeralPublicKey, context, keyExchange },
99
+ ) {
100
+ if (recipientPrivateKey && keyAgreement) {
101
+ throw new Error(
102
+ `${label} accepts either recipientPrivateKey or keyAgreement, not both.`,
103
+ );
104
+ }
105
+ if (keyAgreement) {
106
+ const sharedSecret = toUint8Array(
107
+ await keyAgreement({ ephemeralPublicKey, context, keyExchange }),
108
+ );
109
+ if (sharedSecret.length !== 32) {
110
+ throw new Error(
111
+ `${label} keyAgreement provider must resolve a 32-byte shared secret.`,
112
+ );
113
+ }
114
+ return sharedSecret;
115
+ }
116
+ if (!recipientPrivateKey) {
117
+ throw new Error(`${label} requires recipientPrivateKey or keyAgreement.`);
118
+ }
119
+ return deriveSharedSecret(recipientPrivateKey, ephemeralPublicKey);
120
+ }
121
+
83
122
  function normalizeOptionalBytes(value) {
84
123
  if (value === null || value === undefined) {
85
124
  return new Uint8Array(0);
@@ -308,15 +347,22 @@ export async function protectMarketplaceContent({
308
347
  export async function decryptMarketplaceContentKeyWrap({
309
348
  wrap,
310
349
  recipientPrivateKey,
350
+ keyAgreement,
311
351
  } = {}) {
312
352
  if (!wrap) {
313
353
  throw new Error("decryptMarketplaceContentKeyWrap requires wrap.");
314
354
  }
315
- const sharedSecret = await deriveSharedSecret(
316
- recipientPrivateKey,
317
- wrap.providerEphemeralPublicKey,
318
- );
319
355
  const wrapContext = `marketplace-content-key:${wrap.providerId}:${wrap.contentKeyId}:${wrap.recipientKeyId}`;
356
+ const sharedSecret = await resolveSharedSecret(
357
+ "decryptMarketplaceContentKeyWrap",
358
+ {
359
+ recipientPrivateKey,
360
+ keyAgreement,
361
+ ephemeralPublicKey: wrap.providerEphemeralPublicKey,
362
+ context: wrapContext,
363
+ keyExchange: keyExchangeFromAlgorithm(wrap.algorithm),
364
+ },
365
+ );
320
366
  const wrapKey = await deriveAesKey(sharedSecret, new Uint8Array(0), wrapContext);
321
367
  const keyMaterial = await aesGcmDecrypt(
322
368
  wrapKey,
@@ -364,16 +410,20 @@ async function encryptBytesLegacy({
364
410
  export async function decryptProtectedBytes({
365
411
  protectedBytes,
366
412
  recipientPrivateKey,
413
+ keyAgreement,
367
414
  } = {}) {
368
415
  const parsed = extractPublicationRecordCollection(protectedBytes);
369
416
  if (!parsed?.enc) {
370
417
  return toUint8Array(protectedBytes);
371
418
  }
372
419
  assertGcmEncRecord(parsed.enc);
373
- const sharedSecret = await deriveSharedSecret(
420
+ const sharedSecret = await resolveSharedSecret("decryptProtectedBytes", {
374
421
  recipientPrivateKey,
375
- parsed.enc.ephemeralPublicKey,
376
- );
422
+ keyAgreement,
423
+ ephemeralPublicKey: parsed.enc.ephemeralPublicKey,
424
+ context: parsed.enc.context ?? "",
425
+ keyExchange: parsed.enc.keyExchange,
426
+ });
377
427
  const aesKey = await deriveAesKey(
378
428
  sharedSecret,
379
429
  new Uint8Array(0),