Release Packaging
Witness releases are tag-driven. Use annotated version tags:
git tag -a v0.6.0 -m "Witness 0.6.0"
git push origin v0.6.0
Pushing a v* tag triggers the release workflow
(.forgejo/workflows/release.yml) which builds a native x86_64 Linux binary
archive and publishes it to the Forgejo releases page with SHA-256 checksums.
Public production deployments should pin version tags or image digests instead
of latest.
Release Artifacts
Each release includes one tarball:
witness-v0.6.0-x86_64-linux-gnu.tar.gz
Each tarball contains:
bin/witness-nodebin/witness-gatewaybin/witnessbin/witness-auditorconfigs/,docs/,examples/README.md,PRODUCTION.md,TESTING.md,SECURITY.md,CONTRIBUTING.md,CHANGELOG.md,LICENSE
A SHA256SUMS file is published alongside the tarballs.
Container Images
Container images are built separately by .forgejo/workflows/docker.yml on
pushes to main (not on tags). The workflow builds native linux/amd64 images:
git.carpocratian.org/sibyl/witness-node:<version>
git.carpocratian.org/sibyl/witness-gateway:<version>
Recommended tags:
- full version, such as
0.6.0 - minor version, such as
0.6 - commit SHA for every build
Production deployments should pin a version tag or digest.
The Docker workflow builds linux/amd64 images with BuildKit provenance and
SBOM attestations. Image signing requires COSIGN_PRIVATE_KEY and signs the
pushed manifest digest with cosign.
GitHub Mirror
The Forgejo repository mirrors to GitHub, but only git data syncs โ branches, tags, and commits. Release objects and binary assets are not mirrored. Download binaries from the Forgejo releases page.
Notable behavior changes
-
AttestationSignaturesstrict discrimination (spec ยง3.5): deserialization now rejects ambiguous payloads (those carrying both the multi-sigsignatureskey and either aggregated key) as aDecodeError, instead of the previous#[serde(untagged)]first-variant-wins acceptance asMultiSig.Serializeoutput is byte-identical; only pathological inbound payloads are affected. Golden vectors regenerated (wire.jsonversion 2). Security-sensitive. -
Secret-free verification config:
GET /v1/networkdocuments and returns the publicNetworkVerificationConfigtrust-anchor shape. Witness endpoints, witness bearer tokens, and federation authentication tokens are not part of the response.GET /v1/configremains informational only. - Federation guarantees: a
Federatedproof requires valid cross-anchor signatures from configured peer networks and pinned peer verification configs. The configured peer threshold is an independent durability layer, not Byzantine consensus or protection from colluding operators. - TypeScript numeric/wire contract: generated Rust
u64fields areU64 = number | bigint; the centrallossless-jsoncodec preserves exact values throughu64::MAX, emits unquoted bigint numbers and lowercase hash hex, and rejects unsafe numeric inputs. Query methods accept exact U64 values. - WebSocket contract:
auth_requiredis always recognized. A tokenless challenge raisesAuthRequiredErrorwithout reconnecting; a supplied token is replied with and the stream continues. Explicit close/abort cancels reconnect timers.
SDK Version Compatibility
The Witness SDKs (witness-client on crates.io, @witness/sdk on npm) are
versioned against the gateway's /v1/* wire protocol. The /v1/* routes are
versioned collectively (spec ยง3.9).
| SDK | Gateway /v1 wire |
Notes |
|---|---|---|
| 0.8.x | as of workspace 0.8.0 | current pre-1.0 release; TS package is publishable |
| bump rule | additive routes: none needed; any ยง3 change: SDK minor bump + new vectors | ยง3.9 |
Bump rule (spec ยง3.9): any change to the pinned wire/crypto parameters in
docs/sdk-enhancement-spec.md ยง3 is a breaking change โ bump the SDK minor
version (pre-1.0 semver), regenerate the golden vectors in sdk/vectors/, and
note it in docs/release + CHANGELOG.md. Additive /v1/* routes do not
require a bump.
Both package READMEs (crates/witness-client/README.md and
sdk/ts/README.md) carry the pre-1.0 / unaudited / not-Byzantine-fault-tolerant
disclaimer and state that SDK publication is not an audit.
SDK Publication
The Witness SDK work (spec docs/sdk-enhancement-spec.md) adds three
publishable artifacts alongside the existing binaries:
witness-client(crates/witness-client) โ the Rust client SDK. A library crate covering the full attestation lifecycle, the transparency-log read surface, optional WebSocket push, and local verification (thin wrappers overwitness-core).witness-cliis built on it.@witness/sdk(sdk/ts/) โ the TypeScript SDK. ESM-first package with a./verifysubpath, a WASM-compiled local verifier, WebSocket support, and a typed error hierarchy. Types are generated fromwitness-coreserde definitions (zero-diff CI gate). It uses the central lossless JSON codec for protocol responses, events, and WASM inputs; its strict signature-union decoder rejects ambiguous shapes.witness-core-wasm(crates/witness-core-wasm) โ the WASM build ofwitness-core's verification functions that backs@witness/sdk's local verifier (single trust root, spec ยง4.3 Path A). Not published to crates.io; its.wasmis checked in and shipped inside@witness/sdk.
Publication plan (spec ยง5.9): publish witness-core and witness-client
to crates.io (names to be confirmed by maintainers) and @witness/sdk to npm.
Path+version workspace deps make the Rust publish mechanical. Pre-1.0 semver
policy: breaking changes bump the minor; /v1/* routes are versioned
collectively (ยง3.9). Publishing does not imply a security audit โ both
package READMEs say so.
TypeScript npm publication
From sdk/ts/, the npm release gate is intentionally repeatable and inspectable:
npm install
(cd ../.. && ./scripts/check-generated-drift.sh)
npm run build
npm test
npm run typecheck
npm pack --dry-run
npm publish --access public
prepublishOnly runs the build, tests, typecheck, and npm pack --dry-run.
The package is scoped-public, targets Node 22+, includes dist/, README.md,
and the repository's Apache-2.0 LICENSE, and remains subject to the pre-1.0
minor-bump policy. Review the dry-run file list for accidental secrets, source
artifacts, and missing WASM before publishing.
Pre-Tag Checklist
- Update workspace crate versions in
Cargo.toml. - Update
Cargo.lock. - Update
CHANGELOG.md. - Run
cargo fmt --all -- --check. - Run
cargo clippy --workspace --all-targets -- -D warnings. - Run
cargo test --workspace. - Run
cargo build --release --workspace. - From
sdk/ts/, runnpm install,npm run build,npm test,npm run typecheck, andnpm pack --dry-runbeforenpm publish --access public. - Smoke-test Docker images if deployment assets changed.
- Exercise the example network with
./examples/setup.sh,./examples/start.sh,./examples/demo.sh, and./examples/stop.sh. - Test Freebird consuming mode against the current Freebird verifier.
- Check release archives for accidental secrets or generated local state.
Verification
Before installing a release archive, verify checksums:
sha256sum -c SHA256SUMS
When image signing is enabled, verify image signatures before deployment and record the digest in deployment manifests.
Current Gaps
- Keyless signing is not configured; image signing currently expects a cosign private key secret.
- Binary releases are not signed (checksums only).
SDK Phase 0 Decision โ WASM vs. hand-port for the TypeScript crypto module
Status: Decision recorded. This is the Phase 0 deliverable of
docs/sdk-enhancement-spec.md ยง4.3. The TypeScript package README
(sdk/ts/README.md) references this section.
Decision: Path A โ WASM. Compile witness-core's verification functions to
wasm32-unknown-unknown and wrap them in @witness/sdk. Path B (hand port on
@noble/curves) is feasible but is not required, and per ยง4.3 is only
permitted if Path A is infeasible โ it is not.
Path A prototype result (WASM)
A throwaway prototype crate (outside the repo, under a temp dir) compiled
witness-core to wasm32-unknown-unknown in release mode.
- Toolchain:
wasm32-unknown-unknowntarget installed;wasm-packpresent;wasm-bindgennot installed (not needed for the feasibility probe). - Build: SUCCESS.
witness-corecompiled towasm32-unknown-unknown. blstwasm status: SUPPORTED.blst'sbuild.rsdetectswasm32and switches to no-std, defines__BLST_NO_ASM__(no assembly), adds-ffreestanding, and setsSCRATCH_LIMIT.blstcompiled cleanly for wasm.- Only blocker found:
getrandom.witness-core's keygen functions (generate_bls_keypair,generate_keypair) userand::OsRng, which pulls ingetrandom. Onwasm32-unknown-unknown,getrandomrequires either itsjsfeature (wasm-bindgen) or a custom shim. The verification functions never call OsRng, so a verification-only build can gate keygen behind a feature or provide a shim. This is a well-understood, solvable issue โ not a feasibility blocker. - Toolchain note: Apple's system
clanglacks thewasm32target; the C compiler must be a wasm-capable clang (Homebrewllvmorwasi-sdk). This is a build-environment requirement, not a blocker. - Size: 245,949 bytes raw (~240 KB); 91,279 bytes gzipped (~89 KB). This
includes the full
witness-core(all modules, including keygen, federation, and external anchors). A pruned verification-only subset would be smaller. Well under the 350 KB compressed criterion. - Runtime: the module instantiates in Node 22+ with no imports (no JS shim required for the verification path). Browser + Node compatible.
Path B feasibility assessment (@noble/curves)
- Ed25519:
@noble/curves/ed25519โ available. - BLS12-381:
@noble/curves/bls12-381โ available. ItsshortSignaturesvariant is exactly themin_sigorientation witness uses (48-byte G1 signatures, 96-byte G2 public keys). Custom DST is supported viahash(msg, DST). - SHA-256:
@noble/hashesโ available. - Merkle / STH digest: pure JS โ available.
- Audit status: noble-curves has been independently audited (Cure53, Kudelski, Trail of Bits).
Parameters that must be pinned for byte-parity (from spec ยง3):
- BLS DST (byte-exact):
WITNESS_BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_(crates/witness-core/src/bls.rs). min_sigorientation: 48-byte G1 signatures, 96-byte G2 public keys.- Subgroup checks enabled on signature and public key.
- Untagged-union discrimination (ยง3.5): presence of a
signaturesarray โ multi-sig; presence of bothsignatureandsignersโ aggregated; anything else (including payloads with both shapes' keys) โDecodeError. - Hex encoding (ยง3.4): lowercase, no
0xprefix; decoders accept mixed-case input and reject odd-length/non-hex strings; emitters canonicalize lowercase. - RFC 9162 Merkle domain separators (ยง3.6): leaves
SHA-256(0x00 โ leaf), internal nodesSHA-256(0x01 โ left โ right), positional (no sorting). - STH domain separation (ยง3.7):
STH_DOMAIN = b"witness-sth-v1\x00"+ length-prefixed fields. - Canonical signing message (ยง3.1):
hash(32) โ timestamp(8 LE) โ network_id_len(4 LE) โ network_id โ sequence(8 LE).
Verdict: Path B is feasible with no hard blockers, but it doubles the crypto audit surface and must pass the ยง4.2 vector-parity gate before any release.
Decision rationale (against the ยง4.3 criteria, in priority order)
- Vector parity achievable โ yes (both). Path A trivially achieves parity because the exact Rust code ships to the browser; there is no reimplementation to drift. Path B would require the full ยง4.2 golden-vector gate.
- Single trust root preserved โ Path A only. Path A keeps all crypto
semantics in
witness-core(strongly preferred for a pre-1.0, unaudited codebase). Path B reimplements crypto in TS, doubling the audit surface. - Bundle/toolchain cost โ acceptable for Path A. ~89 KB gzipped (well under
350 KB), instantiates with no imports in Node and browsers. The only costs are
a wasm-capable clang for the C compiler and gating/shimming
getrandomfor keygen โ both mechanical.
Open risks / blockers for the chosen path (Path A)
- Keygen vs. verification split: the wasm build must gate the keygen
functions (which need
OsRng/getrandom) behind a feature or provide a wasm shim. Verification is unaffected. - Toolchain: CI/build machines need a clang with the
wasm32target (Homebrewllvmorwasi-sdk); Apple's system clang lacks it. wasm-bindgenglue: not yet prototyped. The real wrapper needswasm-bindgen(or a manual ABI) for ergonomic TS calls;wasm-bindgenis not currently installed.- Final bundle size: the measured ~89 KB gzipped includes the full core; a pruned verification-only build should be re-measured in Phase 2 to confirm the final number.
blstno-stdmode:blstcompiles in no-stdmode for wasm; confirm the verification functions do not depend onstd-only features that are disabled.
Implementation status (Lane J)
The Phase 0 decision is now implemented in crates/witness-core-wasm:
- Keygen gating:
witness-coregained akeygenfeature (default on) that gatesgenerate_keypair/generate_bls_keypair(the onlyOsRngusers).witness-core-wasmdepends onwitness-corewithdefault-features = false, so the wasm build never pulls ingetrandom. Default behavior is unchanged. - Manual ABI: the wasm crate uses
#[no_mangle] extern "C"exports (nowasm-bindgenglue) with a small hand-rolled loader (sdk/ts/src/wasm/loader.ts). Inputs are JSON/hex strings passed as(ptr, len); results are JSON read from a module-global buffer. - Build:
CC=<wasm-capable clang> cargo build -p witness-core-wasm --target wasm32-unknown-unknown --release. The.wasmis checked in atsdk/ts/src/wasm/witness_core_wasm.wasm(~527 KB raw / ~184 KB gzipped) and copied todist/wasm/onnpm run build. - Conformance:
sdk/ts/test/conformance.test.tsruns every golden vector insdk/vectors/against the wasm verifier + TS decoder and passes 100% (ยง4.2 release gate).
Reconciliation โ hex decode correction (spec ยง3.4)
Spec correction, not a protocol change. The original ยง3.4 wording ("TS
decoders must reject uppercase hex, matching hex::decode") was factually wrong:
Rust's hex::decode accepts uppercase. The pure-TS decodeHex was tightened
to reject uppercase, creating a Rust/TS divergence (the WASM verification path
already accepted uppercase via Rust).
Resolution (Option 1 โ make TS accept uppercase):
sdk/ts/src/decode.tshexValnow acceptsA-F;decodeHexaccepts mixed-case hex and rejects odd-length strings and non-hex characters.- Wire emission is unchanged (still lowercase);
Serializeoutput is untouched. - Golden vectors regenerated:
wire.jsongainshex_adapters.decodewith accept cases (uppercase, mixed-case โ pinned bytes + lowercase re-serialization) and reject cases (odd-length, non-hex). - SDK minor version bumped to 0.8.0 (spec ยง3.9).
Also added the missing Rust verify_log_inclusion mirror to witness-client
(thin wrapper over witness-core) and a tree_size-vs-STH equality check in
both the TS and Rust inclusion verifiers (position-awareness, spec ยง3.6).