@pulse-compute/crypto 1.0.0-beta.1 → 1.0.0-beta.2
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 +16 -3
- package/as/pulse-hmac-as.ts +29 -0
- package/package.json +5 -3
- package/pulsewasm.native.cjs +46 -4
- package/src/provider.cjs +43 -0
package/README.md
CHANGED
|
@@ -6,15 +6,16 @@
|
|
|
6
6
|
> **Install directly:** Yes, when an application uses the crypto verification surface directly; JWT applications receive it transitively.<br>
|
|
7
7
|
> **Supported entry points:** `@pulse-compute/crypto`<br>
|
|
8
8
|
> **Stability:** The package root is the supported bounded verification contract; realization and Native integration subpaths remain toolchain-only.<br>
|
|
9
|
-
> **
|
|
9
|
+
> **npm:** [`@pulse-compute/crypto`](https://www.npmjs.com/package/@pulse-compute/crypto)<br>
|
|
10
|
+
> **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.2/packages/crypto/)
|
|
10
11
|
>
|
|
11
|
-
> This release-status block is generated from the synchronized `Pulse 1.0.0-beta.
|
|
12
|
+
> This release-status block is generated from the synchronized `Pulse 1.0.0-beta.2` package policy.
|
|
12
13
|
<!-- pulse-package-status:end -->
|
|
13
14
|
|
|
14
15
|
Provider-neutral cryptographic verification contracts for Pulse.
|
|
15
16
|
|
|
16
17
|
This package and `@pulse-compute/jwt` are members of the synchronized
|
|
17
|
-
`1.0.0-beta.
|
|
18
|
+
`1.0.0-beta.2` release catalog.
|
|
18
19
|
|
|
19
20
|
## Verification contract
|
|
20
21
|
|
|
@@ -125,3 +126,15 @@ The G3 composition and shared ES256 corpus are recorded in
|
|
|
125
126
|
`wasm/.test-results/jwt-g3/jwt-g3-evidence.json`. This working-candidate proof
|
|
126
127
|
does not add signing, remote key discovery, fallback, publication, or
|
|
127
128
|
deployment.
|
|
129
|
+
|
|
130
|
+
## Trusted byte-output composition
|
|
131
|
+
|
|
132
|
+
S3 uses Crypto-owned SHA-256 and HMAC-SHA256 byte outputs. Native composes the
|
|
133
|
+
existing guest-source unit; Node JavaScript explicitly binds Web Crypto through
|
|
134
|
+
`@pulse-compute/crypto/provider`. The trusted seam snapshots Uint8Array input,
|
|
135
|
+
allows at most 32768 data bytes and 8192 HMAC key bytes, returns exactly 32 bytes,
|
|
136
|
+
and wipes input staging on success or failure. It rejects unavailable selected
|
|
137
|
+
primitives and does not fall back. Empty HMAC keys use the equivalent padded
|
|
138
|
+
zero block required by HMAC, preserving Native semantics despite Web Crypto's
|
|
139
|
+
zero-length import restriction. This provider seam does not add an author-facing
|
|
140
|
+
signing API or change JWT key limits.
|
package/as/pulse-hmac-as.ts
CHANGED
|
@@ -313,3 +313,32 @@ export function pulse_crypto_hs256_verify(
|
|
|
313
313
|
__pulse_crypto_wipe(expected)
|
|
314
314
|
return result
|
|
315
315
|
}
|
|
316
|
+
|
|
317
|
+
// Internal digest/MAC output contract. This frame is a rooted allocation owned
|
|
318
|
+
// by Crypto; host callers may stage synchronously, copy the output, then wipe.
|
|
319
|
+
// It does not grow Wasm memory per operation or overlap allocator-owned memory.
|
|
320
|
+
const __pulse_crypto_bytes_frame = new Uint8Array(8192 + 32768 + 32)
|
|
321
|
+
export function pulse_crypto_bytes_frame_v1(): i32 {
|
|
322
|
+
return <i32>__pulse_crypto_bytes_frame.dataStart
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function pulse_crypto_sha256_bytes_v1(dataPointer: i32, dataLength: i32, outputPointer: i32, outputLength: i32): i32 {
|
|
326
|
+
if (dataLength < 0 || dataLength > 32768 || outputLength != 32
|
|
327
|
+
|| !__pulse_crypto_memory_range_valid(dataPointer, dataLength)
|
|
328
|
+
|| !__pulse_crypto_memory_range_valid(outputPointer, outputLength)) return __PULSE_CRYPTO_INVALID_INPUT
|
|
329
|
+
const digest = __pulse_crypto_sha256_memory(<usize><u32>dataPointer, dataLength)
|
|
330
|
+
for (let i: i32 = 0; i < 32; i++) store<u8>(<usize><u32>outputPointer + <usize>i, unchecked(digest[i]))
|
|
331
|
+
__pulse_crypto_wipe(digest)
|
|
332
|
+
return __PULSE_CRYPTO_VALID
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function pulse_crypto_hmac_sha256_bytes_v1(keyPointer: i32, keyLength: i32, dataPointer: i32, dataLength: i32, outputPointer: i32, outputLength: i32): i32 {
|
|
336
|
+
if (keyLength < 0 || keyLength > 8192 || !__pulse_crypto_memory_range_valid(keyPointer, keyLength)) return __PULSE_CRYPTO_INVALID_KEY
|
|
337
|
+
if (dataLength < 0 || dataLength > 32768 || outputLength != 32
|
|
338
|
+
|| !__pulse_crypto_memory_range_valid(dataPointer, dataLength)
|
|
339
|
+
|| !__pulse_crypto_memory_range_valid(outputPointer, outputLength)) return __PULSE_CRYPTO_INVALID_INPUT
|
|
340
|
+
const digest = __pulse_crypto_hmac_sha256(<usize><u32>keyPointer, keyLength, <usize><u32>dataPointer, dataLength)
|
|
341
|
+
for (let i: i32 = 0; i < 32; i++) store<u8>(<usize><u32>outputPointer + <usize>i, unchecked(digest[i]))
|
|
342
|
+
__pulse_crypto_wipe(digest)
|
|
343
|
+
return __PULSE_CRYPTO_VALID
|
|
344
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulse-compute/crypto",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^22.14.0 || ^24.0.0"
|
|
@@ -14,13 +14,15 @@
|
|
|
14
14
|
"import": "./dist/index.js",
|
|
15
15
|
"default": "./dist/index.js"
|
|
16
16
|
},
|
|
17
|
-
"./pulsewasm-native": "./pulsewasm.native.cjs"
|
|
17
|
+
"./pulsewasm-native": "./pulsewasm.native.cjs",
|
|
18
|
+
"./provider": "./src/provider.cjs"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"as",
|
|
21
22
|
"dist",
|
|
22
23
|
"pulsewasm.native.cjs",
|
|
23
24
|
"README.md",
|
|
25
|
+
"src/provider.cjs",
|
|
24
26
|
"LICENSE",
|
|
25
27
|
"NOTICE"
|
|
26
28
|
],
|
|
@@ -35,7 +37,7 @@
|
|
|
35
37
|
"bugs": {
|
|
36
38
|
"url": "https://github.com/pulse-compute/pulse/issues"
|
|
37
39
|
},
|
|
38
|
-
"homepage": "https://pulsecompute.io/v1.0.0-beta.
|
|
40
|
+
"homepage": "https://pulsecompute.io/v1.0.0-beta.2/packages/crypto/",
|
|
39
41
|
"main": "./dist/index.js",
|
|
40
42
|
"types": "./dist/index.d.ts",
|
|
41
43
|
"scripts": {
|
package/pulsewasm.native.cjs
CHANGED
|
@@ -14,7 +14,8 @@ function sha256(value) {
|
|
|
14
14
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function pulseHmacAssemblyScriptSource() {
|
|
17
|
+
function pulseHmacAssemblyScriptSource(algorithm = 'HS256') {
|
|
18
|
+
if (!['HS256', 'SHA-256', 'HMAC-SHA256'].includes(algorithm)) throw new TypeError('Unsupported Crypto guest-source algorithm.');
|
|
18
19
|
const source = fs.readFileSync(sourceFile, 'utf8');
|
|
19
20
|
return Object.freeze({
|
|
20
21
|
version: CRYPTO_GUEST_SOURCE_CONTRACT_VERSION,
|
|
@@ -25,7 +26,7 @@ function pulseHmacAssemblyScriptSource() {
|
|
|
25
26
|
kind: 'guest-source',
|
|
26
27
|
backend: 'pulse-hmac-as',
|
|
27
28
|
backendVersion: 'pulse-hmac-as.v1',
|
|
28
|
-
algorithm
|
|
29
|
+
algorithm,
|
|
29
30
|
language: 'assemblyscript',
|
|
30
31
|
license: 'Apache-2.0',
|
|
31
32
|
origin: 'package-source',
|
|
@@ -42,13 +43,20 @@ function pulseHmacAssemblyScriptSource() {
|
|
|
42
43
|
imports: Object.freeze([]),
|
|
43
44
|
exports: Object.freeze([
|
|
44
45
|
'pulse_crypto_hs256_verify',
|
|
45
|
-
'pulse_crypto_sha256_digest'
|
|
46
|
+
'pulse_crypto_sha256_digest',
|
|
47
|
+
'pulse_crypto_bytes_frame_v1',
|
|
48
|
+
'pulse_crypto_sha256_bytes_v1',
|
|
49
|
+
'pulse_crypto_hmac_sha256_bytes_v1'
|
|
46
50
|
]),
|
|
47
51
|
resourceLimits: Object.freeze({
|
|
48
52
|
hmacKeyBytesMinimum: 32,
|
|
49
53
|
hmacKeyBytesMaximum: 4 * 1024,
|
|
50
54
|
macDataBytesMaximum: 1024 * 1024,
|
|
51
|
-
hs256TagBytes: 32
|
|
55
|
+
hs256TagBytes: 32,
|
|
56
|
+
byteOperationKeyBytesMaximum: 8192,
|
|
57
|
+
byteOperationDataBytesMaximum: 32768,
|
|
58
|
+
byteOperationOutputBytes: 32,
|
|
59
|
+
byteOperationFrameBytes: 40992
|
|
52
60
|
}),
|
|
53
61
|
resultCodes: Object.freeze({
|
|
54
62
|
valid: 1,
|
|
@@ -66,6 +74,39 @@ function pulseHmacAssemblyScriptSource() {
|
|
|
66
74
|
});
|
|
67
75
|
}
|
|
68
76
|
|
|
77
|
+
function bindNativeDigestMac(moduleExports) {
|
|
78
|
+
const memory = moduleExports && moduleExports.memory;
|
|
79
|
+
const names = ['pulse_crypto_bytes_frame_v1', 'pulse_crypto_sha256_bytes_v1', 'pulse_crypto_hmac_sha256_bytes_v1'];
|
|
80
|
+
if (!(memory instanceof WebAssembly.Memory) || names.some((name) => typeof moduleExports[name] !== 'function')) {
|
|
81
|
+
throw new TypeError('Native Crypto digest/MAC exports are unavailable.');
|
|
82
|
+
}
|
|
83
|
+
const frameBytes = 8192 + 32768 + 32;
|
|
84
|
+
function run(data, key) {
|
|
85
|
+
if (!(data instanceof Uint8Array) || data.length > 32768
|
|
86
|
+
|| (key !== undefined && (!(key instanceof Uint8Array) || key.length > 8192))) {
|
|
87
|
+
throw new TypeError('Native Crypto digest/MAC input exceeds its byte contract.');
|
|
88
|
+
}
|
|
89
|
+
const pointer = moduleExports.pulse_crypto_bytes_frame_v1() >>> 0;
|
|
90
|
+
if (pointer + frameBytes > memory.buffer.byteLength) throw new TypeError('Native Crypto frame is outside memory.');
|
|
91
|
+
const dataPointer = pointer + 8192;
|
|
92
|
+
const outputPointer = dataPointer + 32768;
|
|
93
|
+
try {
|
|
94
|
+
const view = new Uint8Array(memory.buffer);
|
|
95
|
+
view.set(data, dataPointer);
|
|
96
|
+
if (key !== undefined) view.set(key, pointer);
|
|
97
|
+
const code = key === undefined
|
|
98
|
+
? moduleExports.pulse_crypto_sha256_bytes_v1(dataPointer, data.length, outputPointer, 32)
|
|
99
|
+
: moduleExports.pulse_crypto_hmac_sha256_bytes_v1(pointer, key.length, dataPointer, data.length, outputPointer, 32);
|
|
100
|
+
if (code !== 1) throw new TypeError('Native Crypto digest/MAC realization failed.');
|
|
101
|
+
return new Uint8Array(memory.buffer, outputPointer, 32).slice();
|
|
102
|
+
} finally {
|
|
103
|
+
// The Wasm allocator can grow memory while hashing: acquire a fresh view.
|
|
104
|
+
new Uint8Array(memory.buffer, pointer, frameBytes).fill(0);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return Object.freeze({ sha256: (data) => run(data), hmacSha256: (key, data) => run(data, key) });
|
|
108
|
+
}
|
|
109
|
+
|
|
69
110
|
function pulseEs256GuestUnit() {
|
|
70
111
|
return Object.freeze({
|
|
71
112
|
version: GUEST_UNIT_CONTRIBUTION_VERSION,
|
|
@@ -81,5 +122,6 @@ module.exports = Object.freeze({
|
|
|
81
122
|
CRYPTO_GUEST_SOURCE_CONTRACT_VERSION,
|
|
82
123
|
GUEST_UNIT_CONTRIBUTION_VERSION,
|
|
83
124
|
pulseHmacAssemblyScriptSource,
|
|
125
|
+
bindNativeDigestMac,
|
|
84
126
|
pulseEs256GuestUnit
|
|
85
127
|
});
|
package/src/provider.cjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Trusted provider byte-output seam. JWT verification keeps its own key limits.
|
|
4
|
+
const IMPLEMENTATIONS = Object.freeze({
|
|
5
|
+
'SHA-256': 'webcrypto.subtle.sha-256-bytes.v1',
|
|
6
|
+
'HMAC-SHA256': 'webcrypto.subtle.hmac-sha-256-bytes.v1'
|
|
7
|
+
});
|
|
8
|
+
function snapshot(value, maximum) {
|
|
9
|
+
if (!(value instanceof Uint8Array) || value.byteLength > maximum) throw new TypeError('Crypto byte input is outside its bound.');
|
|
10
|
+
return new Uint8Array(value);
|
|
11
|
+
}
|
|
12
|
+
function result(value) {
|
|
13
|
+
if (!(value instanceof ArrayBuffer) || value.byteLength !== 32) throw new TypeError('Crypto byte output is invalid.');
|
|
14
|
+
return new Uint8Array(value);
|
|
15
|
+
}
|
|
16
|
+
function bindJavascriptDigestMac(algorithms, subtle = globalThis.crypto && globalThis.crypto.subtle) {
|
|
17
|
+
if (!Array.isArray(algorithms) || new Set(algorithms).size !== algorithms.length || algorithms.some((name) => !Object.hasOwn(IMPLEMENTATIONS, name))) throw new TypeError('Crypto requires explicit supported byte algorithms.');
|
|
18
|
+
if (!subtle || algorithms.includes('SHA-256') && typeof subtle.digest !== 'function'
|
|
19
|
+
|| algorithms.includes('HMAC-SHA256') && (typeof subtle.sign !== 'function' || typeof subtle.importKey !== 'function')) throw new TypeError('Selected Web Crypto byte realization is unavailable.');
|
|
20
|
+
const bytes = {};
|
|
21
|
+
if (algorithms.includes('SHA-256')) bytes.sha256 = async (input) => {
|
|
22
|
+
const data = snapshot(input, 32768);
|
|
23
|
+
try { return result(await subtle.digest({ name: 'SHA-256' }, data)); }
|
|
24
|
+
finally { data.fill(0); }
|
|
25
|
+
};
|
|
26
|
+
if (algorithms.includes('HMAC-SHA256')) bytes.hmacSha256 = async (keyInput, input) => {
|
|
27
|
+
let key = snapshot(keyInput, 8192);
|
|
28
|
+
let data;
|
|
29
|
+
try {
|
|
30
|
+
data = snapshot(input, 32768);
|
|
31
|
+
// HMAC pads an empty key to a zero block (RFC 2104). Web Crypto rejects
|
|
32
|
+
// a zero-length import; the equivalent padded block retains Native parity.
|
|
33
|
+
if (key.length === 0) key = new Uint8Array(64);
|
|
34
|
+
const imported = await subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256', length: key.length * 8 }, false, ['sign']);
|
|
35
|
+
return result(await subtle.sign({ name: 'HMAC' }, imported, data));
|
|
36
|
+
} finally { key.fill(0); if (data) data.fill(0); }
|
|
37
|
+
};
|
|
38
|
+
return Object.freeze({ bytes: Object.freeze(bytes), realization: Object.freeze({
|
|
39
|
+
target: 'javascript', automaticFallback: false,
|
|
40
|
+
algorithms: Object.freeze(algorithms.map((algorithm) => Object.freeze({ algorithm, realization: 'runtime-builtin', implementation: IMPLEMENTATIONS[algorithm], available: true, automaticFallback: false })))
|
|
41
|
+
}) });
|
|
42
|
+
}
|
|
43
|
+
module.exports = Object.freeze({ IMPLEMENTATIONS, bindJavascriptDigestMac });
|