npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@synoi/sraid

v0.4.0

Published

SRAID reference implementation: canonical JSON serializer, content-addressed OIDs, hybrid Ed25519 + ML-DSA-65 signature verification, lineage, and shape validators. No storage, no transport, no policy.

Readme

@synoi/sraid

Reference TypeScript implementation of SRAID, the SynOI content-addressed object format. MIT-licensed.

SRAID is a name, not an acronym. It has been expanded two different ways in two different places; neither expansion added anything, so this package no longer expands it. What it does is below.

SRAID defines:

  • a content-addressed object envelope, the CDRO (Canonical Data-Rich Object),
  • a deterministic canonical object serializer used as input to hashing and signing,
  • a content-derived OID (sha256: followed by 64 hex chars) over the canonical bytes,
  • a hybrid Ed25519 + ML-DSA-65 signature envelope,
  • a Merkle-DAG lineage layer (prev edge + typed links[]), hashed into the OID so a head id commits its whole reachable history.

Supersession is expressed by two things, both identity-bound because cdroContentCore hashes them: the prev/links[] Merkle edges, and the legacy self-asserted supersedes string. latestWins resolves a version set to its single head by following the identity-bound edges, giving a verifier a latest-wins / monotone rule instead of an unwitnessed pointer. Given the complete version set it will not name a superseded object as head; full rollback-replay resistance additionally requires that the superseding object cannot be withheld, which is a Resolver / transparency-log property (DESIGN, not yet deployed). set_complete on LatestWinsResult is the local signal letting a caller detect an incomplete (potentially withheld) set.

The standalone SRO was removed in 0.4.0 — see the CHANGELOG.

This package is object identity: canonical bytes, OIDs, hybrid signature verification, lineage. It has no storage, no HTTP surface, and no policy.

It verifies signatures, not identities: signer_kid is an opaque string this package never resolves, and nothing here checks revocation. Deciding whether a key was trusted at signing time needs a key directory, which is a caller concern.

Install

npm install @synoi/sraid

Minimal example

import {
  canonicalize,
  cdroOid,
  cdroContentCore,
  pae,
  verifyAttestation,
  validateCdro,
  type AttestationEnvelope,
  type CDRO,
} from '@synoi/sraid'
import { ed25519 } from '@noble/curves/ed25519'
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js'
import { randomBytes } from 'node:crypto'

// 1. Build the CDRO content core: every field EXCEPT the detached
//    envelope fields (oid, signature, attestation, ...).
const core = {
  type: 'gap:capability_declaration',
  sraid_version: '2.0' as const,
  tenant_id: 't-home',
  created_at_ms: Date.now(),
  created_by: 'actor:skill:demo',
  body: { capability: 'door.unlock', risk_class: 'B' },
}

// 2. Identity is the hash of the WHOLE content core, not just the body.
//    Use cdroOid(core) — NOT oidOf(core.body).
const cdro: CDRO<typeof core.body> = { oid: cdroOid(core), ...core }

console.log(validateCdro(cdro))            // { ok: true, errors: [] }
console.log(cdro.oid === cdroOid(cdro))    // true

// 3. Sign the canonical content core through the DSSE PAE, which binds
//    payloadType into the signed bytes (prevents cross-type replay).
const payloadType = 'application/vnd.synoi.sraid+json'
const payload = canonicalize(cdroContentCore(cdro))
const signedBytes = pae(payloadType, payload)

const edPriv = new Uint8Array(randomBytes(32))
const edPub = ed25519.getPublicKey(edPriv)
const mlKeys = ml_dsa65.keygen(new Uint8Array(randomBytes(32)))

const attestation: AttestationEnvelope = {
  payloadType,
  payload,
  signatures: [
    {
      alg: 'ed25519',
      sig: Buffer.from(ed25519.sign(signedBytes, edPriv)).toString('base64'),
      keyid: 'synoi-demo-2026-05',
    },
    {
      alg: 'ml-dsa-65',
      sig: Buffer.from(ml_dsa65.sign(signedBytes, mlKeys.secretKey)).toString('base64'),
      keyid: 'synoi-demo-2026-05',
    },
  ],
}

// 4. Attaching the attestation does NOT change identity — the six detached
//    envelope fields are stripped before hashing.
const attested = { ...cdro, attestation }
console.log(cdroOid(attested) === cdro.oid) // true

// 5. Verify. `valid` is true only when BOTH signatures verify over the PAE.
const v = verifyAttestation({
  envelope: attestation,
  ed25519_pub: edPub,
  ml_dsa_pub: mlKeys.publicKey,
  expectedPayloadType: payloadType,
})
console.log(v)                             // { valid: true, reasons: [] }

Identity is over the content core, not the body. oidOf(cdro.body) is not a CDRO's OID; use cdroOid. verifySignature / SignatureEnvelope (bare-bytes, no payload-type binding) remain exported for compatibility but are superseded by the attestation path above.

Surface

canonicalize(value: unknown): string

