@pulse-compute/crypto
v1.0.0-beta.9
Published
Provider-neutral cryptographic verification and bounded exact-text digests for Pulse.
Readme
@pulse-compute/crypto
Support tier: Supported provider/extension surface Audience: Applications and first-party packages using provider-neutral verification and exact-text SHA-256. Install directly: Yes, when an application uses verification or digestText directly; JWT applications receive it transitively. Supported entry points:
@pulse-compute/cryptoStability: The package root supports bounded verification and request-owned exact-text digests; provider, manifest/compiler and Native integration subpaths remain toolchain-only. npm:@pulse-compute/cryptoCanonical documentation: Package guideThis release-status block is generated from the synchronized
Pulse 1.0.0-beta.9package policy.
Provider-neutral verification and bounded exact-text SHA-256 for Pulse.
This package and @pulse-compute/jwt are members of the synchronized
1.0.0-beta.9 release catalog.
Exact-text SHA-256
import { crypto } from '@pulse-compute/crypto'
const digest = await crypto.digestText(ctx, text)Select pulse.crypto: ['SHA-256']. The current HTTP context owns this effect;
use a directly awaited local variable or keyed ctx.parallel member.
Success is { status: 'ok', sha256, byteLength }: 64 lowercase hex characters
and the exact UTF-8 length. The public DIGEST_TEXT_MAX_BYTES is 2097152, empty
input is accepted, and unpaired surrogates are rejected without replacement.
No normalization, parsing, BOM stripping, or newline conversion occurs.
Failures are { status: 'failed', reason }, where reason is invalid-text,
too-large, unavailable, or realization-failure; cancellation terminates
the invocation. The named digestText export has the same contract.
Node/Fastly Native reuse the selected Crypto SHA-256 guest source. Node/Fastly JavaScript use selected Web Crypto. Neither falls back. This bound matches current S3 text capacity and leaves larger Catalog envelopes for capacity work. See the exact-text digest contract.
Verification contract
This package owns the verification surface while target planning owns backend selection. HS256 uses the MAC seam:
import { crypto } from '@pulse-compute/crypto'
const result = await crypto.mac.verify({
algorithm: 'HS256',
key: {
type: 'hmac-key-bytes',
bytes: secretBytes,
},
data: messageBytes,
tag: authenticatorBytes,
})Keys, messages, and authenticators are byte inputs. The package never performs ambient string encoding. HS256 keys must be between 32 bytes and 4 KiB, data is bounded to 1 MiB, and authenticators are exactly 32 bytes.
Verification returns one frozen, status-only result:
validinvalid-authenticatorinvalid-keyinvalid-inputrealization-failure
Backend objects, error text, key material, messages, and authenticators do not cross the result boundary.
G3 also activates ES256 through the signature seam. Its normalized request
contains an exact 64-byte P-256 public key (x || y), the original signing
input bytes, and an exact 64-byte JOSE signature (r || s). JavaScript keeps
Web Crypto key objects inside the runtime-builtin adapter. Native uses the
audited guest-linked:pulse-es256-rustcrypto-p256 unit. Both return the same
status-only result taxonomy, and neither retries another realization.
Pulse profiles can declare HS256 and ES256 globally or replace that declaration in a named profile:
pulse: {
crypto: ['HS256', 'ES256'],
},
prod: {
crypto: {
HS256: { realization: 'guest-source:pulse-hmac-as' },
ES256: { realization: 'guest-linked:pulse-es256-rustcrypto-p256' },
},
},An empty array or object explicitly declares no algorithms. Planning considers
only requirements emitted by reachable packages, resolves every declared
algorithm against the selected target, reports the exact realization, and
fails before lowering when the relationship
reachable demand ⊆ profile declaration ⊆ target capability is invalid.
Selection never enables detection or fallback.
JavaScript targets now execute the selected runtime-builtin realization
through Web Crypto with explicit HMAC/SHA-256 or ECDSA/P-256/SHA-256
parameters. ES256 validates the normalized point before constructing a
runtime-private public JWK, so malformed points cannot escape into a target's
Web Crypto implementation. Key import and verification stay inside the
byte-only public boundary; invalid authenticators, invalid keys, unavailable
runtimes, and operation failures return normalized status-only results.
The implementation imports no Node API and represents Node, browser, and
Fastly JavaScript uniformly as runtime-builtin. It is not a Fastly Native
host ABI.
The Native realization is guest-source:pulse-hmac-as: first-party
AssemblyScript compiled into the primary Native module. It implements bounded
SHA-256 and HMAC-SHA-256, scans every authenticator byte before branching, and
has no host imports, guest-link dependency, runtime probing, or fallback. Its
source provenance, hash, resource limits, exports, and disabled-fallback posture
are recorded in the Native manifest without recording key, data, or tag bytes.
The crypto seal uses one versioned corpus for the JavaScript Web Crypto and Native AssemblyScript realizations. The corpus includes published RFC 4231 vectors, authenticator mismatches at the first, middle, and last byte, altered inputs, invalid key and tag shapes, and empty, maximum, and over-limit messages. The same proof covers profile replacement without array or object merging, unavailable exact realization pins, target capability failures, normalized result categories, disabled fallback, and report redaction.
The Phase C seal records the locally observed artifact impact rather than treating it as a portable benchmark. With the repository's pinned toolchain, the default Native fixture grew from 2,212 to 5,385 bytes (3,173 bytes), and the experimental size profile grew from 2,058 to 4,951 bytes (2,893 bytes). The five emitted JavaScript runtime modules total 7,676 bytes, or 1,962 gzip bytes.
JWT composition
@pulse-compute/jwt depends directly on this package. It uses mac.verify for
HS256 and signature.verify for ES256. JWT owns compact-JWS, bounded public
JWK/JWKS selection, and claims semantics; crypto owns fixed-width byte
normalization, backend realization, and normalized authenticity status.
Neither package duplicates the other's responsibility, and JWT has no active
jose, Web Crypto, guest-direct, or provider-direct crypto path.
The G3 composition and shared ES256 corpus are recorded in
wasm/.test-results/jwt-g3/jwt-g3-evidence.json. This working-candidate proof
does not add signing, remote key discovery, fallback, publication, or
deployment.
Trusted byte-output composition
S3 uses Crypto-owned SHA-256 and HMAC-SHA256 byte outputs. Native composes the
existing guest-source unit; Node JavaScript explicitly binds Web Crypto through
@pulse-compute/crypto/provider. The trusted seam snapshots Uint8Array input,
allows at most 2097152 SHA-256 data bytes, 65536 HMAC data bytes and 8192 HMAC
key bytes, returns exactly 32 bytes,
and wipes input staging on success or failure. It rejects unavailable selected
primitives and does not fall back. Empty HMAC keys use the equivalent padded
zero block required by HMAC, preserving Native semantics despite Web Crypto's
zero-length import restriction. This provider seam does not add an author-facing
signing API or change JWT key limits.
Large hashes use a separate lazy 2,097,184-byte Native staging frame; small hashes up to 64 KiB and HMAC use the 73,760-byte frame (8 KiB key, 64 KiB data, 32-byte output). Primary-memory Native digest/S3 modules have a 256 MiB Wasm maximum. Fixed-memory linked guests retain their separate ABI and do not establish the 2 MiB text capacity profile.
RS256 signature contract
crypto.signature.verify accepts algorithm: 'RS256' and a
rsa-public-key-bytes key. normalizeRsaPublicKey({ n, e }) converts public
JWK integers into that representation; normalizeRs256JoseVerifyRequest
also decodes the canonical, fixed-width JOSE signature. JWT owns JWK metadata
and selection policy. No generic public signing API is introduced.
The normalized key is a little-endian 32-bit modulus byte length, the unsigned
big-endian modulus, and a four-byte big-endian public exponent. Supported
lengths are 256, 384, and 512 bytes; the modulus has its high bit set and is
odd. Exponents are odd integers in [3, 4294967295]. Signature width equals
modulus width, including any leading zero signature bytes. Data is at most
12288 bytes. Verification returns the existing closed status taxonomy.
The exact Native selection is guest-linked:pulse-rs256-bearssl-i31 with
bearssl.0.6.rsa-i31.sha256.v1. It shares the pinned ES256/RS256 prebuilt and
fixed 2 MiB memory with ES256. The private signing adapter accepts normalized
RSA/CRT bytes, validates their consistency, and checks every resulting
signature by public exponentiation before release. The caller erases its
16640-byte invocation frame and the borrowed 64 KiB guest stack on return.
The Node wrapper also clears both regions on a trap; a Fastly Native trap
aborts execution before in-Wasm cleanup can run. The guest has no retained
key state, entropy imports, allocation, or host callbacks. PKCS#1 v1.5
signing is deterministic and needs no nonce.
BearSSL i31 uses constant-time arithmetic rather than RSA blinding. Its
security assumptions include constant-time integer multiplication and a
compiler/engine that preserves the intended data-independent execution.
This is not a formal timing proof for every Wasm engine or CPU. The pinned
upstream source, license, archive hash, per-file hashes, compiler versions,
and reproducible build command ship with the guest. The historical guest
path and module name remain for compatibility; its ABI is now
pulse.crypto.es256-rs256.verify-and-sign.v3.
JavaScript explicitly selects RSASSA-PKCS1-v1_5 with SHA-256 in Web Crypto
and independently verifies signing output. Redundant private-field import
checks use JavaScript BigInt, which is not constant time; private
exponentiation and blinding belong to the selected Web Crypto runtime.
Immutable JWK strings and runtime-owned key objects cannot be explicitly
erased by Pulse. The provider retains its existing cancellation and
redaction boundaries.
The combined prebuilt is 36378 bytes. The development corpus observed 6324
bytes of guest stack writes within the reserved 65536 bytes. Local warmed
4096-bit operations took roughly 43 ms to sign and 1.5 ms to verify; these
are local Node Wasm observations, not performance guarantees or Fastly
measurements. crypto-rs256 regenerates the observations and tests independent
interoperability, malformed PKCS#1 encodings, CRT corruption, frame bounds,
and cleanup. jwt-rs256 covers the provider and CLI paths.
