sar-envelope
v0.1.0
Published
Portable SAR (Settlement Attestation Receipt) v0.1 envelope primitives: JCS canonicalization, receipt ID derivation, Ed25519 signature-input construction, and .well-known/sar-keys.json key discovery.
Maintainers
Readme
sar-envelope (JS)
Envelope primitives for the portable SAR v0.1 signed core — the
six-field task_id_hash, verdict, confidence, reason_code, ts,
verifier_kid contract used across SAR (Settlement Attestation Receipt)
implementations, including independent implementations on non-EVM/non-
wallet rails.
This package implements exactly four things:
- JCS (RFC 8785) canonicalization —
canonicalize(value) -> Uint8Array. - Deterministic receipt/claim ID derivation —
receiptId(core) = "sha256:" + hex(sha256(JCS(core))). - Ed25519 signature-input construction —
signingInput(core)returns the exact 32 bytes an Ed25519 key signs (the sha256 digest of the JCS-canonicalized core, not the raw canonical JSON bytes). - Key discovery against a published
.well-known/sar-keys.jsonregistry (JWK format) —parseKeyRegistry,resolveKey,fetchKeyRegistry, includingkidresolution and algorithm binding (onlykty=OKP, crv=Ed25519is recognized).
It does not verify signatures, run a trust registry, implement a CLI,
or encode any issuer-specific extension (e.g. a wallet-bound counterparty
binding). counterparty and _ext are never part of the six-field
signed core this package computes over.
Requires Node 19+ (uses the global crypto.subtle and fetch); also
runs unmodified in a browser.
Install
npm install sar-envelopeExample
This reproduces fixture 01_valid_portable from the published conformance
corpus (see Fixtures below) byte-for-byte:
import { extractCore, receiptId, signingInput } from "sar-envelope";
const receipt = {
receipt_version: "0.1",
task_id_hash: "sha256:fixture-portable-task-0001",
verdict: "PASS",
confidence: 1,
reason_code: "SPEC_MATCH",
ts: "2026-07-23T00:00:00Z",
verifier_kid: "fixture-portable-kid-01",
sig_alg: "Ed25519",
};
const core = extractCore(receipt);
const rid = await receiptId(core);
console.assert(
rid === "sha256:be6c760480f4127a842d3321774680531caa4a9acf16684c067086f83a8d8b6f"
);
// The bytes an Ed25519 private key signs for this receipt:
const digest = await signingInput(core); // 32 bytes; receiptId hex-encodes this same valueKey discovery against the real published registry format:
import { fetchKeyRegistry, resolveKey } from "sar-envelope";
const registry = await fetchKeyRegistry("https://defaultverifier.com/.well-known/sar-keys.json");
const entry = resolveKey(registry, "sar-prod-ed25519-01");
entry.pubkey; // raw 32-byte Ed25519 public key (Uint8Array)
entry.alg; // "Ed25519" — only OKP/Ed25519 registry entries are returnedFixtures
The test suite in this package runs against the canonical Portable SAR
v0.1 conformance corpus, bundled at fixtures/portable-sar-fixtures.json
and fixtures/portable-sar-fixture-keys.json:
https://github.com/nutstrut/sar-envelope/tree/main/js/fixtures
Scope and versioning
Implements SAR v0.1, portable six-field signed-core profile only.
Pre-1.0 (0.1.0): the API may change before a 1.0 release. This package
computes canonical bytes, receipt IDs, and signature input deterministically
from data you supply — it does not itself authenticate a producer's
identity, prove human authority over an action, or provide any
end-to-end verification guarantee. Signature verification and trust-registry
policy (pinning, revocation, refresh) are the caller's responsibility and
are out of scope for this package.
