hd-wallet-wasm 2.0.2 → 2.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hd-wallet-wasm",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Comprehensive HD Wallet implementation in WebAssembly - BIP-32/39/44, multi-curve, multi-chain support",
5
5
  "type": "module",
6
6
  "main": "src/index.mjs",
@@ -30,8 +30,6 @@
30
30
  "src/epm-attestation.mjs",
31
31
  "src/aligned.mjs",
32
32
  "src/aligned.d.ts",
33
- "src/sdn-plugin.mjs",
34
- "src/sdn-plugin-manifest-source.mjs",
35
33
  "src/generated/",
36
34
  "dist/hd-wallet.js",
37
35
  "dist/hd-wallet.wasm",
@@ -44,7 +42,7 @@
44
42
  "generate:aligned": "node ../scripts/generate-aligned.mjs",
45
43
  "generate:sdn-plugin": "node ../scripts/generate-sdn-plugin-manifest.mjs",
46
44
  "build": "npm run generate:sdn-plugin && cmake --build ../build-wasm --target hd_wallet_wasm_npm -j",
47
- "test:artifact": "node test/test_bundle_browser_artifact.mjs && node test/test_package_contents.mjs",
45
+ "test:artifact": "node test/test_bundle_browser_artifact.mjs",
48
46
  "test:sdn-plugin": "node test/test_sdn_plugin_compliance.mjs",
49
47
  "test:all": "node test/test_all.mjs",
50
48
  "test": "npm run test:artifact && npm run test:all",
