space-data-module-sdk 0.8.4 → 0.8.5
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 +117 -20
- package/bin/space-data-module.js +263 -0
- package/docs/.nojekyll +1 -0
- package/docs/AGENTS.md +34 -0
- package/docs/architecture.svg +141 -0
- package/docs/browser-wasmedge-isomorphic.md +179 -0
- package/docs/flatsql-streaming-standard.md +341 -0
- package/docs/gpu-module-abi.md +193 -0
- package/docs/index.html +125 -0
- package/docs/isomorphic-pthreads.md +188 -0
- package/docs/isomorphic-sdn-runtime-plan.md +167 -0
- package/docs/language-runtime-matrix.md +82 -0
- package/docs/manifest-structure.svg +114 -0
- package/docs/module-bundle-runtime-plan.md +19 -0
- package/docs/module-lifecycle.svg +70 -0
- package/docs/module-publication-standard.md +540 -0
- package/docs/node-red-default-node-parity.md +88 -0
- package/docs/protocol-installation.md +196 -0
- package/docs/secrets-capability.md +152 -0
- package/docs/styles.css +288 -0
- package/docs/testing-harness.md +408 -0
- package/docs/tri-runtime-parity.md +132 -0
- package/docs/wallet-callback.html +2 -0
- package/package.json +25 -6
- package/schemas/HttpRequestAbi.fbs +41 -0
- package/schemas/HttpResponseAbi.fbs +36 -0
- package/schemas/PluginManifest.fbs +2 -1
- package/src/AGENTS.md +1 -1
- package/src/app/codec.js +548 -0
- package/src/app/index.js +6 -0
- package/src/browser.js +5 -0
- package/src/bundle/constants.js +1 -0
- package/src/bundle/index.js +15 -0
- package/src/bundle/sigdomain.js +179 -0
- package/src/bundle/signing.js +767 -0
- package/src/bundle/wasm.js +3 -2
- package/src/capabilities.js +3 -0
- package/src/compat/index.js +7 -0
- package/src/compat/sdnLegacy.js +449 -0
- package/src/compiler/compileModule.js +556 -55
- package/src/compiler/flatcSupport.js +41 -27
- package/src/compiler/index.d.ts +2 -0
- package/src/compiler/index.js +18 -0
- package/src/compiler/invokeGlue.js +1212 -183
- package/src/compiler/pthreadArtifactGuard.js +910 -0
- package/src/compiler/sdnShimGenerator.js +323 -0
- package/src/compiler/wasiThreadsToolchain.js +150 -0
- package/src/compliance/index.js +11 -1
- package/src/compliance/legacyWildcardPorts.js +228 -0
- package/src/compliance/pluginCompliance.js +1000 -96
- package/src/field-stream/index.d.ts +31 -0
- package/src/field-stream/index.js +119 -0
- package/src/flow/dependencyStreamBridge.js +234 -0
- package/src/flow/flatsqlLinkShim.js +304 -0
- package/src/flow/flowCodec.js +107 -0
- package/src/flow/flowCompiler.js +2900 -0
- package/src/flow/flowRuntimeHost.js +877 -0
- package/src/flow/index.d.ts +117 -0
- package/src/flow/index.js +31 -0
- package/src/flow/isomorphicFlowHost.js +384 -0
- package/src/flow/jsFlowRuntime.js +275 -0
- package/src/flow/normalize.js +294 -0
- package/src/flow/runtime-src/flow_runtime.cpp +1699 -0
- package/src/flow/vendor/sdn-flow/MethodRegistry.js +400 -0
- package/src/flow/vendor/sdn-flow/constants.js +39 -0
- package/src/flow/vendor/sdn-flow/index.js +29 -0
- package/src/flow/vendor/sdn-flow/normalize.js +506 -0
- package/src/flow/vendor/sdn-flow/wasmCompatibility.js +189 -0
- package/src/generated/http/cpp/HttpRequestAbi_generated.h +285 -0
- package/src/generated/http/cpp/HttpResponseAbi_generated.h +192 -0
- package/src/generated/http/sdn/http/http-header.js +53 -0
- package/src/generated/http/sdn/http/http-header.ts +67 -0
- package/src/generated/http/sdn/http/http-request.js +132 -0
- package/src/generated/http/sdn/http/http-request.ts +179 -0
- package/src/generated/http/sdn/http/http-response.js +144 -0
- package/src/generated/http/sdn/http/http-response.ts +168 -0
- package/src/generated/http/sdn/http.js +11 -0
- package/src/generated/http/sdn/http.ts +4 -0
- package/src/generated/orbpro/flow/backpressure-policy.d.ts +12 -0
- package/src/generated/orbpro/flow/backpressure-policy.d.ts.map +1 -0
- package/src/generated/orbpro/flow/backpressure-policy.js +17 -0
- package/src/generated/orbpro/flow/backpressure-policy.js.map +1 -0
- package/src/generated/orbpro/flow/backpressure-policy.ts +15 -0
- package/src/generated/orbpro/flow/flow-bundle-manifest.d.ts +142 -0
- package/src/generated/orbpro/flow/flow-bundle-manifest.d.ts.map +1 -0
- package/src/generated/orbpro/flow/flow-bundle-manifest.js +304 -0
- package/src/generated/orbpro/flow/flow-bundle-manifest.js.map +1 -0
- package/src/generated/orbpro/flow/flow-bundle-manifest.ts +399 -0
- package/src/generated/orbpro/flow/flow-edge.d.ts +113 -0
- package/src/generated/orbpro/flow/flow-edge.d.ts.map +1 -0
- package/src/generated/orbpro/flow/flow-edge.js +221 -0
- package/src/generated/orbpro/flow/flow-edge.js.map +1 -0
- package/src/generated/orbpro/flow/flow-edge.ts +292 -0
- package/src/generated/orbpro/flow/flow-node.d.ts +80 -0
- package/src/generated/orbpro/flow/flow-node.d.ts.map +1 -0
- package/src/generated/orbpro/flow/flow-node.js +158 -0
- package/src/generated/orbpro/flow/flow-node.js.map +1 -0
- package/src/generated/orbpro/flow/flow-node.ts +202 -0
- package/src/generated/orbpro/flow/flow-program.d.ts +164 -0
- package/src/generated/orbpro/flow/flow-program.d.ts.map +1 -0
- package/src/generated/orbpro/flow/flow-program.js +350 -0
- package/src/generated/orbpro/flow/flow-program.js.map +1 -0
- package/src/generated/orbpro/flow/flow-program.ts +477 -0
- package/src/generated/orbpro/flow/flow-trigger.d.ts +108 -0
- package/src/generated/orbpro/flow/flow-trigger.d.ts.map +1 -0
- package/src/generated/orbpro/flow/flow-trigger.js +207 -0
- package/src/generated/orbpro/flow/flow-trigger.js.map +1 -0
- package/src/generated/orbpro/flow/flow-trigger.ts +276 -0
- package/src/generated/orbpro/flow/node-kind.d.ts +13 -0
- package/src/generated/orbpro/flow/node-kind.d.ts.map +1 -0
- package/src/generated/orbpro/flow/node-kind.js +16 -0
- package/src/generated/orbpro/flow/node-kind.js.map +1 -0
- package/src/generated/orbpro/{invoke.ts → flow/node-kind.ts} +12 -2
- package/src/generated/orbpro/flow/plugin-version-binding.d.ts +52 -0
- package/src/generated/orbpro/flow/plugin-version-binding.d.ts.map +1 -0
- package/src/generated/orbpro/flow/plugin-version-binding.js +85 -0
- package/src/generated/orbpro/flow/plugin-version-binding.js.map +1 -0
- package/src/generated/orbpro/flow/plugin-version-binding.ts +121 -0
- package/src/generated/orbpro/flow/schema-hash-binding.d.ts +70 -0
- package/src/generated/orbpro/flow/schema-hash-binding.d.ts.map +1 -0
- package/src/generated/orbpro/flow/schema-hash-binding.js +143 -0
- package/src/generated/orbpro/flow/schema-hash-binding.js.map +1 -0
- package/src/generated/orbpro/flow/schema-hash-binding.ts +189 -0
- package/src/generated/orbpro/flow/trigger-binding.d.ts +71 -0
- package/src/generated/orbpro/flow/trigger-binding.d.ts.map +1 -0
- package/src/generated/orbpro/flow/trigger-binding.js +142 -0
- package/src/generated/orbpro/flow/trigger-binding.js.map +1 -0
- package/src/generated/orbpro/flow/trigger-binding.ts +181 -0
- package/src/generated/orbpro/flow/trigger-kind.d.ts +14 -0
- package/src/generated/orbpro/flow/trigger-kind.d.ts.map +1 -0
- package/src/generated/orbpro/flow/trigger-kind.js +17 -0
- package/src/generated/orbpro/flow/trigger-kind.js.map +1 -0
- package/src/generated/orbpro/flow/trigger-kind.ts +17 -0
- package/src/generated/orbpro/flow.d.ts +21 -0
- package/src/generated/orbpro/flow.d.ts.map +1 -0
- package/src/generated/orbpro/flow.js +23 -0
- package/src/generated/orbpro/flow.js.map +1 -0
- package/src/generated/orbpro/flow.ts +24 -0
- package/src/generated/orbpro/manifest/capability-kind.d.ts +2 -1
- package/src/generated/orbpro/manifest/capability-kind.js +1 -0
- package/src/generated/orbpro/manifest/capability-kind.ts +1 -0
- package/src/generated/orbpro/plugin/stream-invoke-request.d.ts +93 -0
- package/src/generated/orbpro/plugin/stream-invoke-request.js +160 -0
- package/src/generated/orbpro/plugin/stream-invoke-request.ts +211 -0
- package/src/generated/orbpro/plugin/stream-invoke-response.d.ts +99 -0
- package/src/generated/orbpro/plugin/stream-invoke-response.js +175 -0
- package/src/generated/orbpro/plugin/stream-invoke-response.ts +226 -0
- package/src/generated/orbpro/plugin.js +8 -0
- package/src/generated/orbpro/stream.d.ts +12 -0
- package/src/generated/orbpro/stream.js +14 -0
- package/src/generated/orbpro/stream.ts +15 -0
- package/src/generated/spacedatastandards/plg/EntryFunction.d.ts +52 -0
- package/src/generated/spacedatastandards/plg/EntryFunction.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/EntryFunction.js +106 -0
- package/src/generated/spacedatastandards/plg/EntryFunction.ts +162 -0
- package/src/generated/spacedatastandards/plg/FlatBufferTypeRef.d.ts +90 -0
- package/src/generated/spacedatastandards/plg/FlatBufferTypeRef.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/FlatBufferTypeRef.js +189 -0
- package/src/generated/spacedatastandards/plg/FlatBufferTypeRef.ts +270 -0
- package/src/generated/spacedatastandards/plg/PLG.d.ts +492 -0
- package/src/generated/spacedatastandards/plg/PLG.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLG.js +1139 -0
- package/src/generated/spacedatastandards/plg/PLG.ts +1600 -0
- package/src/generated/spacedatastandards/plg/PLGAcceptedTypeSet.d.ts +63 -0
- package/src/generated/spacedatastandards/plg/PLGAcceptedTypeSet.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLGAcceptedTypeSet.js +138 -0
- package/src/generated/spacedatastandards/plg/PLGAcceptedTypeSet.ts +189 -0
- package/src/generated/spacedatastandards/plg/PLGBuildArtifact.d.ts +41 -0
- package/src/generated/spacedatastandards/plg/PLGBuildArtifact.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLGBuildArtifact.js +104 -0
- package/src/generated/spacedatastandards/plg/PLGBuildArtifact.ts +151 -0
- package/src/generated/spacedatastandards/plg/PLGFlowEdge.d.ts +68 -0
- package/src/generated/spacedatastandards/plg/PLGFlowEdge.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLGFlowEdge.js +124 -0
- package/src/generated/spacedatastandards/plg/PLGFlowEdge.ts +184 -0
- package/src/generated/spacedatastandards/plg/PLGFlowEdgeContract.d.ts +58 -0
- package/src/generated/spacedatastandards/plg/PLGFlowEdgeContract.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLGFlowEdgeContract.js +126 -0
- package/src/generated/spacedatastandards/plg/PLGFlowEdgeContract.ts +161 -0
- package/src/generated/spacedatastandards/plg/PLGFlowNode.d.ts +82 -0
- package/src/generated/spacedatastandards/plg/PLGFlowNode.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLGFlowNode.js +164 -0
- package/src/generated/spacedatastandards/plg/PLGFlowNode.ts +244 -0
- package/src/generated/spacedatastandards/plg/PLGFlowTrigger.d.ts +55 -0
- package/src/generated/spacedatastandards/plg/PLGFlowTrigger.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLGFlowTrigger.js +105 -0
- package/src/generated/spacedatastandards/plg/PLGFlowTrigger.ts +162 -0
- package/src/generated/spacedatastandards/plg/PLGFlowTriggerBinding.d.ts +42 -0
- package/src/generated/spacedatastandards/plg/PLGFlowTriggerBinding.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLGFlowTriggerBinding.js +83 -0
- package/src/generated/spacedatastandards/plg/PLGFlowTriggerBinding.ts +127 -0
- package/src/generated/spacedatastandards/plg/PLGHostCapability.d.ts +36 -0
- package/src/generated/spacedatastandards/plg/PLGHostCapability.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLGHostCapability.js +90 -0
- package/src/generated/spacedatastandards/plg/PLGHostCapability.ts +127 -0
- package/src/generated/spacedatastandards/plg/PLGMethodManifest.d.ts +53 -0
- package/src/generated/spacedatastandards/plg/PLGMethodManifest.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLGMethodManifest.js +153 -0
- package/src/generated/spacedatastandards/plg/PLGMethodManifest.ts +210 -0
- package/src/generated/spacedatastandards/plg/PLGPortManifest.d.ts +70 -0
- package/src/generated/spacedatastandards/plg/PLGPortManifest.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLGPortManifest.js +149 -0
- package/src/generated/spacedatastandards/plg/PLGPortManifest.ts +212 -0
- package/src/generated/spacedatastandards/plg/PLGProtocolSpec.d.ts +73 -0
- package/src/generated/spacedatastandards/plg/PLGProtocolSpec.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLGProtocolSpec.js +199 -0
- package/src/generated/spacedatastandards/plg/PLGProtocolSpec.ts +292 -0
- package/src/generated/spacedatastandards/plg/PLGTimerSpec.d.ts +40 -0
- package/src/generated/spacedatastandards/plg/PLGTimerSpec.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PLGTimerSpec.js +103 -0
- package/src/generated/spacedatastandards/plg/PLGTimerSpec.ts +148 -0
- package/src/generated/spacedatastandards/plg/PluginCapability.d.ts +41 -0
- package/src/generated/spacedatastandards/plg/PluginCapability.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PluginCapability.js +82 -0
- package/src/generated/spacedatastandards/plg/{plugin-capability.ts → PluginCapability.ts} +55 -18
- package/src/generated/spacedatastandards/plg/PluginDependency.d.ts +42 -0
- package/src/generated/spacedatastandards/plg/PluginDependency.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/PluginDependency.js +80 -0
- package/src/generated/spacedatastandards/plg/PluginDependency.ts +124 -0
- package/src/generated/spacedatastandards/plg/TAB.d.ts +85 -0
- package/src/generated/spacedatastandards/plg/TAB.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/TAB.js +173 -0
- package/src/generated/spacedatastandards/plg/TAB.ts +228 -0
- package/src/generated/spacedatastandards/plg/bufferMutability.d.ts +18 -0
- package/src/generated/spacedatastandards/plg/bufferMutability.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/bufferMutability.js +20 -0
- package/src/generated/spacedatastandards/plg/bufferMutability.ts +23 -0
- package/src/generated/spacedatastandards/plg/bufferOwnership.d.ts +18 -0
- package/src/generated/spacedatastandards/plg/bufferOwnership.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/bufferOwnership.js +20 -0
- package/src/generated/spacedatastandards/plg/bufferOwnership.ts +23 -0
- package/src/generated/spacedatastandards/plg/drainBehavior.d.ts +20 -0
- package/src/generated/spacedatastandards/plg/drainBehavior.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/drainBehavior.js +22 -0
- package/src/generated/spacedatastandards/plg/drainBehavior.ts +25 -0
- package/src/generated/spacedatastandards/plg/flowEdgeRoutePolicy.d.ts +10 -0
- package/src/generated/spacedatastandards/plg/flowEdgeRoutePolicy.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/flowEdgeRoutePolicy.js +12 -0
- package/src/generated/spacedatastandards/plg/flowEdgeRoutePolicy.ts +13 -0
- package/src/generated/spacedatastandards/plg/hostCapabilityKind.d.ts +51 -0
- package/src/generated/spacedatastandards/plg/hostCapabilityKind.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/hostCapabilityKind.js +53 -0
- package/src/generated/spacedatastandards/plg/hostCapabilityKind.ts +55 -0
- package/src/generated/spacedatastandards/plg/invokeSurfaceKind.d.ts +15 -0
- package/src/generated/spacedatastandards/plg/invokeSurfaceKind.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/invokeSurfaceKind.js +17 -0
- package/src/generated/spacedatastandards/plg/invokeSurfaceKind.ts +19 -0
- package/src/generated/spacedatastandards/plg/main.d.ts +29 -0
- package/src/generated/spacedatastandards/plg/main.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/main.js +30 -0
- package/src/generated/spacedatastandards/plg/main.ts +32 -0
- package/src/generated/spacedatastandards/plg/payloadWireFormat.d.ts +17 -0
- package/src/generated/spacedatastandards/plg/payloadWireFormat.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/payloadWireFormat.js +19 -0
- package/src/generated/spacedatastandards/plg/payloadWireFormat.ts +21 -0
- package/src/generated/spacedatastandards/plg/pluginCategory.d.ts +82 -0
- package/src/generated/spacedatastandards/plg/pluginCategory.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/pluginCategory.js +84 -0
- package/src/generated/spacedatastandards/plg/pluginCategory.ts +103 -0
- package/src/generated/spacedatastandards/plg/publicationState.d.ts +18 -0
- package/src/generated/spacedatastandards/plg/publicationState.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/publicationState.js +20 -0
- package/src/generated/spacedatastandards/plg/purchaseTier.d.ts +18 -0
- package/src/generated/spacedatastandards/plg/purchaseTier.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/purchaseTier.js +20 -0
- package/src/host/AGENTS.md +2 -2
- package/src/host/abi.js +53 -66
- package/src/host/browserHost.js +355 -8
- package/src/host/cpp/keyslotClient.hpp +231 -0
- package/src/host/cpp/secretsClient.hpp +583 -0
- package/src/host/hostcallWire.js +183 -0
- package/src/host/index.js +2 -0
- package/src/host/isomorphicLoader.js +44 -7
- package/src/host/nodeHost.js +227 -8
- package/src/host/sabHostcallChannel.js +206 -0
- package/src/host/timerDriver.js +203 -0
- package/src/host/wasiShim.js +41 -1
- package/src/host/wasiThreadBrowserWorker.mjs +48 -0
- package/src/host/wasiThreadHost.js +393 -0
- package/src/host/wasiThreadWorker.mjs +32 -0
- package/src/host/wasiThreadWorkerRuntime.js +93 -0
- package/src/http/index.js +339 -0
- package/src/index.d.ts +716 -10
- package/src/index.js +4 -0
- package/src/invoke/codec.js +1202 -128
- package/src/invoke/index.js +5 -1
- package/src/licensing/index.d.ts +2 -0
- package/src/licensing/index.js +2 -0
- package/src/licensing/records.js +287 -8
- package/src/manifest/codec.js +62 -14
- package/src/manifest/index.js +2 -0
- package/src/manifest/legacyToPlg.js +164 -25
- package/src/manifest/normalize.js +1 -0
- package/src/manifest/plgCodec.js +1303 -62
- package/src/manifest/typeRefs.js +162 -24
- package/src/runtime/compiledRuntimeAbi.js +47 -0
- package/src/runtime/compiledRuntimeAbi.json +33 -0
- package/src/runtime/index.d.ts +32 -0
- package/src/runtime/index.js +1 -0
- package/src/runtime-host/index.js +3 -0
- package/src/runtime-host/methodRegistry.js +55 -0
- package/src/standards/index.js +200 -56
- package/src/standards/sharedCatalog.js +16 -0
- package/src/testing/browserModuleHarness.js +604 -31
- package/src/testing/docker/wasmedge-parity.Dockerfile +39 -0
- package/src/testing/index.d.ts +151 -0
- package/src/testing/index.js +21 -0
- package/src/testing/native/wasmedge_emscripten_pthread_runner.c +25 -1
- package/src/testing/parityBrowserRunner.js +138 -0
- package/src/testing/parityHarness.js +616 -0
- package/src/testing/parityLanes.js +537 -0
- package/src/testing/processInvoke.js +29 -21
- package/src/testing/streamInvokeCodec.js +10 -175
- package/src/testing/wasmedgePin.json +6 -0
- package/src/testing/workerModuleHarness.js +242 -0
- package/src/testing/workerModuleHarnessWorker.js +176 -0
- package/src/transport/index.js +3 -0
- package/src/transport/pki.js +299 -11
- package/src/transport/records.js +16 -7
- package/src/utils/ecdsaDer.js +106 -0
- package/src/utils/wasmCrypto.js +0 -43
- package/templates/gpu-module/README.md +48 -0
- package/templates/gpu-module/include/space_data_gpu_abi.h +122 -0
- package/templates/gpu-module/manifest.json +84 -0
- package/schemas/PluginInvokeRequest.fbs +0 -18
- package/schemas/PluginInvokeResponse.fbs +0 -30
- package/schemas/spacedatastandards/PLG.fbs +0 -191
- package/src/generated/orbpro/invoke/plugin-invoke-request.d.ts +0 -51
- package/src/generated/orbpro/invoke/plugin-invoke-request.d.ts.map +0 -1
- package/src/generated/orbpro/invoke/plugin-invoke-request.js +0 -131
- package/src/generated/orbpro/invoke/plugin-invoke-request.js.map +0 -1
- package/src/generated/orbpro/invoke/plugin-invoke-request.ts +0 -173
- package/src/generated/orbpro/invoke/plugin-invoke-response.d.ts +0 -76
- package/src/generated/orbpro/invoke/plugin-invoke-response.d.ts.map +0 -1
- package/src/generated/orbpro/invoke/plugin-invoke-response.js +0 -184
- package/src/generated/orbpro/invoke/plugin-invoke-response.js.map +0 -1
- package/src/generated/orbpro/invoke/plugin-invoke-response.ts +0 -243
- package/src/generated/orbpro/invoke.d.ts +0 -3
- package/src/generated/orbpro/invoke.js +0 -5
- package/src/generated/spacedatastandards/plg/entry-function.js +0 -86
- package/src/generated/spacedatastandards/plg/entry-function.ts +0 -119
- package/src/generated/spacedatastandards/plg/index.js +0 -16
- package/src/generated/spacedatastandards/plg/index.ts +0 -11
- package/src/generated/spacedatastandards/plg/plg.js +0 -657
- package/src/generated/spacedatastandards/plg/plg.ts +0 -924
- package/src/generated/spacedatastandards/plg/plugin-capability.js +0 -66
- package/src/generated/spacedatastandards/plg/plugin-category.js +0 -15
- package/src/generated/spacedatastandards/plg/plugin-category.ts +0 -53
- package/src/generated/spacedatastandards/plg/plugin-dependency.js +0 -63
- package/src/generated/spacedatastandards/plg/plugin-dependency.ts +0 -86
- package/src/generated/spacedatastandards/plg/publication-state.js +0 -9
- package/src/generated/spacedatastandards/plg/purchase-tier.js +0 -9
- /package/src/generated/spacedatastandards/plg/{publication-state.ts → publicationState.ts} +0 -0
- /package/src/generated/spacedatastandards/plg/{purchase-tier.ts → purchaseTier.ts} +0 -0
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computeCanonicalModuleHash,
|
|
3
|
+
createSingleFileBundle,
|
|
4
|
+
getWasmCustomSections,
|
|
5
|
+
parseSingleFileBundle,
|
|
6
|
+
} from "./wasm.js";
|
|
7
|
+
import { SDS_MANIFEST_SECTION_NAME } from "./constants.js";
|
|
8
|
+
import {
|
|
9
|
+
extractPublicationRecordCollection,
|
|
10
|
+
} from "../transport/records.js";
|
|
11
|
+
import {
|
|
12
|
+
ed25519PublicKey,
|
|
13
|
+
ed25519Sign,
|
|
14
|
+
ed25519Verify,
|
|
15
|
+
} from "../utils/wasmCrypto.js";
|
|
16
|
+
import { sha256Bytes } from "../utils/crypto.js";
|
|
17
|
+
import {
|
|
18
|
+
DOMAIN_MODULE_PUBLICATION_V1,
|
|
19
|
+
statement as buildSignatureStatement,
|
|
20
|
+
trimGoWhitespace,
|
|
21
|
+
} from "./sigdomain.js";
|
|
22
|
+
import { ModuleBundleEntryRole } from "spacedatastandards.org/lib/js/MBL/main.js";
|
|
23
|
+
|
|
24
|
+
export const MODULE_SIGNATURE_ALGORITHM = "ed25519";
|
|
25
|
+
export const MODULE_SIGNATURE_ENTRY_ROLE = "signature";
|
|
26
|
+
export const LEGACY_MODULE_SIGNATURE_HASH_ALGORITHM =
|
|
27
|
+
"sha256-canonical-module-hash";
|
|
28
|
+
export const BUNDLE_SIGNATURE_HASH_ALGORITHM =
|
|
29
|
+
"sha256-sdn-module-bundle-v1";
|
|
30
|
+
|
|
31
|
+
export class ModuleSignatureError extends Error {
|
|
32
|
+
constructor(code, message) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "ModuleSignatureError";
|
|
35
|
+
this.code = code;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function bytesToHex(bytes) {
|
|
40
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function hexToBytes(hex) {
|
|
44
|
+
const normalized = String(hex ?? "").trim().toLowerCase();
|
|
45
|
+
if (!/^[0-9a-f]*$/.test(normalized) || normalized.length % 2 !== 0) {
|
|
46
|
+
throw new ModuleSignatureError(
|
|
47
|
+
"invalid_hex",
|
|
48
|
+
"signature material must be even-length hex",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const out = new Uint8Array(normalized.length / 2);
|
|
52
|
+
for (let i = 0; i < out.length; i += 1) {
|
|
53
|
+
out[i] = Number.parseInt(normalized.slice(i * 2, i * 2 + 2), 16);
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeTrustedPublicKeys(trustedPublicKeys) {
|
|
59
|
+
const list = Array.isArray(trustedPublicKeys)
|
|
60
|
+
? trustedPublicKeys
|
|
61
|
+
: typeof trustedPublicKeys === "string"
|
|
62
|
+
? trustedPublicKeys.split(",")
|
|
63
|
+
: [];
|
|
64
|
+
return list
|
|
65
|
+
.map((key) => String(key ?? "").trim().toLowerCase())
|
|
66
|
+
.filter((key) => key.length === 64);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function findSignatureEntry(bundle) {
|
|
70
|
+
for (const entry of bundle?.entries ?? []) {
|
|
71
|
+
const role =
|
|
72
|
+
typeof entry.role === "string" ? entry.role.toLowerCase() : entry.role;
|
|
73
|
+
if (
|
|
74
|
+
role === MODULE_SIGNATURE_ENTRY_ROLE ||
|
|
75
|
+
role === ModuleBundleEntryRole.SIGNATURE ||
|
|
76
|
+
entry.entryId === "signature" ||
|
|
77
|
+
entry.sectionName === "sds.signature"
|
|
78
|
+
) {
|
|
79
|
+
return entry;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The exact key spellings the SDK writer emits. Go's encoding/json matches
|
|
86
|
+
// field names CASE-INSENSITIVELY and JavaScript's property access does not, so
|
|
87
|
+
// a trailer spelling "StatementDomain" populates the node's struct and is
|
|
88
|
+
// invisible here — the node would take the domain path and verify while this
|
|
89
|
+
// verifier fell to the legacy path and refused. Same artifact, opposite
|
|
90
|
+
// verdicts. Both sides therefore refuse a case variant of a known key outright.
|
|
91
|
+
// Keys that are not a variant of anything known stay tolerated on both sides:
|
|
92
|
+
// this closes a divergence, it is not a new schema.
|
|
93
|
+
const SIGNATURE_PAYLOAD_KEYS = Object.freeze([
|
|
94
|
+
"algorithm",
|
|
95
|
+
"keyId",
|
|
96
|
+
"publicKeyHex",
|
|
97
|
+
"signatureHex",
|
|
98
|
+
"signedHashHex",
|
|
99
|
+
"signedHashAlgorithm",
|
|
100
|
+
"statementDomain",
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
function decodeSignaturePayload(entry) {
|
|
104
|
+
let parsed;
|
|
105
|
+
try {
|
|
106
|
+
const payload = entry.payload ?? [];
|
|
107
|
+
const text = new TextDecoder().decode(new Uint8Array(payload));
|
|
108
|
+
parsed = JSON.parse(text);
|
|
109
|
+
} catch {
|
|
110
|
+
throw new ModuleSignatureError(
|
|
111
|
+
"invalid_signature_payload",
|
|
112
|
+
"module signature entry payload is not valid JSON",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
116
|
+
throw new ModuleSignatureError(
|
|
117
|
+
"invalid_signature_payload",
|
|
118
|
+
"module signature entry payload is not a JSON object",
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
for (const key of Object.keys(parsed)) {
|
|
122
|
+
if (SIGNATURE_PAYLOAD_KEYS.includes(key)) continue;
|
|
123
|
+
const variant = SIGNATURE_PAYLOAD_KEYS.find(
|
|
124
|
+
(known) => known.toLowerCase() === key.toLowerCase(),
|
|
125
|
+
);
|
|
126
|
+
if (variant) {
|
|
127
|
+
throw new ModuleSignatureError(
|
|
128
|
+
"invalid_signature_payload",
|
|
129
|
+
`module signature entry payload spells "${key}"; the wire form is the module SDK's camelCase JSON and the correct spelling is "${variant}"`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return parsed;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function equalBytes(left, right) {
|
|
137
|
+
const a = new Uint8Array(left ?? []);
|
|
138
|
+
const b = new Uint8Array(right ?? []);
|
|
139
|
+
return a.length === b.length && a.every((byte, index) => byte === b[index]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The canonical module hash, or null if this artifact's wasm cannot be walked.
|
|
144
|
+
* Used ONLY to populate a reporting field on the node-signed path, where the
|
|
145
|
+
* node itself never parses the wasm: a module whose sections this SDK cannot
|
|
146
|
+
* walk must not become an artifact the node admits and the SDK refuses.
|
|
147
|
+
*/
|
|
148
|
+
async function bestEffortCanonicalModuleHashHex(payloadBytes) {
|
|
149
|
+
try {
|
|
150
|
+
return (await computeCanonicalModuleHash(payloadBytes)).hashHex;
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const bundleStatementTextEncoder = new TextEncoder();
|
|
157
|
+
|
|
158
|
+
function compareUtf8Keys(left, right) {
|
|
159
|
+
const leftBytes = bundleStatementTextEncoder.encode(left);
|
|
160
|
+
const rightBytes = bundleStatementTextEncoder.encode(right);
|
|
161
|
+
const length = Math.min(leftBytes.length, rightBytes.length);
|
|
162
|
+
for (let index = 0; index < length; index += 1) {
|
|
163
|
+
if (leftBytes[index] !== rightBytes[index]) {
|
|
164
|
+
return leftBytes[index] - rightBytes[index];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return leftBytes.length - rightBytes.length;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function normalizeBundleStatementValue(value) {
|
|
171
|
+
if (value === undefined) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
if (value === null || typeof value !== "object") {
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
if (Array.isArray(value)) {
|
|
178
|
+
return value.map((item) => {
|
|
179
|
+
const normalized = normalizeBundleStatementValue(item);
|
|
180
|
+
return normalized === undefined ? null : normalized;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return Object.fromEntries(
|
|
184
|
+
Object.entries(value)
|
|
185
|
+
.filter(([, nestedValue]) => nestedValue !== undefined)
|
|
186
|
+
.sort(([left], [right]) => compareUtf8Keys(left, right))
|
|
187
|
+
.map(([key, nestedValue]) => [
|
|
188
|
+
key,
|
|
189
|
+
normalizeBundleStatementValue(nestedValue),
|
|
190
|
+
]),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function canonicalBundleStatementBytes(statement) {
|
|
195
|
+
// Go's encoding/json orders map keys by their UTF-8 bytes and escapes these
|
|
196
|
+
// five code points by default. Match that host-side representation exactly
|
|
197
|
+
// so a bundle digest is independent of JavaScript's locale configuration.
|
|
198
|
+
const json = JSON.stringify(normalizeBundleStatementValue(statement)).replace(
|
|
199
|
+
/[<>&\u2028\u2029]/g,
|
|
200
|
+
(character) =>
|
|
201
|
+
`\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`,
|
|
202
|
+
);
|
|
203
|
+
return bundleStatementTextEncoder.encode(json);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function normalizedBundleEntryForSignature(entry) {
|
|
207
|
+
const payload = new Uint8Array(entry.payload ?? []);
|
|
208
|
+
return {
|
|
209
|
+
entryId: entry.entryId ?? null,
|
|
210
|
+
role: Number(entry.role ?? ModuleBundleEntryRole.AUXILIARY),
|
|
211
|
+
sectionName: entry.sectionName ?? null,
|
|
212
|
+
typeRef: entry.typeRef ?? null,
|
|
213
|
+
payloadEncoding: Number(entry.payloadEncoding ?? 0),
|
|
214
|
+
mediaType: entry.mediaType ?? null,
|
|
215
|
+
flags: Number(entry.flags ?? 0),
|
|
216
|
+
sha256Hex: bytesToHex(new Uint8Array(entry.sha256 ?? [])),
|
|
217
|
+
payloadLength: payload.length,
|
|
218
|
+
description: entry.description ?? null,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Compute the v1 whole-bundle signing digest. Every non-signature entry's
|
|
224
|
+
* payload hash is recomputed before the canonical statement is hashed; an
|
|
225
|
+
* attacker cannot make a modified payload self-consistent merely by changing
|
|
226
|
+
* its MBL sha256 field. Bundle metadata and the portable module hash are bound
|
|
227
|
+
* by the same statement.
|
|
228
|
+
*/
|
|
229
|
+
export async function computeModuleBundleSignatureHash(bundle, options = {}) {
|
|
230
|
+
if (!bundle || typeof bundle !== "object") {
|
|
231
|
+
throw new ModuleSignatureError(
|
|
232
|
+
"invalid_bundle",
|
|
233
|
+
"module bundle is missing or malformed",
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const entries = (bundle.entries ?? []).filter(
|
|
237
|
+
(entry) => !findSignatureEntry({ entries: [entry] }),
|
|
238
|
+
);
|
|
239
|
+
const seen = new Set();
|
|
240
|
+
for (const entry of entries) {
|
|
241
|
+
const entryId = String(entry.entryId ?? "");
|
|
242
|
+
if (!entryId) {
|
|
243
|
+
throw new ModuleSignatureError(
|
|
244
|
+
"invalid_bundle",
|
|
245
|
+
"module bundle contains an entry without an entryId",
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (seen.has(entryId)) {
|
|
249
|
+
throw new ModuleSignatureError(
|
|
250
|
+
"invalid_bundle",
|
|
251
|
+
`module bundle contains duplicate entryId ${JSON.stringify(entryId)}`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
seen.add(entryId);
|
|
255
|
+
const payloadHash = await sha256Bytes(new Uint8Array(entry.payload ?? []));
|
|
256
|
+
if (!equalBytes(payloadHash, entry.sha256)) {
|
|
257
|
+
throw new ModuleSignatureError(
|
|
258
|
+
"hash_mismatch",
|
|
259
|
+
`module bundle entry ${JSON.stringify(entryId)} payload hash does not match its recorded sha256`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const recordedModuleHash = new Uint8Array(bundle.canonicalModuleHash ?? []);
|
|
265
|
+
if (recordedModuleHash.length !== 32) {
|
|
266
|
+
throw new ModuleSignatureError(
|
|
267
|
+
"invalid_bundle",
|
|
268
|
+
"module bundle canonicalModuleHash must be 32 bytes",
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (options.wasmBytes) {
|
|
272
|
+
const canonical = await computeCanonicalModuleHash(options.wasmBytes, {
|
|
273
|
+
customSectionPrefix:
|
|
274
|
+
bundle.canonicalization?.strippedCustomSectionPrefix,
|
|
275
|
+
});
|
|
276
|
+
if (!equalBytes(canonical.hashBytes, recordedModuleHash)) {
|
|
277
|
+
throw new ModuleSignatureError(
|
|
278
|
+
"hash_mismatch",
|
|
279
|
+
"module canonical hash does not match the bundle's recorded hash",
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const manifestEntry = entries.find(
|
|
285
|
+
(entry) =>
|
|
286
|
+
entry.entryId === "manifest" ||
|
|
287
|
+
entry.role === ModuleBundleEntryRole.MANIFEST,
|
|
288
|
+
);
|
|
289
|
+
const recordedManifestHash = new Uint8Array(bundle.manifestHash ?? []);
|
|
290
|
+
if (manifestEntry) {
|
|
291
|
+
const manifestHash = await sha256Bytes(
|
|
292
|
+
new Uint8Array(manifestEntry.payload ?? []),
|
|
293
|
+
);
|
|
294
|
+
if (!equalBytes(manifestHash, recordedManifestHash)) {
|
|
295
|
+
throw new ModuleSignatureError(
|
|
296
|
+
"hash_mismatch",
|
|
297
|
+
"module manifest payload hash does not match the bundle's manifestHash",
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
} else if (recordedManifestHash.length !== 0) {
|
|
301
|
+
throw new ModuleSignatureError(
|
|
302
|
+
"hash_mismatch",
|
|
303
|
+
"module bundle records a manifestHash but contains no manifest entry",
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const statement = {
|
|
308
|
+
version: 1,
|
|
309
|
+
bundleVersion: Number(bundle.bundleVersion ?? 1),
|
|
310
|
+
moduleFormat: bundle.moduleFormat ?? null,
|
|
311
|
+
canonicalization: {
|
|
312
|
+
version: Number(bundle.canonicalization?.version ?? 1),
|
|
313
|
+
strippedCustomSectionPrefix:
|
|
314
|
+
bundle.canonicalization?.strippedCustomSectionPrefix ?? null,
|
|
315
|
+
bundleSectionName:
|
|
316
|
+
bundle.canonicalization?.bundleSectionName ?? null,
|
|
317
|
+
hashAlgorithm: bundle.canonicalization?.hashAlgorithm ?? null,
|
|
318
|
+
},
|
|
319
|
+
canonicalModuleHashHex: bytesToHex(recordedModuleHash),
|
|
320
|
+
manifestHashHex: bytesToHex(recordedManifestHash),
|
|
321
|
+
manifestExportSymbol: bundle.manifestExportSymbol ?? null,
|
|
322
|
+
manifestSizeSymbol: bundle.manifestSizeSymbol ?? null,
|
|
323
|
+
entries: entries
|
|
324
|
+
.map(normalizedBundleEntryForSignature)
|
|
325
|
+
.sort((left, right) => compareUtf8Keys(left.entryId, right.entryId)),
|
|
326
|
+
};
|
|
327
|
+
const hashBytes = await sha256Bytes(canonicalBundleStatementBytes(statement));
|
|
328
|
+
return {
|
|
329
|
+
statement,
|
|
330
|
+
hashBytes,
|
|
331
|
+
hashHex: bytesToHex(hashBytes),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Sign a module artifact's canonical wasm hash with an Ed25519 key and embed
|
|
337
|
+
* the detached signature in the artifact's MBL bundle (sds.signature entry).
|
|
338
|
+
*
|
|
339
|
+
* Existing bundle entries, the manifest, and any ENC/PNM publication records
|
|
340
|
+
* in the REC trailer are preserved. Any previous signature entry is replaced.
|
|
341
|
+
*
|
|
342
|
+
* @param {Uint8Array|ArrayBuffer} bytes - module artifact (raw wasm or single-file bundle)
|
|
343
|
+
* @param {Object} options
|
|
344
|
+
* @param {string} options.privateKeySeedHex - 32-byte Ed25519 seed, hex
|
|
345
|
+
* @param {string} [options.keyId] - identifier recorded alongside the signature
|
|
346
|
+
* @returns {Promise<{wasmBytes: Uint8Array, signature: Object, canonicalModuleHashHex: string}>}
|
|
347
|
+
*/
|
|
348
|
+
export async function signModuleArtifact(bytes, options = {}) {
|
|
349
|
+
const seed = hexToBytes(options.privateKeySeedHex);
|
|
350
|
+
if (seed.length !== 32) {
|
|
351
|
+
throw new ModuleSignatureError(
|
|
352
|
+
"invalid_seed",
|
|
353
|
+
"privateKeySeedHex must be a 32-byte hex Ed25519 seed",
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
const protectedArtifact = extractPublicationRecordCollection(bytes);
|
|
357
|
+
const payloadBytes = protectedArtifact?.payloadBytes ?? bytes;
|
|
358
|
+
const canonical = await computeCanonicalModuleHash(payloadBytes);
|
|
359
|
+
|
|
360
|
+
let manifestBytes;
|
|
361
|
+
let preservedEntries = [];
|
|
362
|
+
if (protectedArtifact?.mbl) {
|
|
363
|
+
const parsed = await parseSingleFileBundle(bytes);
|
|
364
|
+
preservedEntries = (parsed.bundle.entries ?? [])
|
|
365
|
+
.filter((entry) => {
|
|
366
|
+
if (findSignatureEntry({ entries: [entry] })) {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
if (
|
|
370
|
+
entry.entryId === "manifest" ||
|
|
371
|
+
entry.role === ModuleBundleEntryRole.MANIFEST
|
|
372
|
+
) {
|
|
373
|
+
manifestBytes = new Uint8Array(entry.payload ?? []);
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
return true;
|
|
377
|
+
})
|
|
378
|
+
.map((entry) => ({
|
|
379
|
+
...entry,
|
|
380
|
+
payload: new Uint8Array(entry.payload ?? []),
|
|
381
|
+
}));
|
|
382
|
+
}
|
|
383
|
+
if (!manifestBytes) {
|
|
384
|
+
manifestBytes = getWasmCustomSections(
|
|
385
|
+
payloadBytes,
|
|
386
|
+
SDS_MANIFEST_SECTION_NAME,
|
|
387
|
+
)[0];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let signedHashBytes = canonical.hashBytes;
|
|
391
|
+
let signedHashHex = canonical.hashHex;
|
|
392
|
+
let signedHashAlgorithm = LEGACY_MODULE_SIGNATURE_HASH_ALGORITHM;
|
|
393
|
+
if (options.signatureScope === "bundle") {
|
|
394
|
+
const unsigned = await createSingleFileBundle({
|
|
395
|
+
wasmBytes: bytes,
|
|
396
|
+
...(manifestBytes ? { manifestBytes } : {}),
|
|
397
|
+
entries: preservedEntries,
|
|
398
|
+
});
|
|
399
|
+
const parsedUnsigned = await parseSingleFileBundle(unsigned.wasmBytes);
|
|
400
|
+
const bundleHash = await computeModuleBundleSignatureHash(
|
|
401
|
+
parsedUnsigned.bundle,
|
|
402
|
+
{ wasmBytes: parsedUnsigned.wasmBytes },
|
|
403
|
+
);
|
|
404
|
+
signedHashBytes = bundleHash.hashBytes;
|
|
405
|
+
signedHashHex = bundleHash.hashHex;
|
|
406
|
+
signedHashAlgorithm = BUNDLE_SIGNATURE_HASH_ALGORITHM;
|
|
407
|
+
} else if (
|
|
408
|
+
options.signatureScope !== undefined &&
|
|
409
|
+
options.signatureScope !== "module"
|
|
410
|
+
) {
|
|
411
|
+
throw new ModuleSignatureError(
|
|
412
|
+
"invalid_signature_scope",
|
|
413
|
+
'signatureScope must be either "module" or "bundle"',
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const publicKey = await ed25519PublicKey(seed);
|
|
418
|
+
const signatureBytes = await ed25519Sign(signedHashBytes, seed);
|
|
419
|
+
const signature = {
|
|
420
|
+
algorithm: MODULE_SIGNATURE_ALGORITHM,
|
|
421
|
+
keyId: options.keyId ?? null,
|
|
422
|
+
publicKeyHex: bytesToHex(new Uint8Array(publicKey)),
|
|
423
|
+
signatureHex: bytesToHex(new Uint8Array(signatureBytes)),
|
|
424
|
+
signedHashHex,
|
|
425
|
+
signedHashAlgorithm,
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const rebuilt = await createSingleFileBundle({
|
|
429
|
+
wasmBytes: bytes,
|
|
430
|
+
...(manifestBytes ? { manifestBytes } : {}),
|
|
431
|
+
signature,
|
|
432
|
+
entries: preservedEntries,
|
|
433
|
+
});
|
|
434
|
+
return {
|
|
435
|
+
wasmBytes: rebuilt.wasmBytes,
|
|
436
|
+
signature,
|
|
437
|
+
canonicalModuleHashHex: rebuilt.canonicalModuleHashHex,
|
|
438
|
+
signedHashHex,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Verify a module artifact's embedded Ed25519 signature before loading.
|
|
444
|
+
*
|
|
445
|
+
* TWO SIGNED FORMS, and the artifact chooses — not the caller, not a policy:
|
|
446
|
+
*
|
|
447
|
+
* - `statementDomain` PRESENT — the NODE-SIGNED form issued by the node's
|
|
448
|
+
* content-bound signing endpoint. The signature covers the domain-separated
|
|
449
|
+
* statement `domain || 0x00 || sha256(portable)` (see ./sigdomain.js), where
|
|
450
|
+
* `portable` is the trailer-stripped payload — the exact bytes the runtime
|
|
451
|
+
* instantiates and the node's capability policy identifies by content hash.
|
|
452
|
+
* The domain must be EXACTLY {@link DOMAIN_MODULE_PUBLICATION_V1}: not
|
|
453
|
+
* merely "registered". A registered-but-different domain (the reserved
|
|
454
|
+
* update-manifest domain, say) is REFUSED, which is what stops a signature
|
|
455
|
+
* minted for a signed update from being stapled into a module trailer.
|
|
456
|
+
*
|
|
457
|
+
* - `statementDomain` ABSENT — the legacy SDK-signed form: a signature over
|
|
458
|
+
* the bare canonical module (or bundle) digest. Verification is byte-identical
|
|
459
|
+
* to what it has always been, so every artifact already published keeps
|
|
460
|
+
* verifying.
|
|
461
|
+
*
|
|
462
|
+
* THIS MIRRORS THE NODE EXACTLY, deliberately and to the reason code, because
|
|
463
|
+
* the same artifact is verified by the Go loaders
|
|
464
|
+
* (`sdn-server/internal/modulert/publication_signature.go` and its kubo twin)
|
|
465
|
+
* and by this function in the browser and under Node/WasmEdge. An artifact that
|
|
466
|
+
* verifies on the node and is refused here is a cross-runtime defect, not a
|
|
467
|
+
* platform difference — so the checks below run in the node's ORDER (what the
|
|
468
|
+
* signature covers is resolved before who signed it) and the shared vector file
|
|
469
|
+
* `test/support/statement-domain-vectors.json` pins both sides against the same
|
|
470
|
+
* bytes and the same refusal reasons.
|
|
471
|
+
*
|
|
472
|
+
* Note the deliberate omissions on the node-signed path: `signedHashAlgorithm`
|
|
473
|
+
* is not consulted and the bundle's recorded `canonicalModuleHash` is not
|
|
474
|
+
* gated on, because the node consults neither. The domain itself defines the
|
|
475
|
+
* hash basis (portable-payload SHA-256), and that basis binds the entire
|
|
476
|
+
* instantiated payload — a strictly stronger commitment than the canonical
|
|
477
|
+
* hash, which strips `sds.*` custom sections before hashing.
|
|
478
|
+
*
|
|
479
|
+
* A present-but-invalid signature always throws. A missing signature throws
|
|
480
|
+
* only when `requireSignature` is true.
|
|
481
|
+
*
|
|
482
|
+
* @param {Uint8Array|ArrayBuffer} bytes - module artifact bytes
|
|
483
|
+
* @param {Object} options
|
|
484
|
+
* @param {string[]|string} [options.trustedPublicKeys] - allowed signer public keys (hex)
|
|
485
|
+
* @param {boolean} [options.requireSignature=false]
|
|
486
|
+
* @returns {Promise<{verified: boolean, signed: boolean, keyId?: string|null, publicKeyHex?: string, canonicalModuleHashHex?: string, contentHashHex?: string, statementDomain?: string, reason?: string}>}
|
|
487
|
+
*/
|
|
488
|
+
export async function verifyModuleArtifact(bytes, options = {}) {
|
|
489
|
+
const requireSignature = options.requireSignature === true;
|
|
490
|
+
const protectedArtifact = extractPublicationRecordCollection(bytes);
|
|
491
|
+
const signatureEntry = protectedArtifact?.mbl
|
|
492
|
+
? findSignatureEntry(protectedArtifact.mbl)
|
|
493
|
+
: null;
|
|
494
|
+
|
|
495
|
+
if (!signatureEntry) {
|
|
496
|
+
if (requireSignature) {
|
|
497
|
+
throw new ModuleSignatureError(
|
|
498
|
+
"missing_signature",
|
|
499
|
+
"module artifact has no signature entry but signature is required",
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
return { verified: false, signed: false, reason: "unsigned" };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const payload = decodeSignaturePayload(signatureEntry);
|
|
506
|
+
// Trimmed and case-folded, because the node is: `strings.EqualFold(
|
|
507
|
+
// strings.TrimSpace(payload.Algorithm), "ed25519")`. An exact match here
|
|
508
|
+
// would refuse an "ED25519" the node admits.
|
|
509
|
+
if (trimGoWhitespace(payload.algorithm).toLowerCase() !== MODULE_SIGNATURE_ALGORITHM) {
|
|
510
|
+
throw new ModuleSignatureError(
|
|
511
|
+
"unsupported_algorithm",
|
|
512
|
+
`unsupported module signature algorithm: ${payload.algorithm}`,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
const signatureBytes = hexToBytes(payload.signatureHex);
|
|
516
|
+
if (signatureBytes.length !== 64) {
|
|
517
|
+
throw new ModuleSignatureError(
|
|
518
|
+
"invalid_signature",
|
|
519
|
+
"module signature must be 64 bytes",
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
if (signatureBytes.every((byte) => byte === 0)) {
|
|
523
|
+
throw new ModuleSignatureError(
|
|
524
|
+
"invalid_signature",
|
|
525
|
+
"module signature must not be all zeroes",
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
const publicKeyHex = String(payload.publicKeyHex ?? "").trim().toLowerCase();
|
|
529
|
+
const publicKeyBytes = hexToBytes(publicKeyHex);
|
|
530
|
+
if (publicKeyBytes.length !== 32) {
|
|
531
|
+
throw new ModuleSignatureError(
|
|
532
|
+
"invalid_public_key",
|
|
533
|
+
"module signer public key must be 32 bytes",
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const trusted = normalizeTrustedPublicKeys(options.trustedPublicKeys);
|
|
538
|
+
// resolvedStatementDomain is carried onto the thrown error so a refusal
|
|
539
|
+
// reports the same (reason, domain) pair the node's ModuleSignatureStatus
|
|
540
|
+
// does — including for untrusted_signer, where the domain was already
|
|
541
|
+
// resolved before trust was asked.
|
|
542
|
+
const requireTrustedSigner = (resolvedStatementDomain) => {
|
|
543
|
+
if (!trusted.includes(publicKeyHex)) {
|
|
544
|
+
const error = new ModuleSignatureError(
|
|
545
|
+
"untrusted_signer",
|
|
546
|
+
"module signer public key is not in the trusted signer set",
|
|
547
|
+
);
|
|
548
|
+
if (resolvedStatementDomain) {
|
|
549
|
+
error.statementDomain = resolvedStatementDomain;
|
|
550
|
+
}
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// ---- node-signed form: domain-separated statement over the portable hash --
|
|
556
|
+
//
|
|
557
|
+
// Resolved BEFORE the trusted-signer check, mirroring the node: a wrong
|
|
558
|
+
// statement domain is a property of the ARTIFACT and must be reported as
|
|
559
|
+
// such whether or not the signer happens to be trusted. (The shared vector
|
|
560
|
+
// `foreign-registered-domain-untrusted-signer` pins exactly this ordering.)
|
|
561
|
+
const declaredStatementDomain = trimGoWhitespace(payload.statementDomain);
|
|
562
|
+
// AN ARTIFACT DECLARES EXACTLY ONE BASIS. A statement domain says the
|
|
563
|
+
// signature covers domain||0x00||sha256(portable); the bundle hash algorithm
|
|
564
|
+
// says it covers the canonical whole-bundle statement. Different preimages,
|
|
565
|
+
// so an artifact claiming both has not said what its signature covers.
|
|
566
|
+
// Resolving it either way silently discards the other claim — the node twins
|
|
567
|
+
// resolved it bundle-first, which would have dropped the domain check and
|
|
568
|
+
// with it the cross-domain replay guard. All three verifiers refuse instead
|
|
569
|
+
// (sdn-server + kubo: reason "ambiguous_signature_scope").
|
|
570
|
+
if (
|
|
571
|
+
declaredStatementDomain !== "" &&
|
|
572
|
+
trimGoWhitespace(payload.signedHashAlgorithm) ===
|
|
573
|
+
BUNDLE_SIGNATURE_HASH_ALGORITHM
|
|
574
|
+
) {
|
|
575
|
+
const error = new ModuleSignatureError(
|
|
576
|
+
"ambiguous_signature_scope",
|
|
577
|
+
`module signature declares statement domain ${JSON.stringify(declaredStatementDomain)} AND hash algorithm ${JSON.stringify(BUNDLE_SIGNATURE_HASH_ALGORITHM)}; these are different preimages and an artifact must declare exactly one`,
|
|
578
|
+
);
|
|
579
|
+
error.statementDomain = declaredStatementDomain;
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
582
|
+
if (declaredStatementDomain !== "") {
|
|
583
|
+
const portableBytes = new Uint8Array(protectedArtifact.payloadBytes);
|
|
584
|
+
const contentHashBytes = await sha256Bytes(portableBytes);
|
|
585
|
+
const contentHashHex = bytesToHex(contentHashBytes);
|
|
586
|
+
|
|
587
|
+
const declaredHashHex = trimGoWhitespace(payload.signedHashHex).toLowerCase();
|
|
588
|
+
if (declaredHashHex !== "" && declaredHashHex !== contentHashHex) {
|
|
589
|
+
throw new ModuleSignatureError(
|
|
590
|
+
"hash_mismatch",
|
|
591
|
+
`module signature covers content hash ${declaredHashHex}, portable artifact hashes to ${contentHashHex}`,
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (declaredStatementDomain !== DOMAIN_MODULE_PUBLICATION_V1) {
|
|
596
|
+
const error = new ModuleSignatureError(
|
|
597
|
+
"unsupported_statement_domain",
|
|
598
|
+
`module signature declares statement domain ${JSON.stringify(declaredStatementDomain)}; a module artifact must be signed under ${JSON.stringify(DOMAIN_MODULE_PUBLICATION_V1)}`,
|
|
599
|
+
);
|
|
600
|
+
error.statementDomain = declaredStatementDomain;
|
|
601
|
+
throw error;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const statementBytes = buildSignatureStatement(
|
|
605
|
+
DOMAIN_MODULE_PUBLICATION_V1,
|
|
606
|
+
contentHashBytes,
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
requireTrustedSigner(DOMAIN_MODULE_PUBLICATION_V1);
|
|
610
|
+
|
|
611
|
+
const validStatement = await ed25519Verify(
|
|
612
|
+
statementBytes,
|
|
613
|
+
signatureBytes,
|
|
614
|
+
publicKeyBytes,
|
|
615
|
+
);
|
|
616
|
+
if (!validStatement) {
|
|
617
|
+
const error = new ModuleSignatureError(
|
|
618
|
+
"invalid_signature",
|
|
619
|
+
"module publication signature verification failed",
|
|
620
|
+
);
|
|
621
|
+
error.statementDomain = DOMAIN_MODULE_PUBLICATION_V1;
|
|
622
|
+
throw error;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return {
|
|
626
|
+
verified: true,
|
|
627
|
+
signed: true,
|
|
628
|
+
keyId: payload.keyId ?? null,
|
|
629
|
+
publicKeyHex,
|
|
630
|
+
statementDomain: DOMAIN_MODULE_PUBLICATION_V1,
|
|
631
|
+
contentHashHex,
|
|
632
|
+
// Reported for continuity with the legacy result shape (the CLI prints
|
|
633
|
+
// it); NOT gated on, because the node does not gate on it either.
|
|
634
|
+
canonicalModuleHashHex: await bestEffortCanonicalModuleHashHex(
|
|
635
|
+
protectedArtifact.payloadBytes,
|
|
636
|
+
),
|
|
637
|
+
signatureScope: "module",
|
|
638
|
+
signedHashHex: contentHashHex,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ---- legacy form: bare canonical module (or bundle) digest ---------------
|
|
643
|
+
const canonical = await computeCanonicalModuleHash(protectedArtifact.payloadBytes);
|
|
644
|
+
const recordedHash = new Uint8Array(
|
|
645
|
+
protectedArtifact.mbl.canonicalModuleHash ?? [],
|
|
646
|
+
);
|
|
647
|
+
if (
|
|
648
|
+
recordedHash.length !== canonical.hashBytes.length ||
|
|
649
|
+
!recordedHash.every((byte, i) => byte === canonical.hashBytes[i])
|
|
650
|
+
) {
|
|
651
|
+
throw new ModuleSignatureError(
|
|
652
|
+
"hash_mismatch",
|
|
653
|
+
"module canonical hash does not match the bundle's recorded hash",
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
let signedHashBytes = canonical.hashBytes;
|
|
658
|
+
let signedHashHex = canonical.hashHex;
|
|
659
|
+
let signatureScope = "module";
|
|
660
|
+
const signedHashAlgorithm = String(payload.signedHashAlgorithm ?? "");
|
|
661
|
+
if (signedHashAlgorithm === BUNDLE_SIGNATURE_HASH_ALGORITHM) {
|
|
662
|
+
const parsed = await parseSingleFileBundle(bytes);
|
|
663
|
+
const bundleHash = await computeModuleBundleSignatureHash(parsed.bundle, {
|
|
664
|
+
wasmBytes: parsed.wasmBytes,
|
|
665
|
+
});
|
|
666
|
+
signedHashBytes = bundleHash.hashBytes;
|
|
667
|
+
signedHashHex = bundleHash.hashHex;
|
|
668
|
+
signatureScope = "bundle";
|
|
669
|
+
} else if (
|
|
670
|
+
signedHashAlgorithm !== LEGACY_MODULE_SIGNATURE_HASH_ALGORITHM
|
|
671
|
+
) {
|
|
672
|
+
throw new ModuleSignatureError(
|
|
673
|
+
"unsupported_hash_algorithm",
|
|
674
|
+
`unsupported module signature hash algorithm: ${signedHashAlgorithm}`,
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
if (
|
|
678
|
+
String(payload.signedHashHex ?? "").toLowerCase() !== signedHashHex
|
|
679
|
+
) {
|
|
680
|
+
throw new ModuleSignatureError(
|
|
681
|
+
"hash_mismatch",
|
|
682
|
+
"module or bundle hash does not match the signed digest",
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Trust is asked LAST here too, for the same reason as above and so the two
|
|
687
|
+
// forms cannot report different reasons for the same defect.
|
|
688
|
+
requireTrustedSigner();
|
|
689
|
+
|
|
690
|
+
const valid = await ed25519Verify(
|
|
691
|
+
signedHashBytes,
|
|
692
|
+
signatureBytes,
|
|
693
|
+
publicKeyBytes,
|
|
694
|
+
);
|
|
695
|
+
if (!valid) {
|
|
696
|
+
throw new ModuleSignatureError(
|
|
697
|
+
"invalid_signature",
|
|
698
|
+
"module signature verification failed",
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
verified: true,
|
|
703
|
+
signed: true,
|
|
704
|
+
keyId: payload.keyId ?? null,
|
|
705
|
+
publicKeyHex,
|
|
706
|
+
// Null, not absent: the legacy form is a POSITIVE statement that this
|
|
707
|
+
// artifact predates domain separation, which a caller may want to log.
|
|
708
|
+
statementDomain: null,
|
|
709
|
+
contentHashHex: bytesToHex(
|
|
710
|
+
await sha256Bytes(new Uint8Array(protectedArtifact.payloadBytes)),
|
|
711
|
+
),
|
|
712
|
+
canonicalModuleHashHex: canonical.hashHex,
|
|
713
|
+
signatureScope,
|
|
714
|
+
signedHashHex,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function readEnv(name) {
|
|
719
|
+
try {
|
|
720
|
+
if (typeof process !== "undefined" && process?.env?.[name] !== undefined) {
|
|
721
|
+
return process.env[name];
|
|
722
|
+
}
|
|
723
|
+
} catch {
|
|
724
|
+
// no process in this runtime
|
|
725
|
+
}
|
|
726
|
+
return undefined;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Resolve the effective signature-verification policy for a load operation.
|
|
731
|
+
* Sources, in priority order: explicit `options.verifySignature`, then the
|
|
732
|
+
* `SDM_TRUSTED_MODULE_SIGNERS` / `SDM_REQUIRE_MODULE_SIGNATURE` environment
|
|
733
|
+
* variables, then `globalThis.__SDM_TRUSTED_MODULE_SIGNERS__` /
|
|
734
|
+
* `globalThis.__SDM_REQUIRE_MODULE_SIGNATURE__` (for browser hosts).
|
|
735
|
+
*
|
|
736
|
+
* Returns null when no policy is configured (loading proceeds unverified,
|
|
737
|
+
* preserving existing behavior).
|
|
738
|
+
*/
|
|
739
|
+
export function resolveModuleSignaturePolicy(options = {}) {
|
|
740
|
+
if (options.verifySignature === false) {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
if (options.verifySignature && typeof options.verifySignature === "object") {
|
|
744
|
+
return {
|
|
745
|
+
trustedPublicKeys: normalizeTrustedPublicKeys(
|
|
746
|
+
options.verifySignature.trustedPublicKeys,
|
|
747
|
+
),
|
|
748
|
+
requireSignature: options.verifySignature.requireSignature === true,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
const envTrusted = readEnv("SDM_TRUSTED_MODULE_SIGNERS");
|
|
752
|
+
const envRequire = readEnv("SDM_REQUIRE_MODULE_SIGNATURE");
|
|
753
|
+
const globalTrusted = globalThis.__SDM_TRUSTED_MODULE_SIGNERS__;
|
|
754
|
+
const globalRequire = globalThis.__SDM_REQUIRE_MODULE_SIGNATURE__;
|
|
755
|
+
const trustedPublicKeys = normalizeTrustedPublicKeys(
|
|
756
|
+
envTrusted ?? globalTrusted,
|
|
757
|
+
);
|
|
758
|
+
const requireSignature =
|
|
759
|
+
envRequire === "1" ||
|
|
760
|
+
envRequire === "true" ||
|
|
761
|
+
globalRequire === true ||
|
|
762
|
+
globalRequire === "1";
|
|
763
|
+
if (trustedPublicKeys.length === 0 && !requireSignature) {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
return { trustedPublicKeys, requireSignature };
|
|
767
|
+
}
|