// CDRO identity — hash the content core. THIS is an object's OID.
cdroOid(cdro: unknown): string
cdroContentCore(cdro: unknown): Record<string, unknown>
CDRO_ENVELOPE_FIELDS: readonly string[]   // the six stripped fields

// Value-level hashing. oidOf(cdro.body) is NOT the CDRO's OID.
oidOf(canonical: unknown): string
oidOfCanonical(canonical: string | Uint8Array): string

// DSSE attestation — payload-type bound, both signatures required.
verifyAttestation(input): { valid: boolean; reasons: string[] }
pae(payloadType: string, payload: string | Uint8Array): Uint8Array

verifySignature(args: {
  canonical:   string | Uint8Array
  envelope:    SignatureEnvelope
  ed25519_pub: Uint8Array
  ml_dsa_pub:  Uint8Array
}): { valid: boolean; reasons: string[] }

validateCdro(x: unknown): { ok: boolean; errors: string[] }
validateSro(x: unknown): { ok: boolean; errors: string[] }
validateSignatureEnvelope(x: unknown): { ok: boolean; errors: string[] }

// Types
interface CDRO<TBody>            { /* envelope */ }
interface SignatureEnvelope      { ed25519: string; ml_dsa_65: string; signer_kid: string }

Browser, Chrome extension, service worker

The default (.) entry statically imports node:crypto in three places (ed25519.ts, mldsa.ts, oid.ts), so import '@synoi/sraid' breaks any browser bundle. Import the ./verify-browser subpath instead. It carries exactly what a v2 hybrid DSSE receipt verifier needs, with no static node:crypto anywhere in its graph.

import { verifyAttestation, canonicalize, cdroContentCore } from '@synoi/sraid/verify-browser'

const v = await verifyAttestation({
  envelope:            receipt.attestation,
  ed25519_pub,                                   // raw 32 bytes
  ml_dsa_pub,                                    // raw 1952 bytes
  expectedPayloadType: 'application/vnd.synoi.gap+json',
})
// { valid: true, reasons: [] } only when BOTH signatures verify over the PAE
// The subpath surface. Four of these are ASYNC where the node entry is sync,
// because WebCrypto is Promise-based. Everything else is shared byte-for-byte
// with the node entry, so the two agree on the same input.
verifyAttestation(input): Promise<{ valid: boolean; reasons: string[] }>   // ASYNC
cdroOid(cdro: unknown): Promise<string>                                    // ASYNC
oidOf(canonical: unknown): Promise<string>                                 // ASYNC
oidOfCanonical(canonical: string | Uint8Array): Promise<string>            // ASYNC

canonicalize(value: unknown): string          // pure, identical to the node entry
cdroContentCore(cdro: unknown): Record<string, unknown>
pae(payloadType: string, payload: string | Uint8Array): Uint8Array
CDRO_ENVELOPE_FIELDS: readonly string[]
ALG_ED25519, ALG_ML_DSA_65

Crypto backends, since browsers have no native ML-DSA and no node:crypto: Ed25519 verify on WebCrypto (RFC 8032 cofactored, matching the node path) with a @noble/curves fallback below the WebCrypto-Ed25519 support floor; ML-DSA-65 verify on @noble/post-quantum; SHA-256 on WebCrypto subtle.digest.

The node default entry is unchanged and keeps its synchronous node:crypto fast paths. This subpath is purely additive. Added in 0.3.0; earlier versions export only . and ./canonicalize.

Authority verification

Capability-grant and delegation-chain verification is NOT in this package. It is authorization policy, not object identity, and it moved to @synoi/authority-verify in 0.4.0.

capabilityCovers, AuthorityResolver and GrantStatus did NOT move - they are a pure string predicate and a live-status contract, and remain in the surface above.

Canonical form

The canonical form is a strict RFC 8785 (JCS) profile, tested against the RFC 8785 vectors (test/rfc8785-conformance.test.ts):

  • primitives via JSON.stringify,
  • objects emit keys in lexicographic order,
  • undefined properties are omitted,
  • arrays preserve order,
  • no whitespace.

This canonical form is a wire contract. Any byte-level change to the serializer changes every OID and invalidates every previously-created signature. Treat it as frozen for SRAID v2.0; evolve it only via a new, explicitly versioned canonical profile.

OID format

OID = "sha256:" + lowercase_hex( sha256( canonicalize(content) ) )

The hash input is the object MINUS the six detached envelope fields (oid, signature, ml_dsa_signature, signature_key_id, signature_algorithm, attestation) — see CDRO_ENVELOPE_FIELDS. Everything else is hashed, including authority, sensitivity, prev, links and supersedes, so those are identity-bound and cannot be silently stripped. Signing or rotating a signature never changes the OID.

Why hybrid signatures

SRAID objects are signed with both Ed25519 and ML-DSA-65. The classical Ed25519 path is fast and ubiquitously verifiable today; the post-quantum ML-DSA-65 path future-proofs receipts against quantum attacks. verifySignature in this package returns valid: true only when BOTH succeed.

License

MIT, see LICENSE.