package/src/index.mjs CHANGED
@@ -1130,8 +1130,7 @@ try {
1130
1130
  /**
1131
1131
  * SECURITY FIX [HIGH-05]: WASM Module Integrity Verification
1132
1132
  *
1133
- * Verify the integrity of a WASM module before loading.
1134
- * This helps prevent supply chain attacks where the WASM binary is tampered with.
1133
+ * Verify the integrity of a WASM module using the package WASM SHA-256 helper.
1135
1134
  *
1136
1135
  * @param {ArrayBuffer|Uint8Array} wasmBytes - The WASM binary
1137
1136
  * @param {string} expectedHash - Expected SHA-256 hash in hex format
@@ -1146,10 +1145,7 @@ export async function verifyWasmIntegrity(wasmBytes, expectedHash) {
1146
1145
 
1147
1146
  const bytes = wasmBytes instanceof Uint8Array ? wasmBytes : new Uint8Array(wasmBytes);
1148
1147
 
1149
- // Use SubtleCrypto for hash computation
1150
- const hashBuffer = await crypto.subtle.digest('SHA-256', bytes);
1151
- const hashArray = Array.from(new Uint8Array(hashBuffer));
1152
- const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
1148
+ const hashHex = await computeWasmHash(bytes);
1153
1149
 
1154
1150
  if (hashHex.toLowerCase() !== expectedHash.toLowerCase()) {
1155
1151
  throw new Error(
@@ -1163,18 +1159,20 @@ export async function verifyWasmIntegrity(wasmBytes, expectedHash) {
1163
1159
  return true;
1164
1160
  }
1165
1161
 
1162
+ function bytesToHex(bytes) {
1163
+ return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
1164
+ }
1165
+
1166
1166
  /**
1167
- * Compute SHA-256 hash of WASM module
1168
- * Use this during build to get the hash for integrity verification.
1167
+ * Compute SHA-256 hash of bytes with the package WASM implementation.
1169
1168
  *
1170
1169
  * @param {ArrayBuffer|Uint8Array} wasmBytes - The WASM binary
1171
1170
  * @returns {Promise<string>} SHA-256 hash in hex format
1172
1171
  */
1173
1172
  export async function computeWasmHash(wasmBytes) {
1174
1173
  const bytes = wasmBytes instanceof Uint8Array ? wasmBytes : new Uint8Array(wasmBytes);
1175
- const hashBuffer = await crypto.subtle.digest('SHA-256', bytes);
1176
- const hashArray = Array.from(new Uint8Array(hashBuffer));
1177
- return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
1174
+ const wallet = await init();
1175
+ return bytesToHex(wallet.utils.sha256(bytes));
1178
1176
  }
1179
1177
 
1180
1178
  /**
@@ -1,307 +0,0 @@
1
- export const HD_WALLET_SDN_PLUGIN_ID =
2
- 'com.digitalarsenal.infrastructure.hd-wallet-wasm';
3
-
4
- export const HD_WALLET_SDN_PLUGIN_MANIFEST = Object.freeze({
5
- pluginId: HD_WALLET_SDN_PLUGIN_ID,
6
- name: 'HD Wallet Crypto',
7
- version: '2.0.1',
8
- pluginFamily: 'infrastructure',
9
- description:
10
- 'Native-crypto infrastructure plugin surface for detached signing and field-level encryption inside an sdn-flow runtime.',
11
- capabilities: [
12
- {
13
- capabilityId: 'random',
14
- required: true,
15
- description:
16
- 'Explicit host entropy for IVs, salts, and ephemeral encryption material.',
17
- },
18
- {
19
- capabilityId: 'wallet_sign',
20
- required: false,
21
- description:
22
- 'Optional host-resident key surface for signing when private key bytes are not carried in the request payload.',
23
- },
24
- ],
25
- externalInterfaces: [
26
- {
27
- interfaceId: 'wallet-active-key',
28
- kind: 'host-service',
29
- direction: 'bidirectional',
30
- capability: 'wallet_sign',
31
- resource: 'wallet://active-key',
32
- required: false,
33
- description:
34
- 'Optional host-provided signing and key-agreement surface for resident key material.',
35
- properties: {
36
- curves: ['secp256k1', 'ed25519', 'x25519'],
37
- residentKeyMaterial: true,
38
- },
39
- },
40
- {
41
- interfaceId: 'host-rng',
42
- kind: 'host-service',
43
- direction: 'input',
44
- capability: 'random',
45
- resource: 'host-rng://default',
46
- required: true,
47
- description:
48
- 'Explicit entropy source injected by the host for salts, IVs, and ephemeral sender keys.',
49
- properties: {
50
- provides: ['salt', 'iv', 'ephemeral-private-key'],
51
- },
52
- },
53
- ],
54
- methods: [
55
- {
56
- methodId: 'encrypt_fields',
57
- displayName: 'Encrypt Fields',
58
- inputPorts: [
59
- {
60
- portId: 'field_set',
61
- acceptedTypeSets: [
62
- {
63
- setId: 'field-selection-bundle',
64
- allowedTypes: [
65
- {
66
- schemaName: 'FieldSelectionBundle.fbs',
67
- fileIdentifier: 'FSLB',
68
- },
69
- ],
70
- description:
71
- 'Selected plaintext fields plus key agreement material.',
72
- },
73
- ],
74
- minStreams: 1,
75
- maxStreams: 1,
76
- required: true,
77
- description:
78
- 'Field bundle containing plaintext values to encrypt and recipient key material.',
79
- },
80
- ],
81
- outputPorts: [
82
- {
83
- portId: 'encrypted_fields',
84
- acceptedTypeSets: [
85
- {
86
- setId: 'encrypted-field-set',
87
- allowedTypes: [
88
- {
89
- schemaName: 'EncryptedFieldSet.fbs',
90
- fileIdentifier: 'EFLD',
91
- },
92
- ],
93
- description:
94
- 'Encrypted field envelopes emitted by hd-wallet-wasm.',
95
- },
96
- ],
97
- minStreams: 1,
98
- maxStreams: 1,
99
- required: true,
100
- description: 'Encrypted field envelopes with salts, IVs, and tags.',
101
- },
102
- ],
103
- maxBatch: 64,
104
- drainPolicy: 'drain-until-yield',
105
- description:
106
- 'Encrypts selected field payloads without leaving the plugin runtime contract.',
107
- },
108
- {
109
- methodId: 'decrypt_fields',
110
- displayName: 'Decrypt Fields',
111
- inputPorts: [
112
- {
113
- portId: 'encrypted_fields',
114
- acceptedTypeSets: [
115
- {
116
- setId: 'encrypted-field-set',
117
- allowedTypes: [
118
- {
119
- schemaName: 'EncryptedFieldSet.fbs',
120
- fileIdentifier: 'EFLD',
121
- },
122
- ],
123
- description:
124
- 'Encrypted field envelopes emitted by encrypt_fields.',
125
- },
126
- ],
127
- minStreams: 1,
128
- maxStreams: 1,
129
- required: true,
130
- description:
131
- 'Encrypted field envelopes plus recipient private key material.',
132
- },
133
- ],
134
- outputPorts: [
135
- {
136
- portId: 'field_set',
137
- acceptedTypeSets: [
138
- {
139
- setId: 'field-selection-bundle',
140
- allowedTypes: [
141
- {
142
- schemaName: 'FieldSelectionBundle.fbs',
143
- fileIdentifier: 'FSLB',
144
- },
145
- ],
146
- description:
147
- 'Recovered plaintext field bundle after authenticated decryption.',
148
- },
149
- ],
150
- minStreams: 1,
151
- maxStreams: 1,
152
- required: true,
153
- description: 'Recovered plaintext fields.',
154
- },
155
- ],
156
- maxBatch: 64,
157
- drainPolicy: 'drain-until-yield',
158
- description:
159
- 'Performs authenticated field decryption using the manifest-defined plugin surface.',
160
- },
161
- {
162
- methodId: 'sign_detached',
163
- displayName: 'Sign Detached',
164
- inputPorts: [
165
- {
166
- portId: 'message',
167
- acceptedTypeSets: [
168
- {
169
- setId: 'detached-signing-request',
170
- allowedTypes: [
171
- {
172
- schemaName: 'DetachedSigningRequest.fbs',
173
- fileIdentifier: 'SGRQ',
174
- },
175
- ],
176
- description:
177
- 'Message payload and signing material for detached signatures.',
178
- },
179
- ],
180
- minStreams: 1,
181
- maxStreams: 1,
182
- required: true,
183
- description: 'Signing request payload.',
184
- },
185
- ],
186
- outputPorts: [
187
- {
188
- portId: 'signature',
189
- acceptedTypeSets: [
190
- {
191
- setId: 'detached-signature',
192
- allowedTypes: [
193
- {
194
- schemaName: 'DetachedSignature.fbs',
195
- fileIdentifier: 'SIGD',
196
- },
197
- ],
198
- description: 'Detached signature envelope.',
199
- },
200
- ],
201
- minStreams: 1,
202
- maxStreams: 1,
203
- required: true,
204
- description: 'Detached signature plus digest and public key metadata.',
205
- },
206
- ],
207
- maxBatch: 64,
208
- drainPolicy: 'drain-until-yield',
209
- description:
210
- 'Signs payloads through hd-wallet-wasm primitives instead of ad hoc host helpers.',
211
- },
212
- {
213
- methodId: 'verify_detached',
214
- displayName: 'Verify Detached',
215
- inputPorts: [
216
- {
217
- portId: 'signature',
218
- acceptedTypeSets: [
219
- {
220
- setId: 'detached-signature',
221
- allowedTypes: [
222
- {
223
- schemaName: 'DetachedSignature.fbs',
224
- fileIdentifier: 'SIGD',
225
- },
226
- ],
227
- description:
228
- 'Detached signature envelope emitted by sign_detached.',
229
- },
230
- ],
231
- minStreams: 1,
232
- maxStreams: 1,
233
- required: true,
234
- description: 'Detached signature envelope to verify.',
235
- },
236
- ],
237
- outputPorts: [
238
- {
239
- portId: 'verification',
240
- acceptedTypeSets: [
241
- {
242
- setId: 'detached-verification-result',
243
- allowedTypes: [
244
- {
245
- schemaName: 'DetachedVerificationResult.fbs',
246
- fileIdentifier: 'SIGV',
247
- },
248
- ],
249
- description: 'Detached signature verification result.',
250
- },
251
- ],
252
- minStreams: 1,
253
- maxStreams: 1,
254
- required: true,
255
- description: 'Verification outcome and normalized signature metadata.',
256
- },
257
- ],
258
- maxBatch: 64,
259
- drainPolicy: 'drain-until-yield',
260
- description:
261
- 'Verifies detached signatures through the plugin contract using WASM-backed crypto only.',
262
- },
263
- ],
264
- schemasUsed: [
265
- {
266
- schemaName: 'FieldSelectionBundle.fbs',
267
- fileIdentifier: 'FSLB',
268
- },
269
- {
270
- schemaName: 'EncryptedFieldSet.fbs',
271
- fileIdentifier: 'EFLD',
272
- },
273
- {
274
- schemaName: 'DetachedSigningRequest.fbs',
275
- fileIdentifier: 'SGRQ',
276
- },
277
- {
278
- schemaName: 'DetachedSignature.fbs',
279
- fileIdentifier: 'SIGD',
280
- },
281
- {
282
- schemaName: 'DetachedVerificationResult.fbs',
283
- fileIdentifier: 'SIGV',
284
- },
285
- ],
286
- buildArtifacts: [
287
- {
288
- artifactId: 'hd-wallet-wasm-browser',
289
- kind: 'wasm-module',
290
- path: 'wasm/dist/hd-wallet.wasm',
291
- target: 'browser',
292
- entrySymbol: 'plugin_get_manifest_flatbuffer',
293
- },
294
- {
295
- artifactId: 'hd-wallet-wasm-loader',
296
- kind: 'javascript-loader',
297
- path: 'wasm/dist/hd-wallet.js',
298
- target: 'browser',
299
- entrySymbol: 'HDWalletWasm',
300
- },
301
- ],
302
- abiVersion: 1,
303
- });
304
-
305
- export function cloneSdnPluginManifest() {
306
- return JSON.parse(JSON.stringify(HD_WALLET_SDN_PLUGIN_MANIFEST));
307
- }
@@ -1,482 +0,0 @@
1
- import {
2
- cloneSdnPluginManifest,
3
- HD_WALLET_SDN_PLUGIN_MANIFEST,
4
- } from './sdn-plugin-manifest-source.mjs';
5
-
6
- const MANIFEST_EXPORTS = Object.freeze({
7
- bytesSymbol: 'plugin_get_manifest_flatbuffer',
8
- sizeSymbol: 'plugin_get_manifest_flatbuffer_size',
9
- });
10
-
11
- const textEncoder = new TextEncoder();
12
-
13
- function toUint8Array(value, fieldName) {
14
- if (value instanceof Uint8Array) {
15
- return new Uint8Array(value);
16
- }
17
- if (ArrayBuffer.isView(value)) {
18
- return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
19
- }
20
- if (value instanceof ArrayBuffer) {
21
- return new Uint8Array(value);
22
- }
23
- throw new TypeError(`${fieldName} must be a Uint8Array, ArrayBufferView, or ArrayBuffer.`);
24
- }
25
-
26
- function optionalUint8Array(value, fieldName) {
27
- if (value === null || value === undefined) {
28
- return null;
29
- }
30
- return toUint8Array(value, fieldName);
31
- }
32
-
33
- function cloneFrame(frame, payload) {
34
- return {
35
- portId: frame.portId,
36
- typeRef: frame.typeRef ? { ...frame.typeRef } : null,
37
- alignment: frame.alignment ?? 8,
38
- offset: frame.offset ?? 0,
39
- size: frame.size ?? 0,
40
- ownership: frame.ownership ?? 'shared',
41
- generation: frame.generation ?? 0,
42
- mutability: frame.mutability ?? 'immutable',
43
- traceId: frame.traceId ?? null,
44
- streamId: frame.streamId ?? 1,
45
- sequence: frame.sequence ?? 1,
46
- payload,
47
- };
48
- }
49
-
50
- function buildOutputFrame(sourceFrame, portId, schemaName, fileIdentifier, payload) {
51
- return cloneFrame(
52
- {
53
- portId,
54
- typeRef: {
55
- schemaName,
56
- fileIdentifier,
57
- schemaHash: [],
58
- acceptsAnyFlatbuffer: false,
59
- },
60
- alignment: 8,
61
- offset: 0,
62
- size: 0,
63
- ownership: 'shared',
64
- generation: 0,
65
- mutability: 'immutable',
66
- traceId:
67
- sourceFrame?.traceId ??
68
- `${HD_WALLET_SDN_PLUGIN_MANIFEST.pluginId}:${portId}`,
69
- streamId: sourceFrame?.streamId ?? 1,
70
- sequence: sourceFrame?.sequence ?? 1,
71
- },
72
- payload
73
- );
74
- }
75
-
76
- function resolveLastInput(request) {
77
- if (!Array.isArray(request?.inputs) || request.inputs.length === 0) {
78
- throw new Error('Plugin invocation requires at least one input frame.');
79
- }
80
- return request.inputs[request.inputs.length - 1];
81
- }
82
-
83
- function resolveRandomBytes(randomBytes, length, context) {
84
- if (typeof randomBytes !== 'function') {
85
- throw new Error(
86
- 'encrypt_fields requires an explicit randomBytes capability callback.'
87
- );
88
- }
89
- const bytes = toUint8Array(randomBytes(length, context), 'randomBytes result');
90
- if (bytes.length !== length) {
91
- throw new Error(`randomBytes capability must return exactly ${length} bytes.`);
92
- }
93
- return bytes;
94
- }
95
-
96
- function detectSigningCurve(payload) {
97
- const curve = String(payload.curve ?? '').trim();
98
- if (curve) {
99
- return curve;
100
- }
101
- const algorithm = String(payload.algorithm ?? '').trim();
102
- if (algorithm.startsWith('ed25519')) {
103
- return 'ed25519';
104
- }
105
- return 'secp256k1';
106
- }
107
-
108
- function resolveSigningInput(wallet, payload, curve) {
109
- const messageBytes = toUint8Array(
110
- payload.messageBytes ?? payload.message ?? payload.protectedRecordBytes,
111
- 'message'
112
- );
113
- if (curve === 'secp256k1') {
114
- return {
115
- messageBytes,
116
- digest: optionalUint8Array(payload.digest, 'digest') ?? wallet.utils.sha256(messageBytes),
117
- };
118
- }
119
- return {
120
- messageBytes,
121
- digest: optionalUint8Array(payload.digest, 'digest'),
122
- };
123
- }
124
-
125
- function resolveSignatureEnvelope(wallet, payload, walletSign) {
126
- const curve = detectSigningCurve(payload);
127
- const { messageBytes, digest } = resolveSigningInput(wallet, payload, curve);
128
-
129
- if (!payload.signerPrivateKey && typeof walletSign !== 'function') {
130
- throw new Error(
131
- 'sign_detached requires signerPrivateKey bytes or a walletSign capability callback.'
132
- );
133
- }
134
-
135
- if (typeof walletSign === 'function' && !payload.signerPrivateKey) {
136
- const response = walletSign({
137
- curve,
138
- payload,
139
- messageBytes,
140
- digest,
141
- });
142
- if (!response || typeof response !== 'object') {
143
- throw new Error('walletSign capability must return a signature envelope.');
144
- }
145
- return {
146
- curve,
147
- algorithm:
148
- response.algorithm ??
149
- (curve === 'ed25519'
150
- ? digest
151
- ? 'ed25519-prehash-sha256'
152
- : 'ed25519'
153
- : 'secp256k1-sha256'),
154
- messageBytes,
155
- digest,
156
- signature: toUint8Array(response.signature, 'walletSign signature'),
157
- publicKey: toUint8Array(response.publicKey, 'walletSign publicKey'),
158
- };
159
- }
160
-
161
- const privateKey = toUint8Array(payload.signerPrivateKey, 'signerPrivateKey');
162
-
163
- if (curve === 'ed25519') {
164
- const signatureInput = digest ?? messageBytes;
165
- return {
166
- curve,
167
- algorithm:
168
- payload.algorithm ??
169
- (digest ? 'ed25519-prehash-sha256' : 'ed25519'),
170
- messageBytes,
171
- digest,
172
- signature: wallet.curves.ed25519.sign(signatureInput, privateKey),
173
- publicKey:
174
- optionalUint8Array(payload.publicKey, 'publicKey') ??
175
- wallet.curves.ed25519.publicKeyFromSeed(privateKey),
176
- };
177
- }
178
-
179
- return {
180
- curve: 'secp256k1',
181
- algorithm: payload.algorithm ?? 'secp256k1-sha256',
182
- messageBytes,
183
- digest,
184
- signature: wallet.curves.secp256k1.sign(digest, privateKey),
185
- publicKey:
186
- optionalUint8Array(payload.publicKey, 'publicKey') ??
187
- wallet.curves.publicKeyFromPrivate(privateKey, 0),
188
- };
189
- }
190
-
191
- function resolveVerificationEnvelope(wallet, payload) {
192
- const curve = detectSigningCurve(payload);
193
- const signature = toUint8Array(payload.signature, 'signature');
194
- const publicKey = toUint8Array(payload.publicKey, 'publicKey');
195
- const { messageBytes, digest } = resolveSigningInput(wallet, payload, curve);
196
- const verificationInput = curve === 'secp256k1' ? digest : digest ?? messageBytes;
197
- const valid =
198
- curve === 'ed25519'
199
- ? wallet.curves.ed25519.verify(verificationInput, signature, publicKey)
200
- : wallet.curves.secp256k1.verify(verificationInput, signature, publicKey);
201
-
202
- return {
203
- curve,
204
- algorithm:
205
- payload.algorithm ??
206
- (curve === 'ed25519'
207
- ? digest
208
- ? 'ed25519-prehash-sha256'
209
- : 'ed25519'
210
- : 'secp256k1-sha256'),
211
- messageBytes,
212
- digest,
213
- signature,
214
- publicKey,
215
- valid,
216
- };
217
- }
218
-
219
- function resolveFieldCurve(payload, field) {
220
- return String(field?.curve ?? payload.curve ?? 'x25519').trim() || 'x25519';
221
- }
222
-
223
- function ecdhForCurve(wallet, curve, privateKey, publicKey) {
224
- if (curve === 'secp256k1') {
225
- return wallet.curves.secp256k1.ecdh(privateKey, publicKey);
226
- }
227
- if (curve === 'x25519') {
228
- return wallet.curves.x25519.ecdh(privateKey, publicKey);
229
- }
230
- throw new Error(`encrypt_fields does not support curve "${curve}".`);
231
- }
232
-
233
- function publicKeyForCurve(wallet, curve, privateKey) {
234
- if (curve === 'secp256k1') {
235
- return wallet.curves.publicKeyFromPrivate(privateKey, 0);
236
- }
237
- if (curve === 'x25519') {
238
- return wallet.curves.x25519.publicKey(privateKey);
239
- }
240
- throw new Error(`encrypt_fields does not support curve "${curve}".`);
241
- }
242
-
243
- function algorithmForCurve(curve) {
244
- if (curve === 'secp256k1') {
245
- return 'secp256k1-hkdf-aes-256-gcm';
246
- }
247
- return 'x25519-hkdf-aes-256-gcm';
248
- }
249
-
250
- function resolveSenderPrivateKey(randomBytes, payload, curve) {
251
- if (payload.senderPrivateKey) {
252
- return toUint8Array(payload.senderPrivateKey, 'senderPrivateKey');
253
- }
254
- if (curve === 'x25519') {
255
- return resolveRandomBytes(randomBytes, 32, {
256
- methodId: 'encrypt_fields',
257
- purpose: 'senderPrivateKey',
258
- });
259
- }
260
- throw new Error(
261
- 'encrypt_fields requires senderPrivateKey when using secp256k1 field encryption.'
262
- );
263
- }
264
-
265
- function normalizeAad(field, payload) {
266
- return (
267
- optionalUint8Array(field?.aad, 'aad') ??
268
- optionalUint8Array(payload?.aad, 'aad') ??
269
- new Uint8Array()
270
- );
271
- }
272
-
273
- function encryptFieldsPayload(wallet, payload, randomBytes) {
274
- const fields = Array.isArray(payload.fields) ? payload.fields : [];
275
- if (fields.length === 0) {
276
- throw new Error('encrypt_fields requires at least one field entry.');
277
- }
278
-
279
- const encryptedFields = [];
280
- for (const field of fields) {
281
- const curve = resolveFieldCurve(payload, field);
282
- const recipientPublicKey = toUint8Array(
283
- payload.recipientPublicKey ?? field.recipientPublicKey,
284
- 'recipientPublicKey'
285
- );
286
- const senderPrivateKey = resolveSenderPrivateKey(randomBytes, payload, curve);
287
- const senderPublicKey =
288
- optionalUint8Array(field.senderPublicKey, 'senderPublicKey') ??
289
- publicKeyForCurve(wallet, curve, senderPrivateKey);
290
- const salt =
291
- optionalUint8Array(field.salt, 'salt') ??
292
- resolveRandomBytes(randomBytes, 32, {
293
- methodId: 'encrypt_fields',
294
- fieldPath: field.fieldPath,
295
- purpose: 'salt',
296
- });
297
- const iv =
298
- optionalUint8Array(field.iv, 'iv') ??
299
- resolveRandomBytes(randomBytes, 12, {
300
- methodId: 'encrypt_fields',
301
- fieldPath: field.fieldPath,
302
- purpose: 'iv',
303
- });
304
- const plaintext = toUint8Array(field.plaintext, 'plaintext');
305
- const aad = normalizeAad(field, payload);
306
- const sharedSecret = ecdhForCurve(wallet, curve, senderPrivateKey, recipientPublicKey);
307
- const hkdfInfo = textEncoder.encode(`field:${field.fieldPath}`);
308
- const aesKey = wallet.utils.hkdf(sharedSecret, salt, hkdfInfo, 32);
309
- const { ciphertext, tag } = wallet.utils.aesGcm.encrypt(
310
- aesKey,
311
- plaintext,
312
- iv,
313
- aad
314
- );
315
-
316
- encryptedFields.push({
317
- fieldPath: String(field.fieldPath ?? ''),
318
- curve,
319
- algorithm: algorithmForCurve(curve),
320
- salt,
321
- iv,
322
- tag,
323
- ciphertext,
324
- senderPublicKey,
325
- aad,
326
- });
327
- }
328
-
329
- return { fields: encryptedFields };
330
- }
331
-
332
- function decryptFieldsPayload(wallet, payload) {
333
- const fields = Array.isArray(payload.fields) ? payload.fields : [];
334
- if (fields.length === 0) {
335
- throw new Error('decrypt_fields requires at least one encrypted field entry.');
336
- }
337
-
338
- const recipientPrivateKey = toUint8Array(
339
- payload.recipientPrivateKey,
340
- 'recipientPrivateKey'
341
- );
342
- const decryptedFields = [];
343
-
344
- for (const field of fields) {
345
- const curve = resolveFieldCurve(payload, field);
346
- const senderPublicKey = toUint8Array(field.senderPublicKey, 'senderPublicKey');
347
- const salt = toUint8Array(field.salt, 'salt');
348
- const iv = toUint8Array(field.iv, 'iv');
349
- const tag = toUint8Array(field.tag, 'tag');
350
- const ciphertext = toUint8Array(field.ciphertext, 'ciphertext');
351
- const aad = normalizeAad(field, payload);
352
- const sharedSecret = ecdhForCurve(wallet, curve, recipientPrivateKey, senderPublicKey);
353
- const hkdfInfo = textEncoder.encode(`field:${field.fieldPath}`);
354
- const aesKey = wallet.utils.hkdf(sharedSecret, salt, hkdfInfo, 32);
355
- const plaintext = wallet.utils.aesGcm.decrypt(aesKey, ciphertext, tag, iv, aad);
356
-
357
- decryptedFields.push({
358
- fieldPath: String(field.fieldPath ?? ''),
359
- curve,
360
- algorithm: field.algorithm ?? algorithmForCurve(curve),
361
- plaintext,
362
- aad,
363
- });
364
- }
365
-
366
- return { fields: decryptedFields };
367
- }
368
-
369
- function readEmbeddedManifestBytes(wasm) {
370
- const getBytes = wasm._plugin_get_manifest_flatbuffer;
371
- const getSize = wasm._plugin_get_manifest_flatbuffer_size;
372
- if (typeof getBytes !== 'function' || typeof getSize !== 'function') {
373
- throw new Error('Embedded plugin manifest exports are not available in this build.');
374
- }
375
- const pointer = Number(getBytes());
376
- const size = Number(getSize());
377
- if (!Number.isFinite(pointer) || !Number.isFinite(size) || size <= 0) {
378
- throw new Error('Embedded plugin manifest exports returned invalid values.');
379
- }
380
- return wasm.HEAPU8.slice(pointer, pointer + size);
381
- }
382
-
383
- function buildInvocationResult(outputs) {
384
- return {
385
- outputs,
386
- backlogRemaining: 0,
387
- yielded: false,
388
- };
389
- }
390
-
391
- export function createSdnPluginContract({ wallet, wasm, randomBytes = null, walletSign = null }) {
392
- function invoke(methodId, request = {}) {
393
- const inputFrame = resolveLastInput(request);
394
- const payload = inputFrame.payload ?? {};
395
-
396
- switch (methodId) {
397
- case 'encrypt_fields':
398
- return buildInvocationResult([
399
- buildOutputFrame(
400
- inputFrame,
401
- 'encrypted_fields',
402
- 'EncryptedFieldSet.fbs',
403
- 'EFLD',
404
- encryptFieldsPayload(wallet, payload, randomBytes)
405
- ),
406
- ]);
407
- case 'decrypt_fields':
408
- return buildInvocationResult([
409
- buildOutputFrame(
410
- inputFrame,
411
- 'field_set',
412
- 'FieldSelectionBundle.fbs',
413
- 'FSLB',
414
- decryptFieldsPayload(wallet, payload)
415
- ),
416
- ]);
417
- case 'sign_detached':
418
- return buildInvocationResult([
419
- buildOutputFrame(
420
- inputFrame,
421
- 'signature',
422
- 'DetachedSignature.fbs',
423
- 'SIGD',
424
- resolveSignatureEnvelope(wallet, payload, walletSign)
425
- ),
426
- ]);
427
- case 'verify_detached':
428
- return buildInvocationResult([
429
- buildOutputFrame(
430
- inputFrame,
431
- 'verification',
432
- 'DetachedVerificationResult.fbs',
433
- 'SIGV',
434
- resolveVerificationEnvelope(wallet, payload)
435
- ),
436
- ]);
437
- default:
438
- throw new Error(`Unknown SDN plugin method "${methodId}".`);
439
- }
440
- }
441
-
442
- return {
443
- manifest: cloneSdnPluginManifest(),
444
- manifestExports: MANIFEST_EXPORTS,
445
- getManifest() {
446
- return cloneSdnPluginManifest();
447
- },
448
- getManifestBytes() {
449
- return readEmbeddedManifestBytes(wasm);
450
- },
451
- withCapabilities(capabilities = {}) {
452
- return createSdnPluginContract({
453
- wallet,
454
- wasm,
455
- randomBytes:
456
- capabilities.randomBytes !== undefined
457
- ? capabilities.randomBytes
458
- : randomBytes,
459
- walletSign:
460
- capabilities.walletSign !== undefined
461
- ? capabilities.walletSign
462
- : walletSign,
463
- });
464
- },
465
- invoke,
466
- encrypt_fields(request) {
467
- return invoke('encrypt_fields', request);
468
- },
469
- decrypt_fields(request) {
470
- return invoke('decrypt_fields', request);
471
- },
472
- sign_detached(request) {
473
- return invoke('sign_detached', request);
474
- },
475
- verify_detached(request) {
476
- return invoke('verify_detached', request);
477
- },
478
- };
479
- }
480
-
481
- export { MANIFEST_EXPORTS as SDN_PLUGIN_MANIFEST_EXPORTS };
482
-