@pulse-compute/wasm-host-runtime 0.0.0 → 1.0.0-beta.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.
@@ -0,0 +1,32 @@
1
+
2
+ 'use strict';
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { createKvProvider, createKvError } = require('./kv-provider.js');
6
+ const RESULT_NONE = 0; const RESULT_TEXT = 1; const RESULT_JSON = 2; const RESULT_BINARY = 3; const RESULT_EMPTY = 4; const RESULT_STREAM = 5;
7
+ function createSetupError(code, message, details) { const err = new Error(message); err.code = code; err.details = details || null; return err; }
8
+ function normalizeHeaders(headers) { if (!headers) return []; if (Array.isArray(headers)) return headers.map((pair) => [String(pair[0]), String(pair[1])]); if (typeof headers[Symbol.iterator] === 'function') return Array.from(headers, ([name, value]) => [String(name), String(value)]); return Object.entries(headers).map(([name, value]) => [String(name), String(value)]); }
9
+ function methodNameToCode(exports, method) { const upper = String(method || '').toUpperCase(); if (upper === 'GET') return exports.pulse_bridge_method_get(); if (upper === 'POST') return exports.pulse_bridge_method_post(); return 0; }
10
+ function createPulseWasmCompiledHostRuntimeWithKv(options = {}) {
11
+ const wasmBytes = options.wasmBytes || (options.wasmPath ? fs.readFileSync(path.resolve(options.wasmPath)) : undefined);
12
+ if (!wasmBytes) throw createSetupError('PULSEWASM_COMPILED_RUNTIME_WASM_REQUIRED', 'createPulseWasmCompiledHostRuntimeWithKv requires wasmBytes or wasmPath.');
13
+ const deploymentPosture = options.deploymentPosture || { engine: 'wasm' };
14
+ if ((deploymentPosture.engine || 'wasm') === 'js' && !options.allowJsEngine) throw createSetupError('PULSEWASM_COMPILED_RUNTIME_ENGINE_MISMATCH', 'compiled-wasm host runtime refuses JS engine posture.', { engine: deploymentPosture.engine });
15
+ const encoder = new TextEncoder(); const decoder = new TextDecoder();
16
+ const module = new WebAssembly.Module(wasmBytes);
17
+ const imports = { env: { abort() { throw new Error('AssemblyScript abort'); } } };
18
+ const instance = new WebAssembly.Instance(module, imports); const exports = instance.exports;
19
+ const hostResponses = new Map();
20
+ for (const [key, value] of Object.entries(options.hostResponses || {})) hostResponses.set(Number(key), value);
21
+ if (!hostResponses.has(77)) hostResponses.set(77, { status: 206, headers: [['Content-Type', 'application/octet-stream'], ['Grip-Channel', 'backend:stream']], bodyStream: { chunks: ['asset:42'] } });
22
+ const kvProvider = options.kvProvider || createKvProvider({ provider: options.runtimeProvider || { kind: 'local', kv: {} } });
23
+ function memory() { return exports.memory; }
24
+ function allocText(text) { const bytes = encoder.encode(String(text ?? '')); const ptr = exports.pulse_alloc(bytes.length || 1); new Uint8Array(memory().buffer, ptr, bytes.length).set(bytes); return { ptr, len: bytes.length }; }
25
+ function readBytes(ptr, len) { if (!ptr || !len) return ''; return decoder.decode(new Uint8Array(memory().buffer, ptr, len)); }
26
+ function readResultHeaders() { const headers = []; const count = exports.pulse_result_header_count ? exports.pulse_result_header_count() : 0; for (let i = 0; i < count; i += 1) headers.push([readBytes(exports.pulse_result_header_name_ptr(i), exports.pulse_result_header_name_len(i)), readBytes(exports.pulse_result_header_value_ptr(i), exports.pulse_result_header_value_len(i)), exports.pulse_result_header_mode ? exports.pulse_result_header_mode(i) : 2]); return headers; }
27
+ function mergeHeaders(base, resultHeaders) { const merged = normalizeHeaders(base); for (const item of resultHeaders || []) { const name = item[0]; const value = item[1]; const mode = Number(item[2] || 2); const lower = String(name).toLowerCase(); if (mode === 1) { for (let i = merged.length - 1; i >= 0; i -= 1) if (String(merged[i][0]).toLowerCase() === lower) merged.splice(i, 1); } merged.push([String(name), String(value)]); } return merged; }
28
+ function decodeResult(status) { const kindCode = exports.pulse_result_kind ? exports.pulse_result_kind() : RESULT_NONE; const resultStatus = exports.pulse_result_status ? exports.pulse_result_status() : status; const resultHeaders = readResultHeaders(); if (kindCode === RESULT_STREAM) { const responseRef = exports.pulse_result_response_ref ? exports.pulse_result_response_ref() : 0; const streamRef = exports.pulse_result_stream_ref ? exports.pulse_result_stream_ref() : 0; const response = hostResponses.get(responseRef) || (streamRef ? { status: resultStatus || status, headers: [], bodyStream: hostResponses.get(streamRef) } : undefined); return { status: resultStatus || (response && response.status) || status, kind: 'stream', kindCode, headers: mergeHeaders(response && response.headers, resultHeaders), bodyStream: response && response.bodyStream, responseRef, streamRef, response, diagnostics: [] }; } const body = exports.pulse_result_body_ptr ? readBytes(exports.pulse_result_body_ptr(), exports.pulse_result_body_len()) : ''; const kind = kindCode === RESULT_JSON ? 'json' : kindCode === RESULT_TEXT ? 'text' : kindCode === RESULT_EMPTY ? 'empty' : 'none'; return { status: resultStatus || status, kind, kindCode, headers: resultHeaders.map(([name, value]) => [name, value]), body: body || undefined, diagnostics: [] }; }
29
+ function executeRequest(input = {}) { const body = allocText(input.bodyText || ''); if (exports.pulse_bridge_set_body_text) exports.pulse_bridge_set_body_text(body.ptr, body.len); const path = allocText(input.path || '/'); const status = exports.pulse_execute_request(methodNameToCode(exports, input.method), path.ptr, path.len); return decodeResult(status); }
30
+ return { version: 'pulsewasm.compiled-wasm-host-runtime-kv.v1', handlerExecutionMode: 'compiled-wasm', provider: kvProvider, kv(name){ return kvProvider.kv(name); }, executeRequest, executeConnect(){ return { event: 'connect', invoked: 0, status: 200 }; }, executeDisconnect(){ return { event: 'disconnect', invoked: 0, status: 200 }; }, close(){}, exports, imports: WebAssembly.Module.imports(module), hostResponses };
31
+ }
32
+ module.exports = { createPulseWasmCompiledHostRuntimeWithKv, RESULT_NONE, RESULT_TEXT, RESULT_JSON, RESULT_BINARY, RESULT_EMPTY, RESULT_STREAM, createKvError };
@@ -0,0 +1,181 @@
1
+ 'use strict';
2
+
3
+ const CONTINUATION_REGISTRY_VERSION = 'pulse.continuation-registry.v1';
4
+ const CONTINUATION_STATES = Object.freeze(['created', 'waiting', 'resumed', 'completed', 'failed', 'expired', 'cancelled']);
5
+ const TERMINAL_STATES = new Set(['completed', 'failed', 'expired', 'cancelled']);
6
+
7
+ class ContinuationRegistryError extends Error {
8
+ constructor(name, code, message, detail = {}) {
9
+ super(message);
10
+ this.name = name;
11
+ this.code = code;
12
+ this.detail = Object.freeze({ ...detail });
13
+ }
14
+ }
15
+
16
+ function stateError(record, operation, expected) {
17
+ const detail = { id: record.id, state: record.state, operation, expected };
18
+ if (record.state === 'expired') return new ContinuationRegistryError('ContinuationExpiredError', 'PULSE_CONTINUATION_EXPIRED', `Continuation ${record.id} expired before ${operation}.`, detail);
19
+ if (['resumed', 'completed', 'failed'].includes(record.state) && operation === 'resume') return new ContinuationRegistryError('ContinuationDoubleResumeError', 'PULSE_CONTINUATION_DOUBLE_RESUME', `Continuation ${record.id} cannot be resumed from ${record.state}.`, detail);
20
+ return new ContinuationRegistryError('ContinuationStateError', 'PULSE_CONTINUATION_STATE', `Continuation ${record.id} cannot ${operation} from ${record.state}.`, detail);
21
+ }
22
+
23
+ function cloneRecord(record) {
24
+ return Object.freeze({
25
+ id: record.id,
26
+ branchPoint: record.branchPoint,
27
+ effectIds: Object.freeze([...record.effectIds]),
28
+ state: record.state,
29
+ states: Object.freeze([...record.states]),
30
+ createdAt: record.createdAt,
31
+ expiresAt: record.expiresAt,
32
+ resumedAt: record.resumedAt,
33
+ completedAt: record.completedAt,
34
+ failedAt: record.failedAt,
35
+ expiredAt: record.expiredAt,
36
+ cancelledAt: record.cancelledAt,
37
+ resumeCount: record.resumeCount,
38
+ error: record.error
39
+ });
40
+ }
41
+
42
+ function createContinuationRegistry(options = {}) {
43
+ const clock = typeof options.clock === 'function' ? options.clock : () => Date.now();
44
+ const defaultTtlMs = Number.isFinite(Number(options.ttlMs)) ? Math.max(1, Number(options.ttlMs)) : 30000;
45
+ const records = new Map();
46
+ const trace = [];
47
+ let sequence = 0;
48
+
49
+ function event(record, type, detail = {}) {
50
+ trace.push(Object.freeze({
51
+ sequence: trace.length + 1,
52
+ type,
53
+ id: record.id,
54
+ branchPoint: record.branchPoint,
55
+ state: record.state,
56
+ at: clock(),
57
+ detail: Object.freeze({ ...detail })
58
+ }));
59
+ }
60
+
61
+ function requireRecord(id) {
62
+ const record = records.get(String(id));
63
+ if (!record) throw new ContinuationRegistryError('ContinuationNotFoundError', 'PULSE_CONTINUATION_NOT_FOUND', `Unknown continuation ${id}.`, { id: String(id) });
64
+ return record;
65
+ }
66
+
67
+ function transition(record, state, at, extra = {}) {
68
+ record.state = state;
69
+ record.states.push(state);
70
+ Object.assign(record, extra);
71
+ event(record, state, extra);
72
+ return cloneRecord(record);
73
+ }
74
+
75
+ function create(input = {}) {
76
+ sequence += 1;
77
+ const now = clock();
78
+ const id = String(input.id || `continuation-${sequence}`);
79
+ if (records.has(id)) throw new ContinuationRegistryError('ContinuationDuplicateError', 'PULSE_CONTINUATION_DUPLICATE', `Continuation ${id} already exists.`, { id });
80
+ const ttlMs = Number.isFinite(Number(input.ttlMs)) ? Math.max(1, Number(input.ttlMs)) : defaultTtlMs;
81
+ const record = {
82
+ id,
83
+ branchPoint: String(input.branchPoint || id),
84
+ effectIds: Array.isArray(input.effectIds) ? input.effectIds.map(String) : [],
85
+ state: 'created',
86
+ states: ['created'],
87
+ createdAt: now,
88
+ expiresAt: now + ttlMs,
89
+ resumeCount: 0,
90
+ error: undefined
91
+ };
92
+ records.set(id, record);
93
+ event(record, 'created');
94
+ return cloneRecord(record);
95
+ }
96
+
97
+ function wait(id) {
98
+ const record = requireRecord(id);
99
+ if (record.state !== 'created') throw stateError(record, 'wait', 'created');
100
+ return transition(record, 'waiting', clock());
101
+ }
102
+
103
+ function resume(id) {
104
+ const record = requireRecord(id);
105
+ const now = clock();
106
+ if (record.state === 'waiting' && now >= record.expiresAt) {
107
+ transition(record, 'expired', now, { expiredAt: now });
108
+ throw stateError(record, 'resume', 'waiting');
109
+ }
110
+ if (record.state !== 'waiting') throw stateError(record, 'resume', 'waiting');
111
+ record.resumeCount += 1;
112
+ return transition(record, 'resumed', now, { resumedAt: now });
113
+ }
114
+
115
+ function complete(id) {
116
+ const record = requireRecord(id);
117
+ if (record.state !== 'resumed') throw stateError(record, 'complete', 'resumed');
118
+ const now = clock();
119
+ return transition(record, 'completed', now, { completedAt: now });
120
+ }
121
+
122
+ function fail(id, error) {
123
+ const record = requireRecord(id);
124
+ if (!['created', 'waiting', 'resumed'].includes(record.state)) throw stateError(record, 'fail', 'created|waiting|resumed');
125
+ const now = clock();
126
+ const errorName = error && (error.name || error.code || error.message) ? String(error.name || error.code || error.message) : String(error || 'ContinuationFailure');
127
+ return transition(record, 'failed', now, { failedAt: now, error: errorName });
128
+ }
129
+
130
+ function expire(id) {
131
+ const record = requireRecord(id);
132
+ if (TERMINAL_STATES.has(record.state) || record.state === 'resumed') throw stateError(record, 'expire', 'created|waiting');
133
+ const now = clock();
134
+ return transition(record, 'expired', now, { expiredAt: now });
135
+ }
136
+
137
+ function cancel(id) {
138
+ const record = requireRecord(id);
139
+ if (TERMINAL_STATES.has(record.state)) throw stateError(record, 'cancel', 'created|waiting|resumed');
140
+ const now = clock();
141
+ return transition(record, 'cancelled', now, { cancelledAt: now });
142
+ }
143
+
144
+ function sweepExpired(now = clock()) {
145
+ const expired = [];
146
+ for (const record of records.values()) {
147
+ if (['created', 'waiting'].includes(record.state) && now >= record.expiresAt) {
148
+ transition(record, 'expired', now, { expiredAt: now });
149
+ expired.push(record.id);
150
+ }
151
+ }
152
+ return Object.freeze(expired);
153
+ }
154
+
155
+ function get(id) { return cloneRecord(requireRecord(id)); }
156
+ function list() { return Object.freeze([...records.values()].map(cloneRecord)); }
157
+ function traceSnapshot() { return Object.freeze([...trace]); }
158
+
159
+ return Object.freeze({
160
+ version: CONTINUATION_REGISTRY_VERSION,
161
+ states: CONTINUATION_STATES,
162
+ create,
163
+ wait,
164
+ resume,
165
+ complete,
166
+ fail,
167
+ expire,
168
+ cancel,
169
+ sweepExpired,
170
+ get,
171
+ list,
172
+ trace: traceSnapshot
173
+ });
174
+ }
175
+
176
+ module.exports = {
177
+ CONTINUATION_REGISTRY_VERSION,
178
+ CONTINUATION_STATES,
179
+ ContinuationRegistryError,
180
+ createContinuationRegistry
181
+ };
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ const compiledWasmHostRuntimeKv = require('./compiled-wasm-host-runtime-kv.js');
4
+ const kvProvider = require('./kv-provider.js');
5
+ const continuationRegistry = require('./continuation-registry.js');
6
+ const canonicalApiRuntime = require('./canonical-api-runtime.js');
7
+ const canonicalNativeHost = require('./canonical-native-host.js');
8
+
9
+ module.exports = {
10
+ compiledWasmHostRuntimeKv,
11
+ kvProvider,
12
+ continuationRegistry,
13
+ canonicalApiRuntime,
14
+ canonicalNativeHost,
15
+ ...canonicalApiRuntime,
16
+ ...canonicalNativeHost,
17
+ ...continuationRegistry,
18
+ createPulseWasmCompiledHostRuntimeWithKv: compiledWasmHostRuntimeKv.createPulseWasmCompiledHostRuntimeWithKv,
19
+ createKvProvider: kvProvider.createKvProvider,
20
+ createKvError: kvProvider.createKvError,
21
+ createNamespaceStore: kvProvider.createNamespaceStore
22
+ };
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ function createKvError(code, message, details) {
4
+ const err = new Error(message);
5
+ err.code = code;
6
+ err.details = details || null;
7
+ return err;
8
+ }
9
+
10
+ function createNamespaceStore(namespace, map) {
11
+ return {
12
+ namespace,
13
+ getText(key) {
14
+ const normalized = String(key);
15
+ return map.has(normalized) ? String(map.get(normalized)) : null;
16
+ },
17
+ setText(key, value) {
18
+ map.set(String(key), String(value));
19
+ return { ok: true };
20
+ },
21
+ delete(key) {
22
+ const existed = map.delete(String(key));
23
+ return { ok: true, existed };
24
+ },
25
+ has(key) {
26
+ return map.has(String(key));
27
+ },
28
+ entries() {
29
+ return Array.from(map.entries());
30
+ }
31
+ };
32
+ }
33
+
34
+ function createKvProvider(options = {}) {
35
+ const provider = options.provider || options.runtimeProvider || { kind: 'local', kv: {} };
36
+ const stores = new Map();
37
+ const configured = provider && provider.kv ? provider.kv : {};
38
+ const seed = options.seed || {};
39
+ for (const [logicalName, physicalName] of Object.entries(configured)) {
40
+ const initial = new Map(Object.entries(seed[logicalName] || {}));
41
+ stores.set(String(logicalName), createNamespaceStore(String(physicalName), initial));
42
+ }
43
+ return {
44
+ kind: provider.kind || 'unknown',
45
+ configuredNamespaces: Array.from(stores.keys()),
46
+ kv(name) {
47
+ const key = String(name);
48
+ if (!stores.has(key)) {
49
+ throw createKvError('PULSEWASM_KV_NAMESPACE_UNKNOWN', `Unknown KV namespace: ${key}`, { namespace: key, configured: Array.from(stores.keys()) });
50
+ }
51
+ return stores.get(key);
52
+ }
53
+ };
54
+ }
55
+
56
+ module.exports = { createKvProvider, createKvError, createNamespaceStore };
@@ -0,0 +1,330 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ CRYPTO_ES256_GUEST_LINKED_IMPLEMENTATION,
5
+ CRYPTO_ES256_GUEST_LINKED_REALIZATION,
6
+ CRYPTO_GUEST_SOURCE_IMPLEMENTATION,
7
+ CRYPTO_REALIZATION_PLAN_VERSION
8
+ } = require('@pulse-compute/wasm-contracts/crypto/contracts');
9
+
10
+ const NATIVE_CRYPTO_VERIFIER_VERSION = 'pulse.native-crypto-verifier.v1';
11
+ const HS256_REALIZATION = 'guest-source:pulse-hmac-as';
12
+ const HS256_EXPORT = 'pulse_crypto_hs256_verify';
13
+ const ES256_EXPORT = 'pulse_crypto_es256_verify';
14
+ const ES256_REALIZATION = CRYPTO_ES256_GUEST_LINKED_REALIZATION;
15
+ const PAGE_BYTES = 64 * 1024;
16
+ const GUARD_BYTES = PAGE_BYTES;
17
+ const KEY_BYTES_MINIMUM = 32;
18
+ const KEY_BYTES_MAXIMUM = 4 * 1024;
19
+ const DATA_BYTES_MAXIMUM = 1024 * 1024;
20
+ const TAG_BYTES = 32;
21
+ const MAX_SIGNED_POINTER = 0x7fffffff;
22
+ const ES256_MEMORY_BYTES = 32 * PAGE_BYTES;
23
+ const ES256_FRAME_POINTER = 8 * PAGE_BYTES;
24
+ const ES256_FRAME_CAPACITY = 16_640;
25
+ const ES256_FRAME_MAGIC = 0x3253_4550;
26
+ const ES256_FRAME_HEADER_BYTES = 64;
27
+ const ES256_KEY_BYTES = 64;
28
+ const ES256_SIGNATURE_BYTES = 64;
29
+ const ES256_DATA_BYTES_MAXIMUM = 16_340;
30
+
31
+ const RESULT_BY_CODE = Object.freeze({
32
+ 1: 'valid',
33
+ 0: 'invalid-authenticator',
34
+ '-1': 'invalid-key',
35
+ '-2': 'invalid-input',
36
+ '-3': 'realization-failure'
37
+ });
38
+
39
+ function result(status) {
40
+ return Object.freeze({ status });
41
+ }
42
+
43
+ function ownData(value, name) {
44
+ if (!value || typeof value !== 'object') return undefined;
45
+ const descriptor = Object.getOwnPropertyDescriptor(value, name);
46
+ return descriptor && Object.prototype.hasOwnProperty.call(descriptor, 'value')
47
+ ? descriptor.value
48
+ : undefined;
49
+ }
50
+
51
+ function bytes(value) {
52
+ return value instanceof Uint8Array ? value : undefined;
53
+ }
54
+
55
+ function selectedAlgorithm(plan, algorithm, realization, kind, implementation) {
56
+ if (
57
+ !plan
58
+ || typeof plan !== 'object'
59
+ || !plan.crypto
60
+ || plan.crypto.version !== CRYPTO_REALIZATION_PLAN_VERSION
61
+ || plan.crypto.target !== 'native'
62
+ || plan.crypto.automaticFallback !== false
63
+ || !Array.isArray(plan.crypto.algorithms)
64
+ ) return undefined;
65
+ const matches = plan.crypto.algorithms.filter((entry) => (
66
+ entry
67
+ && entry.algorithm === algorithm
68
+ && entry.realization === realization
69
+ && entry.kind === kind
70
+ && entry.implementation === implementation
71
+ && entry.automaticFallback === false
72
+ && entry.targetImplemented === true
73
+ ));
74
+ return matches.length === 1 ? matches[0] : undefined;
75
+ }
76
+
77
+ function selectedHs256(plan) {
78
+ return selectedAlgorithm(
79
+ plan,
80
+ 'HS256',
81
+ HS256_REALIZATION,
82
+ 'guest-source',
83
+ CRYPTO_GUEST_SOURCE_IMPLEMENTATION
84
+ );
85
+ }
86
+
87
+ function selectedEs256(plan) {
88
+ return selectedAlgorithm(
89
+ plan,
90
+ 'ES256',
91
+ ES256_REALIZATION,
92
+ 'guest-linked',
93
+ CRYPTO_ES256_GUEST_LINKED_IMPLEMENTATION
94
+ );
95
+ }
96
+
97
+ function normalizeRequest(request) {
98
+ const algorithm = ownData(request, 'algorithm');
99
+ const key = ownData(request, 'key');
100
+ const keyType = ownData(key, 'type');
101
+ const keyBytes = bytes(ownData(key, 'bytes'));
102
+ const data = bytes(ownData(request, 'data'));
103
+ const tag = bytes(ownData(request, 'tag'));
104
+ if (
105
+ algorithm !== 'HS256'
106
+ || keyType !== 'hmac-key-bytes'
107
+ || !keyBytes
108
+ || !data
109
+ || !tag
110
+ ) return Object.freeze({ result: result('invalid-input') });
111
+ if (
112
+ keyBytes.byteLength < KEY_BYTES_MINIMUM
113
+ || keyBytes.byteLength > KEY_BYTES_MAXIMUM
114
+ ) return Object.freeze({ result: result('invalid-key') });
115
+ if (
116
+ data.byteLength > DATA_BYTES_MAXIMUM
117
+ || tag.byteLength !== TAG_BYTES
118
+ ) return Object.freeze({ result: result('invalid-input') });
119
+ return Object.freeze({ keyBytes, data, tag });
120
+ }
121
+
122
+ function normalizeSignatureRequest(request) {
123
+ const algorithm = ownData(request, 'algorithm');
124
+ const key = ownData(request, 'key');
125
+ const keyType = ownData(key, 'type');
126
+ const keyBytes = bytes(ownData(key, 'bytes'));
127
+ const data = bytes(ownData(request, 'data'));
128
+ const signature = bytes(ownData(request, 'signature'));
129
+ if (
130
+ algorithm !== 'ES256'
131
+ || keyType !== 'p256-public-key-bytes'
132
+ || !keyBytes
133
+ || !data
134
+ || !signature
135
+ ) return Object.freeze({ result: result('invalid-input') });
136
+ if (keyBytes.byteLength !== ES256_KEY_BYTES) {
137
+ return Object.freeze({ result: result('invalid-key') });
138
+ }
139
+ if (
140
+ data.byteLength > ES256_DATA_BYTES_MAXIMUM
141
+ || signature.byteLength !== ES256_SIGNATURE_BYTES
142
+ ) return Object.freeze({ result: result('invalid-input') });
143
+ return Object.freeze({ keyBytes, data, signature });
144
+ }
145
+
146
+ function stage(memory, inputs) {
147
+ const payloadBytes = inputs.reduce((total, input) => total + input.byteLength, 0);
148
+ const oldEnd = memory.buffer.byteLength;
149
+ const required = GUARD_BYTES + payloadBytes + 48;
150
+ const pages = Math.ceil(required / PAGE_BYTES);
151
+ const newEnd = oldEnd + pages * PAGE_BYTES;
152
+ if (
153
+ !Number.isSafeInteger(newEnd)
154
+ || newEnd > MAX_SIGNED_POINTER
155
+ || memory.grow(pages) < 0
156
+ ) return undefined;
157
+
158
+ const view = new Uint8Array(memory.buffer);
159
+ const pointers = [];
160
+ let cursor = oldEnd + GUARD_BYTES;
161
+ for (const input of inputs) {
162
+ cursor = (cursor + 15) & ~15;
163
+ pointers.push(cursor);
164
+ view.set(input, cursor);
165
+ cursor += input.byteLength;
166
+ }
167
+ return Object.freeze({
168
+ pointers: Object.freeze(pointers),
169
+ wipe() {
170
+ new Uint8Array(memory.buffer).fill(0, oldEnd + GUARD_BYTES, cursor);
171
+ }
172
+ });
173
+ }
174
+
175
+ function align16(value) {
176
+ return (value + 15) & ~15;
177
+ }
178
+
179
+ function stageEs256Frame(memory, normalized) {
180
+ if (
181
+ !(memory instanceof WebAssembly.Memory)
182
+ || memory.buffer.byteLength !== ES256_MEMORY_BYTES
183
+ ) return undefined;
184
+ const totalLength = align16(192 + normalized.data.byteLength);
185
+ if (totalLength > ES256_FRAME_CAPACITY) return undefined;
186
+ const frame = new Uint8Array(
187
+ memory.buffer,
188
+ ES256_FRAME_POINTER,
189
+ ES256_FRAME_CAPACITY
190
+ );
191
+ frame.fill(0);
192
+ const words = new DataView(
193
+ memory.buffer,
194
+ ES256_FRAME_POINTER,
195
+ ES256_FRAME_HEADER_BYTES
196
+ );
197
+ const fields = [
198
+ [0, ES256_FRAME_MAGIC],
199
+ [4, 2],
200
+ [8, ES256_FRAME_HEADER_BYTES],
201
+ [12, totalLength],
202
+ [16, 1],
203
+ [20, 0],
204
+ [24, 192],
205
+ [28, normalized.data.byteLength],
206
+ [32, 64],
207
+ [36, ES256_KEY_BYTES],
208
+ [40, 128],
209
+ [44, ES256_SIGNATURE_BYTES]
210
+ ];
211
+ for (const [offset, value] of fields) words.setUint32(offset, value, true);
212
+ frame.set(normalized.keyBytes, 64);
213
+ frame.set(normalized.signature, 128);
214
+ frame.set(normalized.data, 192);
215
+ return Object.freeze({
216
+ pointer: ES256_FRAME_POINTER,
217
+ capacity: ES256_FRAME_CAPACITY,
218
+ wipe() {
219
+ new Uint8Array(
220
+ memory.buffer,
221
+ ES256_FRAME_POINTER,
222
+ ES256_FRAME_CAPACITY
223
+ ).fill(0);
224
+ }
225
+ });
226
+ }
227
+
228
+ function createNativeGuestSourceCryptoVerifier(input = {}) {
229
+ const plan = input.plan;
230
+ const moduleExports = input.exports;
231
+ const selected = selectedHs256(plan);
232
+ const selectedSignature = selectedEs256(plan);
233
+ const memory = moduleExports && moduleExports.memory;
234
+ const verifyHs256 = moduleExports && moduleExports[HS256_EXPORT];
235
+ const verifyEs256 = moduleExports && moduleExports[ES256_EXPORT];
236
+ const hs256Available = Boolean(
237
+ selected
238
+ && memory instanceof WebAssembly.Memory
239
+ && typeof verifyHs256 === 'function'
240
+ );
241
+ const es256Available = Boolean(
242
+ selectedSignature
243
+ && memory instanceof WebAssembly.Memory
244
+ && memory.buffer.byteLength === ES256_MEMORY_BYTES
245
+ && typeof verifyEs256 === 'function'
246
+ );
247
+ const selectedRealizations = Object.freeze([
248
+ ...(selected ? [Object.freeze({
249
+ algorithm: 'HS256',
250
+ realization: HS256_REALIZATION,
251
+ implementation: CRYPTO_GUEST_SOURCE_IMPLEMENTATION,
252
+ guestUnitRequired: false,
253
+ available: hs256Available
254
+ })] : []),
255
+ ...(selectedSignature ? [Object.freeze({
256
+ algorithm: 'ES256',
257
+ realization: ES256_REALIZATION,
258
+ implementation: CRYPTO_ES256_GUEST_LINKED_IMPLEMENTATION,
259
+ guestUnitRequired: true,
260
+ available: es256Available
261
+ })] : [])
262
+ ]);
263
+ const realization = Object.freeze({
264
+ version: NATIVE_CRYPTO_VERIFIER_VERSION,
265
+ semanticOwner: '@pulse-compute/crypto',
266
+ automaticFallback: false,
267
+ algorithms: selectedRealizations,
268
+ available: selectedRealizations.length > 0
269
+ && selectedRealizations.every((entry) => entry.available),
270
+ ...(selectedRealizations.length === 1 ? selectedRealizations[0] : {})
271
+ });
272
+
273
+ const verifier = Object.freeze({
274
+ mac: Object.freeze({
275
+ verify(request) {
276
+ const normalized = normalizeRequest(request);
277
+ if (normalized.result) return normalized.result;
278
+ if (!hs256Available) return result('realization-failure');
279
+ let staged;
280
+ try {
281
+ staged = stage(memory, [
282
+ normalized.keyBytes,
283
+ normalized.data,
284
+ normalized.tag
285
+ ]);
286
+ if (!staged) return result('realization-failure');
287
+ const [keyPointer, dataPointer, tagPointer] = staged.pointers;
288
+ const code = verifyHs256(
289
+ keyPointer,
290
+ normalized.keyBytes.byteLength,
291
+ dataPointer,
292
+ normalized.data.byteLength,
293
+ tagPointer,
294
+ normalized.tag.byteLength
295
+ );
296
+ return result(RESULT_BY_CODE[String(code)] || 'realization-failure');
297
+ } catch {
298
+ return result('realization-failure');
299
+ } finally {
300
+ if (staged) staged.wipe();
301
+ }
302
+ }
303
+ }),
304
+ signature: Object.freeze({
305
+ verify(request) {
306
+ const normalized = normalizeSignatureRequest(request);
307
+ if (normalized.result) return normalized.result;
308
+ if (!es256Available) return result('realization-failure');
309
+ let staged;
310
+ try {
311
+ staged = stageEs256Frame(memory, normalized);
312
+ if (!staged) return result('realization-failure');
313
+ const code = verifyEs256(staged.pointer, staged.capacity);
314
+ return result(RESULT_BY_CODE[String(code)] || 'realization-failure');
315
+ } catch {
316
+ return result('realization-failure');
317
+ } finally {
318
+ if (staged) staged.wipe();
319
+ }
320
+ }
321
+ })
322
+ });
323
+
324
+ return Object.freeze({ verifier, realization });
325
+ }
326
+
327
+ module.exports = Object.freeze({
328
+ NATIVE_CRYPTO_VERIFIER_VERSION,
329
+ createNativeGuestSourceCryptoVerifier
330
+ });