@pulse-compute/crypto 1.0.0-beta.3 → 1.0.0-beta.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 +37 -8
- package/as/digest-text.as.ts +34 -0
- package/as/pulse-hmac-as.ts +7 -1
- package/conformance/digest-text.json +38 -0
- package/conformance/hs256.json +85 -0
- package/dist/digest.d.ts +14 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +17 -0
- package/dist/digest.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +20 -4
- package/pulse.package.json +64 -0
- package/pulsewasm.compiler.cjs +89 -0
- package/pulsewasm.manifest.cjs +15 -0
- package/pulsewasm.native.cjs +13 -7
- package/src/provider.cjs +71 -2
package/README.md
CHANGED
|
@@ -2,20 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- pulse-package-status:start -->
|
|
4
4
|
> **Support tier:** Supported provider/extension surface<br>
|
|
5
|
-
> **Audience:** Applications and first-party packages using provider-neutral
|
|
6
|
-
> **Install directly:** Yes, when an application uses
|
|
5
|
+
> **Audience:** Applications and first-party packages using provider-neutral verification and exact-text SHA-256.<br>
|
|
6
|
+
> **Install directly:** Yes, when an application uses verification or digestText directly; JWT applications receive it transitively.<br>
|
|
7
7
|
> **Supported entry points:** `@pulse-compute/crypto`<br>
|
|
8
|
-
> **Stability:** The package root
|
|
8
|
+
> **Stability:** The package root supports bounded verification and request-owned exact-text digests; provider, manifest/compiler 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.
|
|
10
|
+
> **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.5/packages/crypto/)
|
|
11
11
|
>
|
|
12
|
-
> 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.5` package policy.
|
|
13
13
|
<!-- pulse-package-status:end -->
|
|
14
14
|
|
|
15
|
-
Provider-neutral
|
|
15
|
+
Provider-neutral verification and bounded exact-text SHA-256 for Pulse.
|
|
16
16
|
|
|
17
17
|
This package and `@pulse-compute/jwt` are members of the synchronized
|
|
18
|
-
`1.0.0-beta.
|
|
18
|
+
`1.0.0-beta.5` release catalog.
|
|
19
|
+
|
|
20
|
+
## Exact-text SHA-256
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { crypto } from '@pulse-compute/crypto'
|
|
24
|
+
|
|
25
|
+
const digest = await crypto.digestText(ctx, text)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Select `pulse.crypto: ['SHA-256']`. The current HTTP context owns this effect;
|
|
29
|
+
use a directly awaited local variable or keyed `ctx.parallel` member.
|
|
30
|
+
Success is `{ status: 'ok', sha256, byteLength }`: 64 lowercase hex characters
|
|
31
|
+
and the exact UTF-8 length. The public `DIGEST_TEXT_MAX_BYTES` is 2097152, empty
|
|
32
|
+
input is accepted, and unpaired surrogates are rejected without replacement.
|
|
33
|
+
No normalization, parsing, BOM stripping, or newline conversion occurs.
|
|
34
|
+
Failures are `{ status: 'failed', reason }`, where reason is `invalid-text`,
|
|
35
|
+
`too-large`, `unavailable`, or `realization-failure`; cancellation terminates
|
|
36
|
+
the invocation. The named `digestText` export has the same contract.
|
|
37
|
+
|
|
38
|
+
Node/Fastly Native reuse the selected Crypto SHA-256 guest source. Node/Fastly
|
|
39
|
+
JavaScript use selected Web Crypto. Neither falls back. This bound matches
|
|
40
|
+
current S3 text capacity and leaves larger Catalog envelopes for capacity work.
|
|
41
|
+
See the [exact-text digest contract](https://pulsecompute.io/v1.0.0-beta.5/packages/crypto/#exact-text-digest).
|
|
19
42
|
|
|
20
43
|
## Verification contract
|
|
21
44
|
|
|
@@ -132,9 +155,15 @@ deployment.
|
|
|
132
155
|
S3 uses Crypto-owned SHA-256 and HMAC-SHA256 byte outputs. Native composes the
|
|
133
156
|
existing guest-source unit; Node JavaScript explicitly binds Web Crypto through
|
|
134
157
|
`@pulse-compute/crypto/provider`. The trusted seam snapshots Uint8Array input,
|
|
135
|
-
allows at most
|
|
158
|
+
allows at most 2097152 SHA-256 data bytes, 32768 HMAC data bytes and 8192 HMAC
|
|
159
|
+
key bytes, returns exactly 32 bytes,
|
|
136
160
|
and wipes input staging on success or failure. It rejects unavailable selected
|
|
137
161
|
primitives and does not fall back. Empty HMAC keys use the equivalent padded
|
|
138
162
|
zero block required by HMAC, preserving Native semantics despite Web Crypto's
|
|
139
163
|
zero-length import restriction. This provider seam does not add an author-facing
|
|
140
164
|
signing API or change JWT key limits.
|
|
165
|
+
|
|
166
|
+
Large hashes use a separate lazy 2,097,184-byte Native staging frame; small
|
|
167
|
+
hashes and HMAC keep the 40,992-byte frame. Primary-memory Native digest/S3
|
|
168
|
+
modules have a 256 MiB Wasm maximum. Fixed-memory linked guests retain their
|
|
169
|
+
separate ABI and do not establish the 2 MiB text capacity profile.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Crypto-owned text framing over the existing SHA-256 implementation.
|
|
2
|
+
class __PulseTextDigestResult {
|
|
3
|
+
status: string = 'failed'
|
|
4
|
+
reason: string = ''
|
|
5
|
+
sha256: string = ''
|
|
6
|
+
byteLength: i32 = 0
|
|
7
|
+
}
|
|
8
|
+
function __pulse_crypto_digest_text(text: string | null): __PulseTextDigestResult {
|
|
9
|
+
const result = new __PulseTextDigestResult()
|
|
10
|
+
if (text === null) { result.reason = 'invalid-text'; return result }
|
|
11
|
+
if (text.length > 2097152) { result.reason = 'too-large'; return result }
|
|
12
|
+
let length = 0
|
|
13
|
+
for (let i = 0; i < text.length; i++) {
|
|
14
|
+
const c = text.charCodeAt(i)
|
|
15
|
+
if (c >= 0xd800 && c <= 0xdbff) {
|
|
16
|
+
i += 1
|
|
17
|
+
if (i >= text.length) { result.reason = 'invalid-text'; return result }
|
|
18
|
+
const low = text.charCodeAt(i)
|
|
19
|
+
if (low < 0xdc00 || low > 0xdfff) { result.reason = 'invalid-text'; return result }
|
|
20
|
+
length += 4
|
|
21
|
+
} else if (c >= 0xdc00 && c <= 0xdfff) { result.reason = 'invalid-text'; return result }
|
|
22
|
+
else length += c < 0x80 ? 1 : c < 0x800 ? 2 : 3
|
|
23
|
+
}
|
|
24
|
+
if (length > 2097152) { result.reason = 'too-large'; return result }
|
|
25
|
+
const data = Uint8Array.wrap(String.UTF8.encode(text)), digest = new Uint8Array(32)
|
|
26
|
+
const code = pulse_crypto_sha256_bytes_v1(data.dataStart, data.length, digest.dataStart, 32)
|
|
27
|
+
data.fill(0)
|
|
28
|
+
if (code != 1) { digest.fill(0); result.reason = 'realization-failure'; return result }
|
|
29
|
+
const alphabet = '0123456789abcdef'
|
|
30
|
+
for (let i = 0; i < 32; i++) result.sha256 += alphabet.charAt(digest[i] >> 4) + alphabet.charAt(digest[i] & 15)
|
|
31
|
+
digest.fill(0)
|
|
32
|
+
result.status = 'ok'; result.byteLength = length
|
|
33
|
+
return result
|
|
34
|
+
}
|
package/as/pulse-hmac-as.ts
CHANGED
|
@@ -318,12 +318,18 @@ export function pulse_crypto_hs256_verify(
|
|
|
318
318
|
// by Crypto; host callers may stage synchronously, copy the output, then wipe.
|
|
319
319
|
// It does not grow Wasm memory per operation or overlap allocator-owned memory.
|
|
320
320
|
const __pulse_crypto_bytes_frame = new Uint8Array(8192 + 32768 + 32)
|
|
321
|
+
// Allocate the larger digest-only frame only when a host stages a large hash.
|
|
322
|
+
// Small digest/MAC callers and fixed-memory JWT guests keep the original frame.
|
|
323
|
+
@lazy const __pulse_crypto_digest_frame = new Uint8Array(2097152 + 32)
|
|
324
|
+
export function pulse_crypto_digest_frame_v1(): i32 {
|
|
325
|
+
return <i32>__pulse_crypto_digest_frame.dataStart
|
|
326
|
+
}
|
|
321
327
|
export function pulse_crypto_bytes_frame_v1(): i32 {
|
|
322
328
|
return <i32>__pulse_crypto_bytes_frame.dataStart
|
|
323
329
|
}
|
|
324
330
|
|
|
325
331
|
export function pulse_crypto_sha256_bytes_v1(dataPointer: i32, dataLength: i32, outputPointer: i32, outputLength: i32): i32 {
|
|
326
|
-
if (dataLength < 0 || dataLength >
|
|
332
|
+
if (dataLength < 0 || dataLength > 2097152 || outputLength != 32
|
|
327
333
|
|| !__pulse_crypto_memory_range_valid(dataPointer, dataLength)
|
|
328
334
|
|| !__pulse_crypto_memory_range_valid(outputPointer, outputLength)) return __PULSE_CRYPTO_INVALID_INPUT
|
|
329
335
|
const digest = __pulse_crypto_sha256_memory(<usize><u32>dataPointer, dataLength)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "pulse.crypto-text-digest-corpus.v1",
|
|
3
|
+
"algorithm": "SHA-256",
|
|
4
|
+
"encoding": "lowercase-hex",
|
|
5
|
+
"maxUtf8Bytes": 2097152,
|
|
6
|
+
"vectors": [
|
|
7
|
+
{
|
|
8
|
+
"text": "",
|
|
9
|
+
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
10
|
+
"byteLength": 0
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"text": "abc",
|
|
14
|
+
"sha256": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
|
15
|
+
"byteLength": 3
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"text": "\ufeff\u00e9\ud83d\ude00\u0000\r\n",
|
|
19
|
+
"sha256": "894e231659c59a0e7c35180fbca175d0fb836eb20485a8d0dd02bf5ff8c2a4e7",
|
|
20
|
+
"byteLength": 12
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"text": "\u00e9",
|
|
24
|
+
"sha256": "4a99557e4033c3539de2eb65472017cad5f9557f7a0625a09f1c3f6e2ba69c4c",
|
|
25
|
+
"byteLength": 2
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"text": "e\u0301",
|
|
29
|
+
"sha256": "bf12767b0f2a56b2190075bae8169f656e3ce8d6357d4aff184bc6c7ea48f9f6",
|
|
30
|
+
"byteLength": 3
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"text": "{ \"b\":2, \"a\":1 }",
|
|
34
|
+
"sha256": "a782da44b228c14de003efcebc359fbe0a5bc35d90d5da70513e54e25fa5428a",
|
|
35
|
+
"byteLength": 16
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "pulse.crypto.hs256-conformance.v2",
|
|
3
|
+
"sources": {
|
|
4
|
+
"sha256": "NIST FIPS 180-4",
|
|
5
|
+
"hmac": "RFC 2104",
|
|
6
|
+
"vectors": "RFC 4231 test cases 6 and 7"
|
|
7
|
+
},
|
|
8
|
+
"resultStatuses": [
|
|
9
|
+
"valid",
|
|
10
|
+
"invalid-authenticator",
|
|
11
|
+
"invalid-key",
|
|
12
|
+
"invalid-input",
|
|
13
|
+
"realization-failure"
|
|
14
|
+
],
|
|
15
|
+
"resourceLimits": {
|
|
16
|
+
"hmacKeyBytesMinimum": 32,
|
|
17
|
+
"hmacKeyBytesMaximum": 4096,
|
|
18
|
+
"macDataBytesMaximum": 1048576,
|
|
19
|
+
"hs256TagBytes": 32
|
|
20
|
+
},
|
|
21
|
+
"publishedVectors": [
|
|
22
|
+
{
|
|
23
|
+
"id": "rfc4231-6",
|
|
24
|
+
"keyByte": 170,
|
|
25
|
+
"keyLength": 131,
|
|
26
|
+
"dataHex": "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374",
|
|
27
|
+
"tagHex": "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "rfc4231-7",
|
|
31
|
+
"keyByte": 170,
|
|
32
|
+
"keyLength": 131,
|
|
33
|
+
"dataHex": "5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e",
|
|
34
|
+
"tagHex": "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"boundaryVectors": {
|
|
38
|
+
"emptyData": {
|
|
39
|
+
"keyByte": 42,
|
|
40
|
+
"keyLength": 32,
|
|
41
|
+
"dataLength": 0,
|
|
42
|
+
"dataByte": 0,
|
|
43
|
+
"tagHex": "25e570b039a294705747f0783f32e9b538be395f8713fbcbf346844d84bc78ad"
|
|
44
|
+
},
|
|
45
|
+
"maximumData": {
|
|
46
|
+
"keyByte": 42,
|
|
47
|
+
"keyLength": 32,
|
|
48
|
+
"dataLength": 1048576,
|
|
49
|
+
"dataByte": 165,
|
|
50
|
+
"tagHex": "c897712d87b7a5649ec1115c6d7461372ff21fdebdc8711b23ddefc0a52e74a3"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"verificationCases": [
|
|
54
|
+
{ "id": "valid-tag", "fixture": "rfc4231-6", "mutation": "none", "expected": "valid" },
|
|
55
|
+
{ "id": "invalid-tag", "fixture": "rfc4231-6", "mutation": "zero-tag", "expected": "invalid-authenticator" },
|
|
56
|
+
{ "id": "first-byte-mismatch", "fixture": "rfc4231-6", "mutation": "tag-byte-0", "expected": "invalid-authenticator" },
|
|
57
|
+
{ "id": "middle-byte-mismatch", "fixture": "rfc4231-6", "mutation": "tag-byte-15", "expected": "invalid-authenticator" },
|
|
58
|
+
{ "id": "last-byte-mismatch", "fixture": "rfc4231-6", "mutation": "tag-byte-31", "expected": "invalid-authenticator" },
|
|
59
|
+
{ "id": "altered-data", "fixture": "rfc4231-6", "mutation": "data-byte-0", "expected": "invalid-authenticator" },
|
|
60
|
+
{ "id": "wrong-key", "fixture": "rfc4231-6", "mutation": "key-byte-0", "expected": "invalid-authenticator" },
|
|
61
|
+
{ "id": "undersized-key", "fixture": "rfc4231-6", "mutation": "key-length-31", "expected": "invalid-key" },
|
|
62
|
+
{ "id": "malformed-key", "fixture": "rfc4231-6", "mutation": "malformed-key", "expected": "invalid-key" },
|
|
63
|
+
{ "id": "empty-data", "fixture": "empty-data", "mutation": "none", "expected": "valid" },
|
|
64
|
+
{ "id": "bounded-maximum-data", "fixture": "maximum-data", "mutation": "none", "expected": "valid" },
|
|
65
|
+
{ "id": "over-limit-data", "fixture": "maximum-data", "mutation": "data-over-limit", "expected": "invalid-input" },
|
|
66
|
+
{ "id": "malformed-tag", "fixture": "rfc4231-6", "mutation": "malformed-tag", "expected": "invalid-input" },
|
|
67
|
+
{ "id": "wrong-length-tag", "fixture": "rfc4231-6", "mutation": "tag-length-31", "expected": "invalid-input" }
|
|
68
|
+
],
|
|
69
|
+
"configurationCases": [
|
|
70
|
+
"inherited-global-crypto",
|
|
71
|
+
"named-profile-replacement",
|
|
72
|
+
"no-array-merging",
|
|
73
|
+
"no-object-merging",
|
|
74
|
+
"empty-array",
|
|
75
|
+
"empty-object",
|
|
76
|
+
"duplicate-array-entry",
|
|
77
|
+
"unknown-key",
|
|
78
|
+
"unknown-algorithm",
|
|
79
|
+
"unknown-realization",
|
|
80
|
+
"profile-missing-hs256",
|
|
81
|
+
"target-without-hs256",
|
|
82
|
+
"unavailable-exact-realization-pin",
|
|
83
|
+
"no-fallback-after-realization-failure"
|
|
84
|
+
]
|
|
85
|
+
}
|
package/dist/digest.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PulseContext, PulseParallelEffect } from '@pulse-compute/runtime';
|
|
2
|
+
/** Exact UTF-8 text bound, independent of HMAC and JWT limits. */
|
|
3
|
+
export declare const DIGEST_TEXT_MAX_BYTES: 2097152;
|
|
4
|
+
export type TextDigestResult = {
|
|
5
|
+
readonly status: 'ok';
|
|
6
|
+
readonly sha256: string;
|
|
7
|
+
readonly byteLength: number;
|
|
8
|
+
} | {
|
|
9
|
+
readonly status: 'failed';
|
|
10
|
+
readonly reason: 'invalid-text' | 'too-large' | 'unavailable' | 'realization-failure';
|
|
11
|
+
};
|
|
12
|
+
/** Hash exact, well-formed text without normalization, parsing or uploading. */
|
|
13
|
+
export declare function digestText(ctx: PulseContext, text: string): PulseParallelEffect<TextDigestResult>;
|
|
14
|
+
//# sourceMappingURL=digest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAIhF,kEAAkE;AAClE,eAAO,MAAM,qBAAqB,EAAG,OAAgB,CAAC;AACtD,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC/E;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW,GAAG,aAAa,GAAG,qBAAqB,CAAA;CAAE,CAAC;AAOzH,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAAC,gBAAgB,CAAC,CAMjG"}
|
package/dist/digest.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import packageRuntime from '@pulse-compute/runtime/package';
|
|
2
|
+
import digestProvider from '../src/provider.cjs';
|
|
3
|
+
/** Exact UTF-8 text bound, independent of HMAC and JWT limits. */
|
|
4
|
+
export const DIGEST_TEXT_MAX_BYTES = 2097152;
|
|
5
|
+
const runtime = packageRuntime.createPackageRuntime({
|
|
6
|
+
package: '@pulse-compute/crypto', contractId: 'pulse.crypto', providerKind: 'crypto',
|
|
7
|
+
operations: { digestText: { kind: 'crypto.digestText', capability: 'crypto.digestText', result: 'text-digest-result' } },
|
|
8
|
+
});
|
|
9
|
+
/** Hash exact, well-formed text without normalization, parsing or uploading. */
|
|
10
|
+
export function digestText(ctx, text) {
|
|
11
|
+
// Preserve the over-limit signal without copying unbounded text into the
|
|
12
|
+
// shared effect envelope. This prefix is itself over the code-unit bound
|
|
13
|
+
// and is rejected before UTF-8 encoding or hashing on every realization.
|
|
14
|
+
const boundedText = typeof text === 'string' ? text.slice(0, DIGEST_TEXT_MAX_BYTES + 1) : null;
|
|
15
|
+
return runtime.effect(ctx, 'digestText', { text: boundedText }, digestProvider.normalizeTextDigestResult);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=digest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AACA,OAAO,cAAc,MAAM,gCAAgC,CAAC;AAC5D,OAAO,cAAc,MAAM,qBAAqB,CAAC;AAEjD,kEAAkE;AAClE,MAAM,CAAC,MAAM,qBAAqB,GAAG,OAAgB,CAAC;AAKtD,MAAM,OAAO,GAAG,cAAc,CAAC,oBAAoB,CAAC;IAClD,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ;IACpF,UAAU,EAAE,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE;CAChH,CAAC,CAAC;AAEZ,gFAAgF;AAChF,MAAM,UAAU,UAAU,CAAC,GAAiB,EAAE,IAAY;IACxD,yEAAyE;IACzE,yEAAyE;IACzE,yEAAyE;IACzE,MAAM,WAAW,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,qBAAqB,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/F,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,cAAc,CAAC,yBAAyB,CAAC,CAAC;AAC5G,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,10 @@ export declare const mac: Readonly<{
|
|
|
11
11
|
export declare const signature: Readonly<{
|
|
12
12
|
verify: typeof verifySignature;
|
|
13
13
|
}>;
|
|
14
|
+
export { DIGEST_TEXT_MAX_BYTES, digestText, type TextDigestResult } from './digest.js';
|
|
15
|
+
import { digestText } from './digest.js';
|
|
14
16
|
export declare const crypto: Readonly<{
|
|
17
|
+
digestText: typeof digestText;
|
|
15
18
|
mac: Readonly<{
|
|
16
19
|
verify: typeof verifyMac;
|
|
17
20
|
}>;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,6BAA6B,EAC7B,wCAAwC,EACxC,qCAAqC,EACrC,2CAA2C,EAC3C,kCAAkC,EAClC,sBAAsB,EACtB,qCAAqC,EACrC,4BAA4B,EAC5B,KAAK,eAAe,EACpB,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC5B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,+BAA+B,EAE/B,iCAAiC,EAGlC,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,6BAA6B,EAC7B,wCAAwC,EACxC,qCAAqC,EACrC,2CAA2C,EAC3C,kCAAkC,EAClC,sBAAsB,EACtB,qCAAqC,EACrC,4BAA4B,GAC7B,CAAC;AACF,YAAY,EACV,eAAe,EACf,wBAAwB,EACxB,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,EAClB,sBAAsB,GACvB,CAAC;AAEF,OAAO,EACL,+BAA+B,EAC/B,iCAAiC,GAClC,CAAC;AAEF,wBAAsB,SAAS,CAC7B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,wBAAwB,CAAC,CAcnC;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,wBAAwB,CAAC,CAenC;AAED,eAAO,MAAM,GAAG;;EAEd,CAAC;AAEH,eAAO,MAAM,SAAS;;EAEpB,CAAC;AAEH,eAAO,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,6BAA6B,EAC7B,wCAAwC,EACxC,qCAAqC,EACrC,2CAA2C,EAC3C,kCAAkC,EAClC,sBAAsB,EACtB,qCAAqC,EACrC,4BAA4B,EAC5B,KAAK,eAAe,EACpB,KAAK,wBAAwB,EAC7B,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC5B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,+BAA+B,EAE/B,iCAAiC,EAGlC,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,6BAA6B,EAC7B,wCAAwC,EACxC,qCAAqC,EACrC,2CAA2C,EAC3C,kCAAkC,EAClC,sBAAsB,EACtB,qCAAqC,EACrC,4BAA4B,GAC7B,CAAC;AACF,YAAY,EACV,eAAe,EACf,wBAAwB,EACxB,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,EAClB,sBAAsB,GACvB,CAAC;AAEF,OAAO,EACL,+BAA+B,EAC/B,iCAAiC,GAClC,CAAC;AAEF,wBAAsB,SAAS,CAC7B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,wBAAwB,CAAC,CAcnC;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,wBAAwB,CAAC,CAenC;AAED,eAAO,MAAM,GAAG;;EAEd,CAAC;AAEH,eAAO,MAAM,SAAS;;EAEpB,CAAC;AAEH,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,eAAO,MAAM,MAAM;;;;;;;;EAIjB,CAAC;AAEH,eAAe,MAAM,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -44,7 +44,10 @@ export const mac = Object.freeze({
|
|
|
44
44
|
export const signature = Object.freeze({
|
|
45
45
|
verify: verifySignature,
|
|
46
46
|
});
|
|
47
|
+
export { DIGEST_TEXT_MAX_BYTES, digestText } from './digest.js';
|
|
48
|
+
import { digestText } from './digest.js';
|
|
47
49
|
export const crypto = Object.freeze({
|
|
50
|
+
digestText,
|
|
48
51
|
mac,
|
|
49
52
|
signature,
|
|
50
53
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,6BAA6B,EAC7B,wCAAwC,EACxC,qCAAqC,EACrC,2CAA2C,EAC3C,kCAAkC,EAClC,sBAAsB,EACtB,qCAAqC,EACrC,4BAA4B,GAa7B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EACL,+BAA+B,EAC/B,yBAAyB,EACzB,iCAAiC,EACjC,+BAA+B,EAC/B,2BAA2B,GAC5B,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,6BAA6B,EAC7B,wCAAwC,EACxC,qCAAqC,EACrC,2CAA2C,EAC3C,kCAAkC,EAClC,sBAAsB,EACtB,qCAAqC,EACrC,4BAA4B,GAC7B,CAAC;AAgBF,OAAO,EACL,+BAA+B,EAC/B,iCAAiC,GAClC,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAyB;IAEzB,IAAI,UAAwD,CAAC;IAC7D,IAAI,CAAC;QACH,UAAU,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,2BAA2B,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,EAAE;QAAE,OAAO,UAAU,CAAC,MAAM,CAAC;IAE7C,IAAI,CAAC;QACH,OAAO,2BAA2B,CAAC,MAAM,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,2BAA2B,CAAC,SAAS,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAA+B;IAE/B,IAAI,UAA8D,CAAC;IACnE,IAAI,CAAC;QACH,UAAU,GAAG,+BAA+B,CAAC,OAAO,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,2BAA2B,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,EAAE;QAAE,OAAO,UAAU,CAAC,MAAM,CAAC;IAE7C,IAAI,CAAC;QACH,OAAO,2BAA2B,CAAC,MAAM,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrC,OAAO,2BAA2B,CAAC,SAAS,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC;IAC/B,MAAM,EAAE,SAAS;CAClB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;IACrC,MAAM,EAAE,eAAe;CACxB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,GAAG;IACH,SAAS;CACV,CAAC,CAAC;AAEH,eAAe,MAAM,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,6BAA6B,EAC7B,wCAAwC,EACxC,qCAAqC,EACrC,2CAA2C,EAC3C,kCAAkC,EAClC,sBAAsB,EACtB,qCAAqC,EACrC,4BAA4B,GAa7B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EACL,+BAA+B,EAC/B,yBAAyB,EACzB,iCAAiC,EACjC,+BAA+B,EAC/B,2BAA2B,GAC5B,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,6BAA6B,EAC7B,wCAAwC,EACxC,qCAAqC,EACrC,2CAA2C,EAC3C,kCAAkC,EAClC,sBAAsB,EACtB,qCAAqC,EACrC,4BAA4B,GAC7B,CAAC;AAgBF,OAAO,EACL,+BAA+B,EAC/B,iCAAiC,GAClC,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,OAAyB;IAEzB,IAAI,UAAwD,CAAC;IAC7D,IAAI,CAAC;QACH,UAAU,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,2BAA2B,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,EAAE;QAAE,OAAO,UAAU,CAAC,MAAM,CAAC;IAE7C,IAAI,CAAC;QACH,OAAO,2BAA2B,CAAC,MAAM,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,2BAA2B,CAAC,SAAS,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAA+B;IAE/B,IAAI,UAA8D,CAAC;IACnE,IAAI,CAAC;QACH,UAAU,GAAG,+BAA+B,CAAC,OAAO,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,2BAA2B,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,EAAE;QAAE,OAAO,UAAU,CAAC,MAAM,CAAC;IAE7C,IAAI,CAAC;QACH,OAAO,2BAA2B,CAAC,MAAM,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrC,OAAO,2BAA2B,CAAC,SAAS,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC;IAC/B,MAAM,EAAE,SAAS;CAClB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC;IACrC,MAAM,EAAE,eAAe;CACxB,CAAC,CAAC;AAEH,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAyB,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,UAAU;IACV,GAAG;IACH,SAAS;CACV,CAAC,CAAC;AAEH,eAAe,MAAM,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulse-compute/crypto",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.5",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^22.14.0 || ^24.0.0"
|
|
7
7
|
},
|
|
8
|
-
"description": "Provider-neutral cryptographic verification
|
|
8
|
+
"description": "Provider-neutral cryptographic verification and bounded exact-text digests for Pulse.",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"sideEffects": false,
|
|
11
11
|
"exports": {
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
"default": "./dist/index.js"
|
|
16
16
|
},
|
|
17
17
|
"./pulsewasm-native": "./pulsewasm.native.cjs",
|
|
18
|
-
"./provider": "./src/provider.cjs"
|
|
18
|
+
"./provider": "./src/provider.cjs",
|
|
19
|
+
"./pulsewasm/manifest": "./pulsewasm.manifest.cjs",
|
|
20
|
+
"./pulsewasm/compiler": "./pulsewasm.compiler.cjs"
|
|
19
21
|
},
|
|
20
22
|
"files": [
|
|
21
23
|
"as",
|
|
@@ -23,6 +25,10 @@
|
|
|
23
25
|
"pulsewasm.native.cjs",
|
|
24
26
|
"README.md",
|
|
25
27
|
"src/provider.cjs",
|
|
28
|
+
"pulse.package.json",
|
|
29
|
+
"pulsewasm.manifest.cjs",
|
|
30
|
+
"pulsewasm.compiler.cjs",
|
|
31
|
+
"conformance",
|
|
26
32
|
"LICENSE",
|
|
27
33
|
"NOTICE"
|
|
28
34
|
],
|
|
@@ -37,9 +43,19 @@
|
|
|
37
43
|
"bugs": {
|
|
38
44
|
"url": "https://github.com/pulse-compute/pulse/issues"
|
|
39
45
|
},
|
|
40
|
-
"homepage": "https://pulsecompute.io/v1.0.0-beta.
|
|
46
|
+
"homepage": "https://pulsecompute.io/v1.0.0-beta.5/packages/crypto/",
|
|
41
47
|
"main": "./dist/index.js",
|
|
42
48
|
"types": "./dist/index.d.ts",
|
|
49
|
+
"pulse": {
|
|
50
|
+
"contract": "./pulse.package.json"
|
|
51
|
+
},
|
|
52
|
+
"pulsewasm": {
|
|
53
|
+
"manifest": "./pulsewasm.manifest.cjs"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@pulse-compute/runtime": "1.0.0-beta.5",
|
|
57
|
+
"@pulse-compute/wasm-contracts": "1.0.0-beta.5"
|
|
58
|
+
},
|
|
43
59
|
"scripts": {
|
|
44
60
|
"build": "tsc -b",
|
|
45
61
|
"test": "corepack pnpm --dir ../.. exec vitest run packages/crypto/test",
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "pulse.package-contract.v1",
|
|
3
|
+
"kind": "pulse.package-contract",
|
|
4
|
+
"contractId": "pulse.crypto",
|
|
5
|
+
"npmPackage": "@pulse-compute/crypto",
|
|
6
|
+
"description": "Bounded exact-text SHA-256 through an execution-owned effect.",
|
|
7
|
+
"authoring": {
|
|
8
|
+
"namespace": "crypto",
|
|
9
|
+
"import": "@pulse-compute/crypto",
|
|
10
|
+
"symbols": [
|
|
11
|
+
"digestText"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"ownership": {
|
|
15
|
+
"profileFragment": "crypto",
|
|
16
|
+
"helperImports": [],
|
|
17
|
+
"reExportImports": []
|
|
18
|
+
},
|
|
19
|
+
"composition": {
|
|
20
|
+
"status": "reserved",
|
|
21
|
+
"helperSymbol": "crypto",
|
|
22
|
+
"profileToken": "opaque-direct-use",
|
|
23
|
+
"reasonCode": "PULSE_PACKAGE_COMPOSITION_HELPER_NOT_REALIZED"
|
|
24
|
+
},
|
|
25
|
+
"targets": {
|
|
26
|
+
"native": {
|
|
27
|
+
"status": "provider-dependent",
|
|
28
|
+
"realization": "canonical-package-lowering",
|
|
29
|
+
"lowerableSubpath": "@pulse-compute/crypto",
|
|
30
|
+
"providerRequirements": [
|
|
31
|
+
"crypto.digestText"
|
|
32
|
+
],
|
|
33
|
+
"reasonCode": "PULSE_PACKAGE_PROVIDER_CAPABILITY_REQUIRED"
|
|
34
|
+
},
|
|
35
|
+
"javascript": {
|
|
36
|
+
"status": "supported",
|
|
37
|
+
"realization": "javascript-package-runtime",
|
|
38
|
+
"entry": "./dist/index.js",
|
|
39
|
+
"providerRequirements": [
|
|
40
|
+
"crypto.digestText"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"lowering": {
|
|
45
|
+
"manifest": "./pulsewasm.manifest.cjs",
|
|
46
|
+
"trust": "first-party",
|
|
47
|
+
"canonicalOperationVersion": "pulse.canonical-package-operation.v1",
|
|
48
|
+
"bundleVersion": "pulse.package-lowering-bundle.v1",
|
|
49
|
+
"providerRequirementVersion": "pulse.provider-requirement-record.v1"
|
|
50
|
+
},
|
|
51
|
+
"conformance": {
|
|
52
|
+
"status": "target-overlap",
|
|
53
|
+
"fixtureRoots": [
|
|
54
|
+
"./conformance"
|
|
55
|
+
],
|
|
56
|
+
"semanticCases": [
|
|
57
|
+
"crypto.digestText"
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"docs": {
|
|
61
|
+
"readme": "./README.md",
|
|
62
|
+
"contract": "./pulse.package.json"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { normalizeDiagnostic, sourceLoc } = require('@pulse-compute/wasm-contracts/diagnostics');
|
|
3
|
+
const { CANONICAL_PACKAGE_EFFECT_VERSION } = require('@pulse-compute/wasm-contracts/handler/canonical-runtime');
|
|
4
|
+
const { PACKAGE_CRYPTO_REQUIREMENT_VERSION } = require('@pulse-compute/wasm-contracts/package/package-contract');
|
|
5
|
+
const PACKAGE = '@pulse-compute/crypto';
|
|
6
|
+
function buildCryptoLoweringPlan(input = {}) {
|
|
7
|
+
const ts = input.typescript || require('typescript');
|
|
8
|
+
const source = input.sourceFile || ts.createSourceFile(input.sourcePath || 'index.ts', input.sourceText || '', ts.ScriptTarget.ES2022, true);
|
|
9
|
+
const namespaces = new Set(), direct = new Set(), diagnostics = [], effects = [];
|
|
10
|
+
const error = (node, suffix, message) => diagnostics.push(normalizeDiagnostic({
|
|
11
|
+
phase: 'crypto-lowering', severity: 'error', code: `PULSEWASM_CRYPTO_DIGEST_${suffix}`, message,
|
|
12
|
+
hint: 'Await crypto.digestText(ctx, text) into a local variable or use it as a direct keyed ctx.parallel member.', loc: sourceLoc(source, node)
|
|
13
|
+
}));
|
|
14
|
+
for (const statement of source.statements) {
|
|
15
|
+
if (!ts.isImportDeclaration(statement) || statement.moduleSpecifier.text !== PACKAGE || statement.importClause?.isTypeOnly) continue;
|
|
16
|
+
if (statement.importClause?.name) namespaces.add(statement.importClause.name.text);
|
|
17
|
+
const named = statement.importClause?.namedBindings;
|
|
18
|
+
if (named && ts.isNamedImports(named)) for (const entry of named.elements) {
|
|
19
|
+
if (entry.isTypeOnly) continue;
|
|
20
|
+
const name = (entry.propertyName || entry.name).text;
|
|
21
|
+
if (name === 'crypto') namespaces.add(entry.name.text);
|
|
22
|
+
if (name === 'digestText') direct.add(entry.name.text);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function checkBinding(name) {
|
|
26
|
+
if (ts.isIdentifier(name)) {
|
|
27
|
+
if (namespaces.has(name.text) || direct.has(name.text)) error(name, 'SHADOWED', 'Imported Crypto bindings cannot be shadowed.');
|
|
28
|
+
} else if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
|
|
29
|
+
for (const element of name.elements) if (ts.isBindingElement(element)) checkBinding(element.name);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function isDigest(expression) {
|
|
33
|
+
return ts.isIdentifier(expression) && direct.has(expression.text)
|
|
34
|
+
|| ts.isPropertyAccessExpression(expression) && expression.name.text === 'digestText'
|
|
35
|
+
&& ts.isIdentifier(expression.expression) && namespaces.has(expression.expression.text);
|
|
36
|
+
}
|
|
37
|
+
// Verification remains an ordinary JavaScript API. Its existing facade
|
|
38
|
+
// aliases and helpers must not acquire digest-specific authoring restrictions.
|
|
39
|
+
let hasDigestUse = false;
|
|
40
|
+
function findDigestUse(node) {
|
|
41
|
+
if (ts.isImportDeclaration(node) || ts.isTypeNode(node)) return;
|
|
42
|
+
if (isDigest(node)
|
|
43
|
+
|| ts.isPropertyAccessExpression(node) && node.name.text === 'digestText'
|
|
44
|
+
|| ts.isElementAccessExpression(node) && node.argumentExpression && ts.isStringLiteralLike(node.argumentExpression) && node.argumentExpression.text === 'digestText') hasDigestUse = true;
|
|
45
|
+
ts.forEachChild(node, findDigestUse);
|
|
46
|
+
}
|
|
47
|
+
findDigestUse(source);
|
|
48
|
+
function visit(node) {
|
|
49
|
+
if (ts.isTypeNode(node)) return;
|
|
50
|
+
if ((ts.isVariableDeclaration(node) || ts.isParameter(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) && node.name) checkBinding(node.name);
|
|
51
|
+
if (ts.isIdentifier(node) && namespaces.has(node.text)
|
|
52
|
+
&& !ts.isImportClause(node.parent) && !ts.isImportSpecifier(node.parent)
|
|
53
|
+
&& !(ts.isPropertyAccessExpression(node.parent))) error(node, 'DETACHED', 'The Crypto facade cannot be detached or accessed dynamically.');
|
|
54
|
+
if (isDigest(node) && !ts.isImportSpecifier(node.parent)
|
|
55
|
+
&& !(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)
|
|
56
|
+
&& (!ts.isCallExpression(node.parent) || node.parent.expression !== node)) error(node, 'DETACHED', 'Digest operations cannot be detached or aliased.');
|
|
57
|
+
if (ts.isCallExpression(node) && isDigest(node.expression)) {
|
|
58
|
+
let fn = node.parent;
|
|
59
|
+
while (fn && !ts.isArrowFunction(fn) && !ts.isFunctionExpression(fn)) fn = fn.parent;
|
|
60
|
+
const ctx = fn && fn.parameters[0] && fn.parameters[0].name;
|
|
61
|
+
if (node.arguments.length !== 2 || !ctx || !ts.isIdentifier(ctx)
|
|
62
|
+
|| !ts.isIdentifier(node.arguments[0]) || node.arguments[0].text !== ctx.text) {
|
|
63
|
+
error(node, 'ARGUMENTS', 'Digest requires the current handler context and one text input.'); return;
|
|
64
|
+
}
|
|
65
|
+
let placement = '';
|
|
66
|
+
const parent = node.parent;
|
|
67
|
+
if (ts.isAwaitExpression(parent) && ts.isVariableDeclaration(parent.parent)) placement = 'variable';
|
|
68
|
+
if (ts.isPropertyAssignment(parent) && ts.isObjectLiteralExpression(parent.parent)) {
|
|
69
|
+
const call = parent.parent.parent;
|
|
70
|
+
if (ts.isCallExpression(call) && ts.isPropertyAccessExpression(call.expression)
|
|
71
|
+
&& ts.isIdentifier(call.expression.expression) && call.expression.expression.text === ctx.text
|
|
72
|
+
&& call.expression.name.text === 'parallel' && ts.isAwaitExpression(call.parent)) placement = 'statement';
|
|
73
|
+
}
|
|
74
|
+
if (!placement) { error(node, 'PLACEMENT', 'Digest must be directly awaited or grouped.'); return; }
|
|
75
|
+
effects.push({ version: CANONICAL_PACKAGE_EFFECT_VERSION, contractId: 'pulse.crypto', package: PACKAGE, import: PACKAGE,
|
|
76
|
+
kind: 'crypto.digestText', capability: 'crypto.digestText', result: 'text-digest-result', providerKind: 'crypto', operation: 'digestText', placement,
|
|
77
|
+
range: { start: node.getStart(source), end: node.getEnd() }, resource: {}, payload: {},
|
|
78
|
+
runtimeInputs: [{ name: 'text', argumentIndex: 1, source: 'package-call-argument' }],
|
|
79
|
+
providerRequirements: ['crypto.digestText'], schemaReferences: [], redaction: ['text', 'sha256'], loc: sourceLoc(source, node) });
|
|
80
|
+
}
|
|
81
|
+
ts.forEachChild(node, visit);
|
|
82
|
+
}
|
|
83
|
+
if (hasDigestUse) visit(source);
|
|
84
|
+
const cryptoRequirements = effects.length ? [{ version: PACKAGE_CRYPTO_REQUIREMENT_VERSION, algorithms: ['SHA-256'], reachable: true, requestedBy: PACKAGE, semanticOwner: PACKAGE }] : [];
|
|
85
|
+
return { artifact: { version: 'pulse.crypto-text-lowering-plan.v1', contractId: 'pulse.crypto', package: PACKAGE, lowerableSubpath: PACKAGE,
|
|
86
|
+
status: diagnostics.length ? 'error' : 'ok', canonicalEffects: effects, cryptoRequirements },
|
|
87
|
+
canonicalEffects: effects, cryptoRequirements, diagnostics, hasErrors: diagnostics.length > 0 };
|
|
88
|
+
}
|
|
89
|
+
module.exports = { buildCryptoLoweringPlan };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
module.exports = Object.freeze({
|
|
3
|
+
version: 'pulsewasm.lowerable-library-manifest.v2', kind: 'pulsewasm.lowerable-library-manifest',
|
|
4
|
+
contractId: 'pulse.crypto', npmPackage: '@pulse-compute/crypto', lowerableSubpath: '@pulse-compute/crypto',
|
|
5
|
+
facade: { namespace: 'crypto', import: '@pulse-compute/crypto', symbols: ['digestText'] },
|
|
6
|
+
publicApi: { package: '@pulse-compute/crypto', symbols: ['crypto', 'digestText'], compatibilitySubpaths: [], loweringIntentionallyNarrow: true },
|
|
7
|
+
compiler: { version: 'pulsewasm.lowerable-compiler-builder.v1', entry: './pulsewasm.compiler.cjs', export: 'buildCryptoLoweringPlan', artifact: 'crypto-lowering-plan.json', builderOwner: '@pulse-compute/crypto', trust: 'first-party', inputs: ['typescript.SourceFile'] },
|
|
8
|
+
modes: {
|
|
9
|
+
typescript: { entry: './dist/index.js' }, jsEngine: { entry: './dist/index.js', behavior: 'provider-dependent' },
|
|
10
|
+
wasm: { mode: 'wasm-sidecar', sidecar: './as/digest-text.as.ts',
|
|
11
|
+
lowerings: [{ tsSymbol: 'crypto.digestText', asSymbol: '__pulse_crypto_digest_text', callShape: 'context-runtime-text', hostCapabilities: ['result'] }],
|
|
12
|
+
hostCapabilities: ['result'] }
|
|
13
|
+
},
|
|
14
|
+
policy: { packageOwnsFacadeMapping: true, canonicalRootLowering: true, automaticFallback: false }
|
|
15
|
+
});
|
package/pulsewasm.native.cjs
CHANGED
|
@@ -45,6 +45,7 @@ function pulseHmacAssemblyScriptSource(algorithm = 'HS256') {
|
|
|
45
45
|
'pulse_crypto_hs256_verify',
|
|
46
46
|
'pulse_crypto_sha256_digest',
|
|
47
47
|
'pulse_crypto_bytes_frame_v1',
|
|
48
|
+
'pulse_crypto_digest_frame_v1',
|
|
48
49
|
'pulse_crypto_sha256_bytes_v1',
|
|
49
50
|
'pulse_crypto_hmac_sha256_bytes_v1'
|
|
50
51
|
]),
|
|
@@ -55,8 +56,10 @@ function pulseHmacAssemblyScriptSource(algorithm = 'HS256') {
|
|
|
55
56
|
hs256TagBytes: 32,
|
|
56
57
|
byteOperationKeyBytesMaximum: 8192,
|
|
57
58
|
byteOperationDataBytesMaximum: 32768,
|
|
59
|
+
digestDataBytesMaximum: 2097152,
|
|
58
60
|
byteOperationOutputBytes: 32,
|
|
59
|
-
byteOperationFrameBytes: 40992
|
|
61
|
+
byteOperationFrameBytes: 40992,
|
|
62
|
+
digestFrameBytes: 2097184
|
|
60
63
|
}),
|
|
61
64
|
resultCodes: Object.freeze({
|
|
62
65
|
valid: 1,
|
|
@@ -76,20 +79,23 @@ function pulseHmacAssemblyScriptSource(algorithm = 'HS256') {
|
|
|
76
79
|
|
|
77
80
|
function bindNativeDigestMac(moduleExports) {
|
|
78
81
|
const memory = moduleExports && moduleExports.memory;
|
|
79
|
-
const names = ['pulse_crypto_bytes_frame_v1', 'pulse_crypto_sha256_bytes_v1', 'pulse_crypto_hmac_sha256_bytes_v1'];
|
|
82
|
+
const names = ['pulse_crypto_bytes_frame_v1', 'pulse_crypto_digest_frame_v1', 'pulse_crypto_sha256_bytes_v1', 'pulse_crypto_hmac_sha256_bytes_v1'];
|
|
80
83
|
if (!(memory instanceof WebAssembly.Memory) || names.some((name) => typeof moduleExports[name] !== 'function')) {
|
|
81
84
|
throw new TypeError('Native Crypto digest/MAC exports are unavailable.');
|
|
82
85
|
}
|
|
83
|
-
const frameBytes = 8192 + 32768 + 32;
|
|
84
86
|
function run(data, key) {
|
|
85
|
-
if (!(data instanceof Uint8Array) || data.length > 32768
|
|
87
|
+
if (!(data instanceof Uint8Array) || data.length > (key === undefined ? 2097152 : 32768)
|
|
86
88
|
|| (key !== undefined && (!(key instanceof Uint8Array) || key.length > 8192))) {
|
|
87
89
|
throw new TypeError('Native Crypto digest/MAC input exceeds its byte contract.');
|
|
88
90
|
}
|
|
89
|
-
const
|
|
91
|
+
const largeDigest = key === undefined && data.length > 32768;
|
|
92
|
+
const dataBytes = largeDigest ? 2097152 : 32768;
|
|
93
|
+
const keyBytes = largeDigest ? 0 : 8192;
|
|
94
|
+
const frameBytes = keyBytes + dataBytes + 32;
|
|
95
|
+
const pointer = (largeDigest ? moduleExports.pulse_crypto_digest_frame_v1() : moduleExports.pulse_crypto_bytes_frame_v1()) >>> 0;
|
|
90
96
|
if (pointer + frameBytes > memory.buffer.byteLength) throw new TypeError('Native Crypto frame is outside memory.');
|
|
91
|
-
const dataPointer = pointer +
|
|
92
|
-
const outputPointer = dataPointer +
|
|
97
|
+
const dataPointer = pointer + keyBytes;
|
|
98
|
+
const outputPointer = dataPointer + dataBytes;
|
|
93
99
|
try {
|
|
94
100
|
const view = new Uint8Array(memory.buffer);
|
|
95
101
|
view.set(data, dataPointer);
|
package/src/provider.cjs
CHANGED
|
@@ -19,7 +19,7 @@ function bindJavascriptDigestMac(algorithms, subtle = globalThis.crypto && globa
|
|
|
19
19
|
|| algorithms.includes('HMAC-SHA256') && (typeof subtle.sign !== 'function' || typeof subtle.importKey !== 'function')) throw new TypeError('Selected Web Crypto byte realization is unavailable.');
|
|
20
20
|
const bytes = {};
|
|
21
21
|
if (algorithms.includes('SHA-256')) bytes.sha256 = async (input) => {
|
|
22
|
-
const data = snapshot(input,
|
|
22
|
+
const data = snapshot(input, 2097152);
|
|
23
23
|
try { return result(await subtle.digest({ name: 'SHA-256' }, data)); }
|
|
24
24
|
finally { data.fill(0); }
|
|
25
25
|
};
|
|
@@ -40,4 +40,73 @@ function bindJavascriptDigestMac(algorithms, subtle = globalThis.crypto && globa
|
|
|
40
40
|
algorithms: Object.freeze(algorithms.map((algorithm) => Object.freeze({ algorithm, realization: 'runtime-builtin', implementation: IMPLEMENTATIONS[algorithm], available: true, automaticFallback: false })))
|
|
41
41
|
}) });
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
const DIGEST_TEXT_MAX_BYTES = 2097152;
|
|
44
|
+
const digestFailures = Object.freeze(Object.fromEntries(
|
|
45
|
+
['invalid-text', 'too-large', 'unavailable', 'realization-failure'].map(reason => [reason, Object.freeze({ status: 'failed', reason })])
|
|
46
|
+
));
|
|
47
|
+
// Count scalar UTF-8 bytes before allocating staging; do not replace surrogates.
|
|
48
|
+
function textDigestByteLength(text) {
|
|
49
|
+
if (typeof text !== 'string') return -1;
|
|
50
|
+
if (text.length > DIGEST_TEXT_MAX_BYTES) return -2;
|
|
51
|
+
let bytes = 0;
|
|
52
|
+
for (let i = 0; i < text.length; i++) {
|
|
53
|
+
const c = text.charCodeAt(i);
|
|
54
|
+
if (c >= 0xd800 && c <= 0xdbff) {
|
|
55
|
+
const low = text.charCodeAt(++i);
|
|
56
|
+
if (!(low >= 0xdc00 && low <= 0xdfff)) return -1;
|
|
57
|
+
bytes += 4;
|
|
58
|
+
} else if (c >= 0xdc00 && c <= 0xdfff) return -1;
|
|
59
|
+
else bytes += c < 0x80 ? 1 : c < 0x800 ? 2 : 3;
|
|
60
|
+
}
|
|
61
|
+
return bytes > DIGEST_TEXT_MAX_BYTES ? -2 : bytes;
|
|
62
|
+
}
|
|
63
|
+
function normalizeTextDigestResult(value) {
|
|
64
|
+
const failed = digestFailures['realization-failure'];
|
|
65
|
+
if (!value || typeof value !== 'object' || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return failed;
|
|
66
|
+
const properties = Object.getOwnPropertyDescriptors(value), keys = Reflect.ownKeys(properties);
|
|
67
|
+
if (keys.some(key => typeof key !== 'string' || !properties[key].enumerable || !Object.hasOwn(properties[key], 'value'))) return failed;
|
|
68
|
+
const status = properties.status?.value, reason = properties.reason?.value;
|
|
69
|
+
if (status === 'failed' && keys.length === 2 && typeof reason === 'string' && Object.hasOwn(digestFailures, reason)) return digestFailures[reason];
|
|
70
|
+
const sha256 = properties.sha256?.value, byteLength = properties.byteLength?.value;
|
|
71
|
+
if (status !== 'ok' || keys.length !== 3 || typeof sha256 !== 'string' || !/^[a-f0-9]{64}$/.test(sha256)
|
|
72
|
+
|| !Number.isInteger(byteLength) || byteLength < 0 || byteLength > DIGEST_TEXT_MAX_BYTES) return failed;
|
|
73
|
+
return Object.freeze({ status: 'ok', sha256, byteLength });
|
|
74
|
+
}
|
|
75
|
+
function checkDigestCancellation(signal) {
|
|
76
|
+
if (signal?.aborted) throw signal.reason || new Error('Pulse digest invocation cancelled.');
|
|
77
|
+
}
|
|
78
|
+
async function executeTextDigest(effect, options = {}) {
|
|
79
|
+
if (!effect || effect.package !== '@pulse-compute/crypto' || effect.contractId !== 'pulse.crypto'
|
|
80
|
+
|| effect.providerKind !== 'crypto' || effect.operation !== 'digestText'
|
|
81
|
+
|| effect.kind !== 'crypto.digestText' || effect.capability !== 'crypto.digestText') throw new TypeError('Invalid Crypto digest effect authority.');
|
|
82
|
+
checkDigestCancellation(options.signal);
|
|
83
|
+
const text = effect.payload?.text, byteLength = textDigestByteLength(text);
|
|
84
|
+
if (byteLength < 0) return digestFailures[byteLength === -1 ? 'invalid-text' : 'too-large'];
|
|
85
|
+
const realization = options.cryptoTarget === 'javascript' ? 'runtime-builtin' : 'guest-source:pulse-hmac-as';
|
|
86
|
+
const bytes = options.cryptoVerifier?.bytes, algorithms = options.cryptoRealization?.algorithms;
|
|
87
|
+
if (typeof bytes?.sha256 !== 'function' || !Array.isArray(algorithms)
|
|
88
|
+
|| !algorithms.some(entry => entry.algorithm === 'SHA-256' && entry.available === true && entry.realization === realization)) return digestFailures.unavailable;
|
|
89
|
+
const data = new TextEncoder().encode(text);
|
|
90
|
+
let digest;
|
|
91
|
+
try {
|
|
92
|
+
digest = await bytes.sha256(data);
|
|
93
|
+
checkDigestCancellation(options.signal);
|
|
94
|
+
if (!(digest instanceof Uint8Array) || digest.length !== 32) return digestFailures['realization-failure'];
|
|
95
|
+
const sha256 = Array.from(digest, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
96
|
+
return Object.freeze({ status: 'ok', sha256, byteLength });
|
|
97
|
+
} catch (error) {
|
|
98
|
+
checkDigestCancellation(options.signal);
|
|
99
|
+
return digestFailures['realization-failure'];
|
|
100
|
+
} finally { data.fill(0); if (digest instanceof Uint8Array) digest.fill(0); }
|
|
101
|
+
}
|
|
102
|
+
function createJavascriptTextDigest(options = {}) {
|
|
103
|
+
return (effect, execution = {}) => {
|
|
104
|
+
let selected;
|
|
105
|
+
try { selected = bindJavascriptDigestMac(['SHA-256'], options.digestSubtle === undefined ? globalThis.crypto?.subtle : options.digestSubtle); }
|
|
106
|
+
catch { selected = undefined; }
|
|
107
|
+
return executeTextDigest(effect, { signal: execution.signal, cryptoTarget: 'javascript',
|
|
108
|
+
cryptoVerifier: selected, cryptoRealization: selected?.realization });
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
module.exports = Object.freeze({ IMPLEMENTATIONS, bindJavascriptDigestMac, DIGEST_TEXT_MAX_BYTES,
|
|
112
|
+
textDigestByteLength, normalizeTextDigestResult, executeTextDigest, createJavascriptTextDigest });
|